MethodDefinition.java revision 7e24a9f010eeeff54f7ca0cb589a75cc251fabdd
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.Code.Analysis.AnalyzedInstruction;
35import org.jf.dexlib.Code.Analysis.MethodAnalyzer;
36import org.jf.dexlib.Code.Analysis.RegisterType;
37import org.jf.dexlib.Debug.DebugInstructionIterator;
38import org.jf.dexlib.Code.Instruction;
39import org.jf.dexlib.Code.Opcode;
40import org.jf.dexlib.Code.OffsetInstruction;
41import org.jf.dexlib.Util.AccessFlags;
42import org.antlr.stringtemplate.StringTemplateGroup;
43import org.antlr.stringtemplate.StringTemplate;
44import org.jf.dexlib.Util.SparseIntArray;
45
46import java.util.*;
47
48public class MethodDefinition {
49    private final StringTemplateGroup stg;
50    private final ClassDataItem.EncodedMethod encodedMethod;
51    private final MethodAnalyzer methodAnalyzer;
52
53    private final LabelCache labelCache = new LabelCache();
54
55    private final SparseIntArray packedSwitchMap;
56    private final SparseIntArray sparseSwitchMap;
57    private final SparseIntArray instructionMap;
58
59    public MethodDefinition(StringTemplateGroup stg, ClassDataItem.EncodedMethod encodedMethod) {
60        this.stg = stg;
61        this.encodedMethod = encodedMethod;
62
63        //TODO: what about try/catch blocks inside the dead code? those will need to be commented out too. ugh.
64
65        if (encodedMethod.codeItem != null) {
66            methodAnalyzer = new MethodAnalyzer(encodedMethod);
67            AnalyzedInstruction[] instructions = methodAnalyzer.makeInstructionArray();
68
69            packedSwitchMap = new SparseIntArray(1);
70            sparseSwitchMap = new SparseIntArray(1);
71            instructionMap = new SparseIntArray(instructions.length);
72
73            int currentCodeAddress = 0;
74            for (int i=0; i<instructions.length; i++) {
75                AnalyzedInstruction instruction = instructions[i];
76                if (instruction.instruction.opcode == Opcode.PACKED_SWITCH) {
77                    packedSwitchMap.append(
78                            currentCodeAddress + ((OffsetInstruction)instruction).getTargetAddressOffset(),
79                            currentCodeAddress);
80                } else if (instruction.instruction.opcode == Opcode.SPARSE_SWITCH) {
81                    sparseSwitchMap.append(
82                            currentCodeAddress + ((OffsetInstruction)instruction).getTargetAddressOffset(),
83                            currentCodeAddress);
84                }
85                instructionMap.append(currentCodeAddress, i);
86                currentCodeAddress += instruction.instruction.getSize(currentCodeAddress);
87            }
88        } else {
89            packedSwitchMap = null;
90            sparseSwitchMap = null;
91            instructionMap = null;
92            methodAnalyzer = null;
93        }
94    }
95
96    public StringTemplate createTemplate(AnnotationSetItem annotationSet,
97                                                AnnotationSetRefList parameterAnnotations) {
98
99        CodeItem codeItem = encodedMethod.codeItem;
100
101        StringTemplate template = stg.getInstanceOf("method");
102
103        template.setAttribute("AccessFlags", getAccessFlags(encodedMethod));
104        template.setAttribute("MethodName", encodedMethod.method.getMethodName().getStringValue());
105        template.setAttribute("Prototype", encodedMethod.method.getPrototype().getPrototypeString());
106        template.setAttribute("HasCode", codeItem != null);
107        template.setAttribute("RegistersDirective", baksmali.useLocalsDirective?".locals":".registers");
108        template.setAttribute("RegisterCount", codeItem==null?"0":Integer.toString(getRegisterCount(encodedMethod)));
109        template.setAttribute("Parameters", getParameters(stg, codeItem, parameterAnnotations));
110        template.setAttribute("Annotations", getAnnotations(stg, annotationSet));
111        template.setAttribute("MethodItems", getMethodItems());
112
113        return template;
114    }
115
116    private static int getRegisterCount(ClassDataItem.EncodedMethod encodedMethod)
117    {
118        int totalRegisters = encodedMethod.codeItem.getRegisterCount();
119        if (baksmali.useLocalsDirective) {
120            int parameterRegisters = encodedMethod.method.getPrototype().getParameterRegisterCount();
121            if ((encodedMethod.accessFlags & AccessFlags.STATIC.getValue()) == 0) {
122                parameterRegisters++;
123            }
124            return totalRegisters - parameterRegisters;
125        }
126        return totalRegisters;
127    }
128
129    private static List<String> getAccessFlags(ClassDataItem.EncodedMethod encodedMethod) {
130        List<String> accessFlags = new ArrayList<String>();
131
132        for (AccessFlags accessFlag: AccessFlags.getAccessFlagsForMethod(encodedMethod.accessFlags)) {
133            accessFlags.add(accessFlag.toString());
134        }
135
136        return accessFlags;
137    }
138
139    private static List<StringTemplate> getParameters(StringTemplateGroup stg, CodeItem codeItem,
140                                                               AnnotationSetRefList parameterAnnotations) {
141        DebugInfoItem debugInfoItem = null;
142        if (baksmali.outputDebugInfo && codeItem != null) {
143            debugInfoItem = codeItem.getDebugInfo();
144        }
145
146        int parameterCount = 0;
147
148        List<AnnotationSetItem> annotations = new ArrayList<AnnotationSetItem>();
149        if (parameterAnnotations != null) {
150            AnnotationSetItem[] _annotations = parameterAnnotations.getAnnotationSets();
151            if (_annotations != null) {
152                annotations.addAll(Arrays.asList(_annotations));
153            }
154
155            parameterCount = annotations.size();
156        }
157
158        List<String> parameterNames = new ArrayList<String>();
159        if (debugInfoItem != null) {
160            StringIdItem[] _parameterNames = debugInfoItem.getParameterNames();
161            if (_parameterNames != null) {
162                for (StringIdItem parameterName: _parameterNames) {
163                    parameterNames.add(parameterName==null?null:parameterName.getStringValue());
164                }
165            }
166
167            if (parameterCount < parameterNames.size()) {
168                parameterCount = parameterNames.size();
169            }
170        }
171
172        List<StringTemplate> parameters = new ArrayList<StringTemplate>();
173        for (int i=0; i<parameterCount; i++) {
174            AnnotationSetItem annotationSet = null;
175            if (i < annotations.size()) {
176                annotationSet = annotations.get(i);
177            }
178
179            String parameterName = null;
180            if (i < parameterNames.size()) {
181                parameterName = parameterNames.get(i);
182            }
183
184            parameters.add(ParameterAdaptor.createTemplate(stg, parameterName, annotationSet));
185        }
186
187        return parameters;
188    }
189
190    public LabelCache getLabelCache() {
191        return labelCache;
192    }
193
194    public int getPackedSwitchBaseAddress(int packedSwitchDataAddress) {
195        int packedSwitchBaseAddress = this.packedSwitchMap.get(packedSwitchDataAddress, -1);
196
197        if (packedSwitchBaseAddress == -1) {
198            throw new RuntimeException("Could not find the packed switch statement corresponding to the packed " +
199                    "switch data at address " + packedSwitchDataAddress);
200        }
201
202        return packedSwitchBaseAddress;
203    }
204
205    public int getSparseSwitchBaseAddress(int sparseSwitchDataAddress) {
206        int sparseSwitchBaseAddress = this.sparseSwitchMap.get(sparseSwitchDataAddress, -1);
207
208        if (sparseSwitchBaseAddress == -1) {
209            throw new RuntimeException("Could not find the sparse switch statement corresponding to the sparse " +
210                    "switch data at address " + sparseSwitchDataAddress);
211        }
212
213        return sparseSwitchBaseAddress;
214    }
215
216    private static List<StringTemplate> getAnnotations(StringTemplateGroup stg, AnnotationSetItem annotationSet) {
217        if (annotationSet == null) {
218            return null;
219        }
220
221        List<StringTemplate> annotationAdaptors = new ArrayList<StringTemplate>();
222
223        for (AnnotationItem annotationItem: annotationSet.getAnnotations()) {
224            annotationAdaptors.add(AnnotationAdaptor.createTemplate(stg, annotationItem));
225        }
226        return annotationAdaptors;
227    }
228
229    private List<MethodItem> getMethodItems() {
230        List<MethodItem> methodItems = new ArrayList<MethodItem>();
231
232        if (encodedMethod.codeItem == null) {
233            return methodItems;
234        }
235
236        AnalyzedInstruction[] instructions;
237        if (baksmali.verboseRegisterInfo) {
238            instructions = methodAnalyzer.analyze();
239        } else {
240            instructions = methodAnalyzer.makeInstructionArray();
241        }
242
243        int currentCodeAddress = 0;
244        for (int i=0; i<instructions.length; i++) {
245            AnalyzedInstruction instruction = instructions[i];
246
247            methodItems.add(InstructionMethodItemFactory.makeInstructionFormatMethodItem(this,
248                    encodedMethod.codeItem, currentCodeAddress, stg, instruction.instruction));
249
250            if (i != instructions.length - 1) {
251                methodItems.add(new BlankMethodItem(stg, currentCodeAddress));
252            }
253
254            if (baksmali.verboseRegisterInfo) {
255                if (instruction.getPredecessorCount() > 1 || i == 0) {
256                    methodItems.add(new CommentMethodItem(stg, getPreInstructionRegisterString(instruction),
257                            currentCodeAddress, Integer.MIN_VALUE));
258                }
259                methodItems.add(new CommentMethodItem(stg, getPostInstructionRegisterString(instruction),
260                        currentCodeAddress, Integer.MAX_VALUE-1));
261            }
262
263
264            currentCodeAddress += instruction.instruction.getSize(currentCodeAddress);
265        }
266
267        addTries(methodItems);
268        addDebugInfo(methodItems);
269
270        if (baksmali.useSequentialLabels) {
271            setLabelSequentialNumbers();
272        }
273
274
275        for (LabelMethodItem labelMethodItem: labelCache.getLabels()) {
276            if (labelMethodItem.isCommentedOut()) {
277                methodItems.add(new CommentedOutMethodItem(stg, labelMethodItem));
278            } else {
279                methodItems.add(labelMethodItem);
280            }
281        }
282
283        Collections.sort(methodItems);
284
285        return methodItems;
286    }
287
288    private String getPreInstructionRegisterString(AnalyzedInstruction instruction) {
289        StringBuilder sb = new StringBuilder();
290
291        for (int i=0; i<instruction.getRegisterCount(); i++) {
292            RegisterType registerType = instruction.getPreInstructionRegisterType(i);
293            sb.append("v");
294            sb.append(i);
295            sb.append("=");
296            if (registerType == null) {
297                sb.append("null");
298            } else {
299                sb.append(registerType.toString());
300            }
301            sb.append(";");
302        }
303
304        return sb.toString();
305    }
306
307    private String getPostInstructionRegisterString(AnalyzedInstruction instruction) {
308        StringBuilder sb = new StringBuilder();
309
310        for (int i=0; i<instruction.getRegisterCount(); i++) {
311            RegisterType registerType = instruction.getPostInstructionRegisterType(i);
312            sb.append("v");
313            sb.append(i);
314            sb.append("=");
315            sb.append(registerType.toString());
316            sb.append(";");
317        }
318
319        return sb.toString();
320    }
321
322
323    private void addTries(List<MethodItem> methodItems) {
324        if (encodedMethod.codeItem == null || encodedMethod.codeItem.getTries() == null) {
325            return;
326        }
327
328        Instruction[] instructions = encodedMethod.codeItem.getInstructions();
329
330        for (CodeItem.TryItem tryItem: encodedMethod.codeItem.getTries()) {
331            int startAddress = tryItem.getStartCodeAddress();
332            int endAddress = tryItem.getStartCodeAddress() + tryItem.getTryLength();
333
334            /**
335             * The end address points to the address immediately after the end of the last
336             * instruction that the try block covers. We want the .catch directive and end_try
337             * label to be associated with the last covered instruction, so we need to get
338             * the address for that instruction
339             */
340
341            int index = instructionMap.get(endAddress, -1);
342            int lastInstructionAddress;
343
344            /**
345             * If we couldn't find the index, then the try block probably extends to the last instruction in the
346             * method, and so endAddress would be the address immediately after the end of the last instruction.
347             * Check to make sure this is the case, if not, throw an exception.
348             */
349            if (index == -1) {
350                Instruction lastInstruction = instructions[instructions.length - 1];
351                lastInstructionAddress = instructionMap.keyAt(instructionMap.size() - 1);
352
353                if (endAddress != lastInstructionAddress + lastInstruction.getSize(lastInstructionAddress)) {
354                    throw new RuntimeException("Invalid code offset " + endAddress + " for the try block end address");
355                }
356            } else {
357                if (index == 0) {
358                    throw new RuntimeException("Unexpected instruction index");
359                }
360                Instruction lastInstruction = instructions[index - 1];
361
362                if (lastInstruction.getFormat().variableSizeFormat) {
363                    throw new RuntimeException("This try block unexpectedly ends on a switch/array data block.");
364                }
365
366                //getSize for non-variable size formats should return the same size regardless of code address, so just
367                //use a dummy address of "0"
368                lastInstructionAddress = endAddress - lastInstruction.getSize(0);
369            }
370
371            //add the catch all handler if it exists
372            int catchAllAddress = tryItem.encodedCatchHandler.getCatchAllHandlerAddress();
373            if (catchAllAddress != -1) {
374                CatchMethodItem catchAllMethodItem = new CatchMethodItem(labelCache, lastInstructionAddress, stg, null,
375                        startAddress, endAddress, catchAllAddress);
376                methodItems.add(catchAllMethodItem);
377            }
378
379            //add the rest of the handlers
380            for (CodeItem.EncodedTypeAddrPair handler: tryItem.encodedCatchHandler.handlers) {
381                //use the address from the last covered instruction
382                CatchMethodItem catchMethodItem = new CatchMethodItem(labelCache, lastInstructionAddress, stg,
383                        handler.exceptionType, startAddress, endAddress, handler.getHandlerAddress());
384                methodItems.add(catchMethodItem);
385            }
386        }
387    }
388
389    private void addDebugInfo(final List<MethodItem> methodItems) {
390        if (encodedMethod.codeItem == null || encodedMethod.codeItem.getDebugInfo() == null) {
391            return;
392        }
393
394        final CodeItem codeItem = encodedMethod.codeItem;
395        DebugInfoItem debugInfoItem = codeItem.getDebugInfo();
396
397        DebugInstructionIterator.DecodeInstructions(debugInfoItem, codeItem.getRegisterCount(),
398                new DebugInstructionIterator.ProcessDecodedDebugInstructionDelegate() {
399                    @Override
400                    public void ProcessStartLocal(int codeAddress, int length, int registerNum, StringIdItem name,
401                                                  TypeIdItem type) {
402                        methodItems.add(new LocalDebugMethodItem(codeItem, codeAddress, stg, "StartLocal",
403                                -1, registerNum, name, type, null));
404                    }
405
406                    @Override
407                    public void ProcessStartLocalExtended(int codeAddress, int length, int registerNum,
408                                                          StringIdItem name, TypeIdItem type,
409                                                          StringIdItem signature) {
410                        methodItems.add(new LocalDebugMethodItem(codeItem, codeAddress, stg, "StartLocal",
411                                -1, registerNum, name, type, signature));
412                    }
413
414                    @Override
415                    public void ProcessEndLocal(int codeAddress, int length, int registerNum, StringIdItem name,
416                                                TypeIdItem type, StringIdItem signature) {
417                        methodItems.add(new LocalDebugMethodItem(codeItem, codeAddress, stg, "EndLocal", -1,
418                                registerNum, name, type, signature));
419                    }
420
421                    @Override
422                    public void ProcessRestartLocal(int codeAddress, int length, int registerNum, StringIdItem name,
423                                                    TypeIdItem type, StringIdItem signature) {
424                        methodItems.add(new LocalDebugMethodItem(codeItem, codeAddress, stg, "RestartLocal", -1,
425                                registerNum, name, type, signature));
426                    }
427
428                    @Override
429                    public void ProcessSetPrologueEnd(int codeAddress) {
430                        methodItems.add(new DebugMethodItem(codeAddress, stg, "EndPrologue", -4));
431                    }
432
433                    @Override
434                    public void ProcessSetEpilogueBegin(int codeAddress) {
435                        methodItems.add(new DebugMethodItem(codeAddress, stg, "StartEpilogue", -4));
436                    }
437
438                    @Override
439                    public void ProcessSetFile(int codeAddress, int length, final StringIdItem name) {
440                        methodItems.add(new DebugMethodItem(codeAddress, stg, "SetFile", -3) {
441                            @Override
442                            protected void setAttributes(StringTemplate template) {
443                                template.setAttribute("FileName", name.getStringValue());
444                            }
445                        });
446                    }
447
448                    @Override
449                    public void ProcessLineEmit(int codeAddress, final int line) {
450                         methodItems.add(new DebugMethodItem(codeAddress, stg, "Line", -2) {
451                             @Override
452                             protected void setAttributes(StringTemplate template) {
453                                 template.setAttribute("Line", line);
454                             }
455                         });
456                    }
457                });
458    }
459
460    private void setLabelSequentialNumbers() {
461        HashMap<String, Integer> nextLabelSequenceByType = new HashMap<String, Integer>();
462        ArrayList<LabelMethodItem> sortedLabels = new ArrayList<LabelMethodItem>(labelCache.getLabels());
463
464        //sort the labels by their location in the method
465        Collections.sort(sortedLabels);
466
467        for (LabelMethodItem labelMethodItem: sortedLabels) {
468            Integer labelSequence = nextLabelSequenceByType.get(labelMethodItem.getLabelPrefix());
469            if (labelSequence == null) {
470                labelSequence = 0;
471            }
472            labelMethodItem.setLabelSequence(labelSequence);
473            nextLabelSequenceByType.put(labelMethodItem.getLabelPrefix(), labelSequence + 1);
474        }
475    }
476
477    public static class LabelCache {
478        protected HashMap<LabelMethodItem, LabelMethodItem> labels = new HashMap<LabelMethodItem, LabelMethodItem>();
479
480        public LabelCache() {
481        }
482
483        public LabelMethodItem internLabel(LabelMethodItem labelMethodItem) {
484            LabelMethodItem internedLabelMethodItem = labels.get(labelMethodItem);
485            if (internedLabelMethodItem != null) {
486                if (!labelMethodItem.isCommentedOut()) {
487                    internedLabelMethodItem.setUncommented();
488                }
489                return internedLabelMethodItem;
490            }
491            labels.put(labelMethodItem, labelMethodItem);
492            return labelMethodItem;
493        }
494
495
496        public Collection<LabelMethodItem> getLabels() {
497            return labels.values();
498        }
499    }
500}
501