MethodDefinition.java revision b2e1e2067d5179b0ec38c76a5191909f5a7dc1cc
1/*
2 * [The "BSD licence"]
3 * Copyright (c) 2009 Ben Gruver
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 * 3. The name of the author may not be used to endorse or promote products
15 *    derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29package org.jf.baksmali.Adaptors;
30
31import org.jf.baksmali.Adaptors.Format.*;
32import org.jf.baksmali.baksmali;
33import org.jf.dexlib.*;
34import org.jf.dexlib.Debug.DebugInstructionIterator;
35import org.jf.dexlib.Code.Instruction;
36import org.jf.dexlib.Code.Opcode;
37import org.jf.dexlib.Code.OffsetInstruction;
38import org.jf.dexlib.Util.AccessFlags;
39import org.antlr.stringtemplate.StringTemplateGroup;
40import org.antlr.stringtemplate.StringTemplate;
41import org.jf.dexlib.Util.SparseIntArray;
42
43import java.util.*;
44
45public class MethodDefinition {
46    private final StringTemplateGroup stg;
47    private final ClassDataItem.EncodedMethod encodedMethod;
48
49    private final LabelCache labelCache = new LabelCache();
50
51    private final SparseIntArray packedSwitchMap;
52    private final SparseIntArray sparseSwitchMap;
53    private final SparseIntArray instructionMap;
54
55    public MethodDefinition(StringTemplateGroup stg, ClassDataItem.EncodedMethod encodedMethod) {
56        this.stg = stg;
57        this.encodedMethod = encodedMethod;
58
59        //TODO: what about try/catch blocks inside the dead code? those will need to be commented out too. ugh.
60
61        if (encodedMethod.codeItem != null) {
62            Instruction[] instructions = encodedMethod.codeItem.getInstructions();
63
64            packedSwitchMap = new SparseIntArray(1);
65            sparseSwitchMap = new SparseIntArray(1);
66            instructionMap = new SparseIntArray(instructions.length);
67
68            int currentCodeAddress = 0;
69            for (int i=0; i<instructions.length; i++) {
70                Instruction instruction = instructions[i];
71                if (instruction.opcode == Opcode.PACKED_SWITCH) {
72                    packedSwitchMap.append(
73                            currentCodeAddress + ((OffsetInstruction)instruction).getTargetAddressOffset(),
74                            currentCodeAddress);
75                } else if (instruction.opcode == Opcode.SPARSE_SWITCH) {
76                    sparseSwitchMap.append(
77                            currentCodeAddress + ((OffsetInstruction)instruction).getTargetAddressOffset(),
78                            currentCodeAddress);
79                }
80                instructionMap.append(currentCodeAddress, i);
81                currentCodeAddress += instruction.getSize(currentCodeAddress);
82            }
83        } else {
84            packedSwitchMap = null;
85            sparseSwitchMap = null;
86            instructionMap = null;
87        }
88    }
89
90    public StringTemplate createTemplate(AnnotationSetItem annotationSet,
91                                                AnnotationSetRefList parameterAnnotations) {
92
93        CodeItem codeItem = encodedMethod.codeItem;
94
95        StringTemplate template = stg.getInstanceOf("method");
96
97        template.setAttribute("AccessFlags", getAccessFlags(encodedMethod));
98        template.setAttribute("MethodName", encodedMethod.method.getMethodName().getStringValue());
99        template.setAttribute("Prototype", encodedMethod.method.getPrototype().getPrototypeString());
100        template.setAttribute("HasCode", codeItem != null);
101        template.setAttribute("RegistersDirective", baksmali.useLocalsDirective?".locals":".registers");
102        template.setAttribute("RegisterCount", codeItem==null?"0":Integer.toString(getRegisterCount(encodedMethod)));
103        template.setAttribute("Parameters", getParameters(stg, codeItem, parameterAnnotations));
104        template.setAttribute("Annotations", getAnnotations(stg, annotationSet));
105        template.setAttribute("MethodItems", getMethodItems());
106
107        return template;
108    }
109
110    private static int getRegisterCount(ClassDataItem.EncodedMethod encodedMethod)
111    {
112        int totalRegisters = encodedMethod.codeItem.getRegisterCount();
113        if (baksmali.useLocalsDirective) {
114            int parameterRegisters = encodedMethod.method.getPrototype().getParameterRegisterCount();
115            if ((encodedMethod.accessFlags & AccessFlags.STATIC.getValue()) == 0) {
116                parameterRegisters++;
117            }
118            return totalRegisters - parameterRegisters;
119        }
120        return totalRegisters;
121    }
122
123    private static List<String> getAccessFlags(ClassDataItem.EncodedMethod encodedMethod) {
124        List<String> accessFlags = new ArrayList<String>();
125
126        for (AccessFlags accessFlag: AccessFlags.getAccessFlagsForMethod(encodedMethod.accessFlags)) {
127            accessFlags.add(accessFlag.toString());
128        }
129
130        return accessFlags;
131    }
132
133    private static List<StringTemplate> getParameters(StringTemplateGroup stg, CodeItem codeItem,
134                                                               AnnotationSetRefList parameterAnnotations) {
135        DebugInfoItem debugInfoItem = null;
136        if (baksmali.outputDebugInfo && codeItem != null) {
137            debugInfoItem = codeItem.getDebugInfo();
138        }
139
140        int parameterCount = 0;
141
142        List<AnnotationSetItem> annotations = new ArrayList<AnnotationSetItem>();
143        if (parameterAnnotations != null) {
144            AnnotationSetItem[] _annotations = parameterAnnotations.getAnnotationSets();
145            if (_annotations != null) {
146                annotations.addAll(Arrays.asList(_annotations));
147            }
148
149            parameterCount = annotations.size();
150        }
151
152        List<String> parameterNames = new ArrayList<String>();
153        if (debugInfoItem != null) {
154            StringIdItem[] _parameterNames = debugInfoItem.getParameterNames();
155            if (_parameterNames != null) {
156                for (StringIdItem parameterName: _parameterNames) {
157                    parameterNames.add(parameterName==null?null:parameterName.getStringValue());
158                }
159            }
160
161            if (parameterCount < parameterNames.size()) {
162                parameterCount = parameterNames.size();
163            }
164        }
165
166        List<StringTemplate> parameters = new ArrayList<StringTemplate>();
167        for (int i=0; i<parameterCount; i++) {
168            AnnotationSetItem annotationSet = null;
169            if (i < annotations.size()) {
170                annotationSet = annotations.get(i);
171            }
172
173            String parameterName = null;
174            if (i < parameterNames.size()) {
175                parameterName = parameterNames.get(i);
176            }
177
178            parameters.add(ParameterAdaptor.createTemplate(stg, parameterName, annotationSet));
179        }
180
181        return parameters;
182    }
183
184    public LabelCache getLabelCache() {
185        return labelCache;
186    }
187
188    public int getPackedSwitchBaseAddress(int packedSwitchDataAddress) {
189        int packedSwitchBaseAddress = this.packedSwitchMap.get(packedSwitchDataAddress, -1);
190
191        if (packedSwitchBaseAddress == -1) {
192            throw new RuntimeException("Could not find the packed switch statement corresponding to the packed " +
193                    "switch data at address " + packedSwitchDataAddress);
194        }
195
196        return packedSwitchBaseAddress;
197    }
198
199    public int getSparseSwitchBaseAddress(int sparseSwitchDataAddress) {
200        int sparseSwitchBaseAddress = this.sparseSwitchMap.get(sparseSwitchDataAddress, -1);
201
202        if (sparseSwitchBaseAddress == -1) {
203            throw new RuntimeException("Could not find the sparse switch statement corresponding to the sparse " +
204                    "switch data at address " + sparseSwitchDataAddress);
205        }
206
207        return sparseSwitchBaseAddress;
208    }
209
210    private static List<StringTemplate> getAnnotations(StringTemplateGroup stg, AnnotationSetItem annotationSet) {
211        if (annotationSet == null) {
212            return null;
213        }
214
215        List<StringTemplate> annotationAdaptors = new ArrayList<StringTemplate>();
216
217        for (AnnotationItem annotationItem: annotationSet.getAnnotations()) {
218            annotationAdaptors.add(AnnotationAdaptor.createTemplate(stg, annotationItem));
219        }
220        return annotationAdaptors;
221    }
222
223    private List<MethodItem> getMethodItems() {
224        List<MethodItem> methodItems = new ArrayList<MethodItem>();
225
226        if (encodedMethod.codeItem == null) {
227            return methodItems;
228        }
229
230        Instruction[] instructions = encodedMethod.codeItem.getInstructions();
231
232        int currentCodeAddress = 0;
233        for (int i=0; i<instructions.length; i++) {
234            Instruction instruction = instructions[i];
235
236            methodItems.add(InstructionMethodItemFactory.makeInstructionFormatMethodItem(this,
237                    encodedMethod.codeItem, currentCodeAddress, stg, instruction));
238
239            if (i != instructions.length - 1) {
240                methodItems.add(new BlankMethodItem(stg, currentCodeAddress));
241            }
242
243            currentCodeAddress += instruction.getSize(currentCodeAddress);
244        }
245
246        addTries(methodItems);
247        addDebugInfo(methodItems);
248
249        if (baksmali.useSequentialLabels) {
250            setLabelSequentialNumbers();
251        }
252
253
254        for (LabelMethodItem labelMethodItem: labelCache.getLabels()) {
255            if (labelMethodItem.isCommentedOut()) {
256                methodItems.add(new CommentedOutMethodItem(stg, labelMethodItem));
257            } else {
258                methodItems.add(labelMethodItem);
259            }
260        }
261
262        Collections.sort(methodItems);
263
264        return methodItems;
265    }
266
267    private void addTries(List<MethodItem> methodItems) {
268        if (encodedMethod.codeItem == null || encodedMethod.codeItem.getTries() == null) {
269            return;
270        }
271
272        Instruction[] instructions = encodedMethod.codeItem.getInstructions();
273
274        for (CodeItem.TryItem tryItem: encodedMethod.codeItem.getTries()) {
275            int startAddress = tryItem.getStartCodeAddress();
276            int endAddress = tryItem.getStartCodeAddress() + tryItem.getTryLength();
277
278            /**
279             * The end address points to the address immediately after the end of the last
280             * instruction that the try block covers. We want the .catch directive and end_try
281             * label to be associated with the last covered instruction, so we need to get
282             * the address for that instruction
283             */
284
285            int index = instructionMap.get(endAddress, -1);
286            int lastInstructionAddress;
287
288            /**
289             * If we couldn't find the index, then the try block probably extends to the last instruction in the
290             * method, and so endAddress would be the address immediately after the end of the last instruction.
291             * Check to make sure this is the case, if not, throw an exception.
292             */
293            if (index == -1) {
294                Instruction lastInstruction = instructions[instructions.length - 1];
295                lastInstructionAddress = instructionMap.keyAt(instructionMap.size() - 1);
296
297                if (endAddress != lastInstructionAddress + lastInstruction.getSize(lastInstructionAddress)) {
298                    throw new RuntimeException("Invalid code offset " + endAddress + " for the try block end address");
299                }
300            } else {
301                if (index == 0) {
302                    throw new RuntimeException("Unexpected instruction index");
303                }
304                Instruction lastInstruction = instructions[index - 1];
305
306                if (lastInstruction.getFormat().variableSizeFormat) {
307                    throw new RuntimeException("This try block unexpectedly ends on a switch/array data block.");
308                }
309
310                //getSize for non-variable size formats should return the same size regardless of code address, so just
311                //use a dummy address of "0"
312                lastInstructionAddress = endAddress - lastInstruction.getSize(0);
313            }
314
315            //add the catch all handler if it exists
316            int catchAllAddress = tryItem.encodedCatchHandler.getCatchAllHandlerAddress();
317            if (catchAllAddress != -1) {
318                CatchMethodItem catchAllMethodItem = new CatchMethodItem(labelCache, lastInstructionAddress, stg, null,
319                        startAddress, endAddress, catchAllAddress);
320                methodItems.add(catchAllMethodItem);
321            }
322
323            //add the rest of the handlers
324            for (CodeItem.EncodedTypeAddrPair handler: tryItem.encodedCatchHandler.handlers) {
325                //use the address from the last covered instruction
326                CatchMethodItem catchMethodItem = new CatchMethodItem(labelCache, lastInstructionAddress, stg,
327                        handler.exceptionType, startAddress, endAddress, handler.getHandlerAddress());
328                methodItems.add(catchMethodItem);
329            }
330        }
331    }
332
333    private void addDebugInfo(final List<MethodItem> methodItems) {
334        if (encodedMethod.codeItem == null || encodedMethod.codeItem.getDebugInfo() == null) {
335            return;
336        }
337
338        final CodeItem codeItem = encodedMethod.codeItem;
339        DebugInfoItem debugInfoItem = codeItem.getDebugInfo();
340
341        DebugInstructionIterator.DecodeInstructions(debugInfoItem, codeItem.getRegisterCount(),
342                new DebugInstructionIterator.ProcessDecodedDebugInstructionDelegate() {
343                    @Override
344                    public void ProcessStartLocal(int codeAddress, int length, int registerNum, StringIdItem name,
345                                                  TypeIdItem type) {
346                        methodItems.add(new LocalDebugMethodItem(codeItem, codeAddress, stg, "StartLocal",
347                                -1, registerNum, name, type, null));
348                    }
349
350                    @Override
351                    public void ProcessStartLocalExtended(int codeAddress, int length, int registerNum,
352                                                          StringIdItem name, TypeIdItem type,
353                                                          StringIdItem signature) {
354                        methodItems.add(new LocalDebugMethodItem(codeItem, codeAddress, stg, "StartLocal",
355                                -1, registerNum, name, type, signature));
356                    }
357
358                    @Override
359                    public void ProcessEndLocal(int codeAddress, int length, int registerNum, StringIdItem name,
360                                                TypeIdItem type, StringIdItem signature) {
361                        methodItems.add(new LocalDebugMethodItem(codeItem, codeAddress, stg, "EndLocal", -1,
362                                registerNum, name, type, signature));
363                    }
364
365                    @Override
366                    public void ProcessRestartLocal(int codeAddress, int length, int registerNum, StringIdItem name,
367                                                    TypeIdItem type, StringIdItem signature) {
368                        methodItems.add(new LocalDebugMethodItem(codeItem, codeAddress, stg, "RestartLocal", -1,
369                                registerNum, name, type, signature));
370                    }
371
372                    @Override
373                    public void ProcessSetPrologueEnd(int codeAddress) {
374                        methodItems.add(new DebugMethodItem(codeAddress, stg, "EndPrologue", -4));
375                    }
376
377                    @Override
378                    public void ProcessSetEpilogueBegin(int codeAddress) {
379                        methodItems.add(new DebugMethodItem(codeAddress, stg, "StartEpilogue", -4));
380                    }
381
382                    @Override
383                    public void ProcessSetFile(int codeAddress, int length, final StringIdItem name) {
384                        methodItems.add(new DebugMethodItem(codeAddress, stg, "SetFile", -3) {
385                            @Override
386                            protected void setAttributes(StringTemplate template) {
387                                template.setAttribute("FileName", name.getStringValue());
388                            }
389                        });
390                    }
391
392                    @Override
393                    public void ProcessLineEmit(int codeAddress, final int line) {
394                         methodItems.add(new DebugMethodItem(codeAddress, stg, "Line", -2) {
395                             @Override
396                             protected void setAttributes(StringTemplate template) {
397                                 template.setAttribute("Line", line);
398                             }
399                         });
400                    }
401                });
402    }
403
404    private void setLabelSequentialNumbers() {
405        HashMap<String, Integer> nextLabelSequenceByType = new HashMap<String, Integer>();
406        ArrayList<LabelMethodItem> sortedLabels = new ArrayList<LabelMethodItem>(labelCache.getLabels());
407
408        //sort the labels by their location in the method
409        Collections.sort(sortedLabels);
410
411        for (LabelMethodItem labelMethodItem: sortedLabels) {
412            Integer labelSequence = nextLabelSequenceByType.get(labelMethodItem.getLabelPrefix());
413            if (labelSequence == null) {
414                labelSequence = 0;
415            }
416            labelMethodItem.setLabelSequence(labelSequence);
417            nextLabelSequenceByType.put(labelMethodItem.getLabelPrefix(), labelSequence + 1);
418        }
419    }
420
421    public static class LabelCache {
422        protected HashMap<LabelMethodItem, LabelMethodItem> labels = new HashMap<LabelMethodItem, LabelMethodItem>();
423
424        public LabelCache() {
425        }
426
427        public LabelMethodItem internLabel(LabelMethodItem labelMethodItem) {
428            LabelMethodItem internedLabelMethodItem = labels.get(labelMethodItem);
429            if (internedLabelMethodItem != null) {
430                if (!labelMethodItem.isCommentedOut()) {
431                    internedLabelMethodItem.setUncommented();
432                }
433                return internedLabelMethodItem;
434            }
435            labels.put(labelMethodItem, labelMethodItem);
436            return labelMethodItem;
437        }
438
439
440        public Collection<LabelMethodItem> getLabels() {
441            return labels.values();
442        }
443    }
444}
445