MethodDefinition.java revision d7df5938b154b6d83c8c2e1c7a6fe47f41797e0d
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.dexlib.*;
33import org.jf.dexlib.code.Format.*;
34import org.jf.dexlib.code.Instruction;
35import org.jf.dexlib.code.InstructionField;
36import org.jf.dexlib.code.Opcode;
37import org.jf.dexlib.util.AccessFlags;
38import org.jf.dexlib.util.DebugInfoDecoder;
39
40import java.util.*;
41
42public class MethodDefinition {
43    private ClassDataItem.EncodedMethod encodedMethod;
44    private MethodIdItem methodIdItem;
45    private CodeItem codeItem;
46    private AnnotationSetItem annotationSet;
47    private AnnotationSetRefList parameterAnnotations;
48
49    public MethodDefinition(ClassDataItem.EncodedMethod encodedMethod, AnnotationSetItem annotationSet,
50                            AnnotationSetRefList parameterAnnotations) {
51        this.encodedMethod = encodedMethod;
52        this.methodIdItem = encodedMethod.getMethod();
53        this.codeItem = encodedMethod.getCodeItem();
54        this.annotationSet = annotationSet;
55        this.parameterAnnotations = parameterAnnotations;
56    }
57
58    private String methodName = null;
59    public String getMethodName() {
60        if (methodName == null) {
61            methodName = methodIdItem.getMethodName();
62        }
63        return methodName;
64    }
65
66    private List<String> accessFlags = null;
67    public List<String> getAccessFlags() {
68        if (accessFlags == null) {
69            accessFlags = new ArrayList<String>();
70
71            for (AccessFlags accessFlag: AccessFlags.getAccessFlagsForMethod(encodedMethod.getAccessFlags())) {
72                accessFlags.add(accessFlag.toString());
73            }
74        }
75        return accessFlags;
76    }
77
78    private String prototype = null;
79    public String getPrototype() {
80        if (prototype == null) {
81            prototype = methodIdItem.getPrototype().getPrototypeString();
82        }
83        return prototype;
84    }
85
86    private Boolean hasCode = null;
87    public boolean getHasCode() {
88        if (hasCode == null) {
89            hasCode = (codeItem != null);
90        }
91        return hasCode;
92    }
93
94    private String registerCount = null;
95    public String getRegisterCount() {
96        if (registerCount == null) {
97            if (codeItem == null) {
98                registerCount = "0";
99            } else {
100                registerCount = Integer.toString(codeItem.getRegisterCount());
101            }
102        }
103        return registerCount;
104    }
105
106    public List<AnnotationAdaptor> getAnnotations() {
107        if (annotationSet == null) {
108            return null;
109        }
110
111        List<AnnotationAdaptor> annotationAdaptors = new ArrayList<AnnotationAdaptor>();
112
113        for (AnnotationItem annotationItem: annotationSet.getAnnotationItems()) {
114            annotationAdaptors.add(new AnnotationAdaptor(annotationItem));
115        }
116        return annotationAdaptors;
117    }
118
119    public List<ParameterAdaptor> getParameters() {
120        DebugInfoItem debugInfoItem = null;
121        if (codeItem != null) {
122            debugInfoItem = codeItem.getDebugInfo();
123        }
124
125        int parameterCount = 0;
126
127        List<AnnotationSetItem> annotations = new ArrayList<AnnotationSetItem>();
128        if (parameterAnnotations != null) {
129            List<AnnotationSetItem> _annotations = parameterAnnotations.getAnnotationSets();
130            if (_annotations != null) {
131                annotations.addAll(_annotations);
132            }
133
134            parameterCount = annotations.size();
135        }
136
137        List<String> parameterNames = new ArrayList<String>();
138        if (debugInfoItem != null) {
139            List<String> _parameterNames = debugInfoItem.getParameterNames();
140            if (_parameterNames != null) {
141                parameterNames.addAll(_parameterNames);
142            }
143
144            if (parameterCount < parameterNames.size()) {
145                parameterCount = parameterNames.size();
146            }
147        }
148
149        List<ParameterAdaptor> parameterAdaptors = new ArrayList<ParameterAdaptor>();
150        for (int i=0; i<parameterCount; i++) {
151            AnnotationSetItem annotationSet = null;
152            if (i < annotations.size()) {
153                annotationSet = annotations.get(i);
154            }
155
156            String parameterName = null;
157            if (i < parameterNames.size()) {
158                parameterName = parameterNames.get(i);
159            }
160
161            parameterAdaptors.add(new ParameterAdaptor(parameterName, annotationSet));
162        }
163
164        return parameterAdaptors;
165    }
166
167    private List<List<AnnotationAdaptor>> getParameterAnnotations() {
168        if (parameterAnnotations == null) {
169            return null;
170        }
171
172        List<List<AnnotationAdaptor>> parameterAnnotationList = new ArrayList<List<AnnotationAdaptor>>();
173
174        List<AnnotationSetItem> annotationSets = parameterAnnotations.getAnnotationSets();
175
176        for (AnnotationSetItem annotationSet: annotationSets) {
177            List<AnnotationAdaptor> parameterAnnotationAdaptors = new ArrayList<AnnotationAdaptor>();
178            for (AnnotationItem annotationItem: annotationSet.getAnnotationItems()) {
179                parameterAnnotationAdaptors.add(new AnnotationAdaptor(annotationItem));
180            }
181            parameterAnnotationList.add(parameterAnnotationAdaptors);
182        }
183
184        return parameterAnnotationList;
185    }
186
187    public List<String> getParameterNames() {
188        if (codeItem == null) {
189            return null;
190        }
191
192        DebugInfoItem debugInfoItem = codeItem.getDebugInfo();
193        if (debugInfoItem == null) {
194            return null;
195        }
196
197        return debugInfoItem.getParameterNames();
198    }
199
200    private List<MethodItem> methodItems = null;
201    public List<MethodItem> getMethodItems() {
202        if (methodItems == null) {
203            MethodItemList methodItemList = new MethodItemList();
204            methodItemList.generateMethodItemList();
205
206            methodItems = new ArrayList<MethodItem>();
207
208            methodItems.addAll(methodItemList.labels);
209            methodItems.addAll(methodItemList.instructions);
210            methodItems.addAll(methodItemList.blanks);
211            methodItems.addAll(methodItemList.catches);
212            methodItems.addAll(methodItemList.debugItems);
213            Collections.sort(methodItems);
214        }
215        return methodItems;
216    }
217
218
219    private class MethodItemList {
220        public HashSet<LabelMethodItem> labels = new HashSet<LabelMethodItem>();
221        public List<MethodItem> instructions = new ArrayList<MethodItem>();
222        public List<BlankMethodItem> blanks = new ArrayList<BlankMethodItem>();
223        public List<CatchMethodItem> catches = new ArrayList<CatchMethodItem>();
224        public List<MethodItem> debugItems = new ArrayList<MethodItem>();
225
226        private HashMap<Integer, Integer> packedSwitchMap = new HashMap<Integer, Integer>();
227        private HashMap<Integer, Integer> sparseSwitchMap = new HashMap<Integer, Integer>();
228
229
230        public void generateMethodItemList() {
231            if (codeItem == null) {
232                return;
233            }
234
235            int offset = 0;
236            for (InstructionField instructionField: codeItem.getInstructions()) {
237                Instruction instruction = instructionField.getInstruction();
238                if (instruction.getOpcode() == Opcode.PACKED_SWITCH) {
239                    packedSwitchMap.put(offset+((Instruction31t)instruction).getOffset(), offset);
240                } else if (instruction.getOpcode() == Opcode.SPARSE_SWITCH) {
241                    sparseSwitchMap.put(offset+((Instruction31t)instruction).getOffset(), offset);
242                }
243
244                offset += instructionField.getSize(offset * 2) / 2;
245            }
246
247            offset = 0;
248            for (InstructionField instructionField: codeItem.getInstructions()) {
249                addMethodItemsForInstruction(offset, instructionField);
250                blanks.add(new BlankMethodItem(offset));
251                offset += instructionField.getSize(offset*2) / 2;
252            }
253            blanks.remove(blanks.size()-1);
254
255            addTries();
256
257            addDebugInfo();
258        }
259
260        private void addMethodItemsForInstruction(int offset, InstructionField instructionField) {
261            Instruction instruction = instructionField.getInstruction();
262
263            switch (instruction.getFormat()) {
264                case Format10t:
265                    instructions.add(new Instruction10tMethodItem(offset, (Instruction10t)instruction));
266                    labels.add(new LabelMethodItem(offset + ((Instruction10t)instruction).getOffset(), "goto_"));
267                    return;
268                case Format10x:
269                    instructions.add(new Instruction10xMethodItem(offset, (Instruction10x)instruction));
270                    return;
271                case Format11n:
272                    instructions.add(new Instruction11nMethodItem(offset, (Instruction11n)instruction));
273                    return;
274                case Format11x:
275                    instructions.add(new Instruction11xMethodItem(offset, (Instruction11x)instruction));
276                    return;
277                case Format12x:
278                    instructions.add(new Instruction12xMethodItem(offset, (Instruction12x)instruction));
279                    return;
280                case Format20t:
281                    instructions.add(new Instruction20tMethodItem(offset, (Instruction20t)instruction));
282                    labels.add(new LabelMethodItem(offset + ((Instruction20t)instruction).getOffset(), "goto_"));
283                    return;
284                case Format21c:
285                    instructions.add(new Instruction21cMethodItem(offset, (Instruction21c)instruction));
286                    return;
287                case Format21h:
288                    instructions.add(new Instruction21hMethodItem(offset, (Instruction21h)instruction));
289                    return;
290                case Format21s:
291                    instructions.add(new Instruction21sMethodItem(offset, (Instruction21s)instruction));
292                    return;
293                case Format21t:
294                    instructions.add(new Instruction21tMethodItem(offset, (Instruction21t)instruction));
295                    labels.add(new LabelMethodItem(offset + ((Instruction21t)instruction).getOffset(), "cond_"));
296                    return;
297                case Format22b:
298                    instructions.add(new Instruction22bMethodItem(offset, (Instruction22b)instruction));
299                    return;
300                case Format22c:
301                    instructions.add(new Instruction22cMethodItem(offset, (Instruction22c)instruction));
302                    return;
303                case Format22s:
304                    instructions.add(new Instruction22sMethodItem(offset, (Instruction22s)instruction));
305                    return;
306                case Format22t:
307                    instructions.add(new Instruction22tMethodItem(offset, (Instruction22t)instruction));
308                    labels.add(new LabelMethodItem(offset + ((Instruction22t)instruction).getOffset(), "cond_"));
309                    return;
310                case Format22x:
311                    instructions.add(new Instruction22xMethodItem(offset, (Instruction22x)instruction));
312                    return;
313                case Format23x:
314                    instructions.add(new Instruction23xMethodItem(offset, (Instruction23x)instruction));
315                    return;
316                case Format30t:
317                    instructions.add(new Instruction30tMethodItem(offset, (Instruction30t)instruction));
318                    labels.add(new LabelMethodItem(offset + ((Instruction30t)instruction).getOffset(), "goto_"));
319                    return;
320                case Format31c:
321                    instructions.add(new Instruction31cMethodItem(offset, (Instruction31c)instruction));
322                    return;
323                case Format31i:
324                    instructions.add(new Instruction31iMethodItem(offset, (Instruction31i)instruction));
325                    return;
326                case Format31t:
327                    instructions.add(new Instruction31tMethodItem(offset, (Instruction31t)instruction));
328                    if (instruction.getOpcode() == Opcode.FILL_ARRAY_DATA) {
329                        labels.add(new LabelMethodItem(offset + ((Instruction31t)instruction).getOffset(), "array_"));
330                    } else if (instruction.getOpcode() == Opcode.PACKED_SWITCH) {
331                        labels.add(new LabelMethodItem(offset + ((Instruction31t)instruction).getOffset(), "pswitch_data_"));
332                    } else if (instruction.getOpcode() == Opcode.SPARSE_SWITCH) {
333                        labels.add(new LabelMethodItem(offset + ((Instruction31t)instruction).getOffset(), "sswitch_data_"));
334                    }
335                    return;
336                case Format32x:
337                    instructions.add(new Instruction32xMethodItem(offset, (Instruction32x)instruction));
338                    return;
339                case Format35c:
340                    instructions.add(new Instruction35cMethodItem(offset, (Instruction35c)instruction));
341                    return;
342                case Format3rc:
343                    instructions.add(new Instruction3rcMethodItem(offset, (Instruction3rc)instruction));
344                    return;
345                case Format51l:
346                    instructions.add(new Instruction51lMethodItem(offset, (Instruction51l)instruction));
347                    return;
348                case ArrayData:
349                    instructions.add(new ArrayDataMethodItem(offset, (ArrayDataPseudoInstruction)instruction));
350                    return;
351                case PackedSwitchData:
352                {
353                    Integer baseAddress = packedSwitchMap.get(offset);
354
355                    if (baseAddress != null) {
356                        instructions.add(new PackedSwitchMethodItem(offset,
357                                (PackedSwitchDataPseudoInstruction)instruction, baseAddress));
358                        for (int target: ((PackedSwitchDataPseudoInstruction)instruction).getTargets()) {
359                            labels.add(new LabelMethodItem(baseAddress + target, "pswitch_"));
360                        }
361                    }
362                    return;
363                }
364                case SparseSwitchData:
365                {
366                    Integer baseAddress = sparseSwitchMap.get(offset);
367                    if (baseAddress != null) {
368                        instructions.add(new SparseSwitchMethodItem(offset,
369                                (SparseSwitchDataPseudoInstruction)instruction, baseAddress));
370                        for (int target: ((SparseSwitchDataPseudoInstruction)instruction).getTargets()) {
371                            labels.add(new LabelMethodItem(baseAddress + target, "sswitch_"));
372                        }
373                    }
374                    return;
375                }
376            }
377        }
378
379        private void addTries() {
380            for (CodeItem.TryItem tryItem: codeItem.getTries()) {
381                int startAddress = tryItem.getStartAddress();
382                int endAddress = tryItem.getEndAddress();
383
384                /**
385                 * The end address points to the address immediately after the end of the last
386                 * instruction that the try block covers. We want the .catch directive and end_try
387                 * label to be associated with the last covered instruction, so we need to get
388                 * the offset for that instruction
389                 */
390                int index = Collections.binarySearch(instructions, new BlankMethodItem(endAddress));
391                if (index < 0) {
392                    index = (index * -1) - 1;
393                }
394                //index should never by 0, so this should be safe
395                if (index == instructions.size()) {
396                    //if the end address is the same as the address of the last instruction, then
397                    //this try item ends at the next to last instruction.
398                    //otherwise, if the end address is past the address of the last instruction,
399                    //thin this try item ends at the last instruction
400                    if (instructions.get(instructions.size() - 1).getOffset() == endAddress) {
401                        //get the address for the next to last instruction
402                        index -= 2;
403                    } else {
404                        //get the address for the last instruction
405                        index--;
406                    }
407                } else {
408                    index -= 2;
409                }
410
411                int lastInstructionOffset = instructions.get(index).getOffset();
412
413                //add the catch all handler if it exists
414                int catchAllAddress = tryItem.getHandler().getCatchAllAddress();
415                if (catchAllAddress != -1) {
416                    CatchMethodItem catchMethodItem = new CatchMethodItem(lastInstructionOffset, null, startAddress,
417                            endAddress, catchAllAddress) {
418                        public String getTemplate() {
419                            return "CatchAll";
420                        }
421                    };
422                    catches.add(catchMethodItem);
423
424                    labels.add(new LabelMethodItem(startAddress, "try_start_"));
425                    //use the offset from the last covered instruction, but make the label
426                    //name refer to the address of the next instruction
427                    labels.add(new EndTryLabelMethodItem(lastInstructionOffset, endAddress));
428                    labels.add(new LabelMethodItem(catchAllAddress, "handler_"));
429
430                }
431
432                //add the rest of the handlers
433                //TODO: find adjacent handlers for the same type and combine them
434                for (CodeItem.EncodedTypeAddrPair handler: tryItem.getHandler().getHandlers()) {
435                    //use the offset from the last covered instruction
436                    CatchMethodItem catchMethodItem = new CatchMethodItem(lastInstructionOffset,
437                            handler.getTypeReferenceField(), startAddress, endAddress, handler.getHandlerAddress());
438                    catches.add(catchMethodItem);
439
440                    labels.add(new LabelMethodItem(startAddress, "try_start_"));
441                    //use the offset from the last covered instruction, but make the label
442                    //name refer to the address of the next instruction
443                    labels.add(new EndTryLabelMethodItem(lastInstructionOffset, endAddress));
444                    labels.add(new LabelMethodItem(handler.getHandlerAddress(), "handler_"));
445                }
446            }
447        }
448
449        private void addDebugInfo() {
450            DebugInfoItem debugInfoItem = codeItem.getDebugInfo();
451            if (debugInfoItem == null) {
452                return;
453            }
454
455            DebugInfoDecoder decoder = new DebugInfoDecoder(debugInfoItem, new DebugInfoDelegate(),
456                    codeItem.getRegisterCount());
457            decoder.decode();
458        }
459
460        private class DebugInfoDelegate implements DebugInfoDecoder.DebugInfoDelegate {
461
462            public void endPrologue(int address) {
463                debugItems.add(new DebugMethodItem(address, "EndPrologue", -4));
464            }
465
466            public void startEpilogue(int address) {
467                debugItems.add(new DebugMethodItem(address, "StartEpilogue", -4));
468            }
469
470            public void startLocal(int address, DebugInfoDecoder.Local local) {
471                debugItems.add(new LocalDebugMethodItem(address, "StartLocal", -1, local));
472            }
473
474            public void endLocal(int address, DebugInfoDecoder.Local local) {
475                debugItems.add(new LocalDebugMethodItem(address, "EndLocal", -1, local));
476            }
477
478            public void restartLocal(int address, DebugInfoDecoder.Local local) {
479                debugItems.add(new LocalDebugMethodItem(address, "RestartLocal", -1, local));
480            }
481
482            public void setFile(int address, final StringIdItem fileName) {
483                debugItems.add(new DebugMethodItem(address, "SetFile", -3) {
484                    public String getFileName() {
485                        return fileName.getStringValue();
486                    }
487                });
488            }
489
490            public void line(int address, final int line) {
491                debugItems.add(new DebugMethodItem(address, "Line", -2) {
492                    public int getLine() {
493                        return line;
494                    }
495                });
496            }
497        }
498    }
499}
500