MethodDefinition.java revision 7fe0543b660587a77f6013905a85e4f737a9a851
1/*
2 * [The "BSD licence"]
3 * Copyright (c) 2010 Ben Gruver (JesusFreke)
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 com.google.common.collect.ImmutableList;
32import org.jf.baksmali.Adaptors.Debug.DebugMethodItem;
33import org.jf.baksmali.Adaptors.Format.InstructionMethodItemFactory;
34import org.jf.dexlib2.AccessFlags;
35import org.jf.dexlib2.Opcode;
36import org.jf.dexlib2.iface.*;
37import org.jf.dexlib2.iface.debug.DebugItem;
38import org.jf.dexlib2.iface.instruction.Instruction;
39import org.jf.dexlib2.iface.instruction.OffsetInstruction;
40import org.jf.dexlib2.util.InstructionOffsetMap;
41import org.jf.dexlib2.util.MethodUtil;
42import org.jf.dexlib2.util.TypeUtils;
43import org.jf.util.IndentingWriter;
44import org.jf.baksmali.baksmali;
45import org.jf.util.ExceptionWithContext;
46import org.jf.dexlib.Util.SparseIntArray;
47
48import javax.annotation.Nonnull;
49import java.io.IOException;
50import java.util.*;
51
52public class MethodDefinition {
53    @Nonnull public final ClassDefinition classDef;
54    @Nonnull public final Method method;
55    @Nonnull public final MethodImplementation methodImpl;
56    @Nonnull public final ImmutableList<Instruction> instructions;
57    @Nonnull public final ImmutableList<MethodParameter> methodParameters;
58    public RegisterFormatter registerFormatter;
59
60    @Nonnull private final LabelCache labelCache = new LabelCache();
61
62    @Nonnull private final SparseIntArray packedSwitchMap;
63    @Nonnull private final SparseIntArray sparseSwitchMap;
64    @Nonnull private final InstructionOffsetMap instructionOffsetMap;
65
66    public MethodDefinition(@Nonnull ClassDefinition classDef, @Nonnull Method method,
67                            @Nonnull MethodImplementation methodImpl) {
68        this.classDef = classDef;
69        this.method = method;
70        this.methodImpl = methodImpl;
71
72
73        try {
74            //TODO: what about try/catch blocks inside the dead code? those will need to be commented out too. ugh.
75
76            instructions = ImmutableList.copyOf(methodImpl.getInstructions());
77            methodParameters = ImmutableList.copyOf(method.getParameters());
78
79            packedSwitchMap = new SparseIntArray(0);
80            sparseSwitchMap = new SparseIntArray(0);
81            instructionOffsetMap = new InstructionOffsetMap(instructions);
82
83            for (int i=0; i<instructions.size(); i++) {
84                Instruction instruction = instructions.get(i);
85
86                Opcode opcode = instruction.getOpcode();
87                if (opcode == Opcode.PACKED_SWITCH) {
88                    int codeOffset = instructionOffsetMap.getInstructionCodeOffset(i);
89                    int targetOffset = codeOffset + ((OffsetInstruction)instruction).getCodeOffset();
90                    targetOffset = findSwitchPayload(targetOffset, Opcode.PACKED_SWITCH_PAYLOAD);
91                    packedSwitchMap.append(targetOffset, codeOffset);
92                } else if (opcode == Opcode.SPARSE_SWITCH) {
93                    int codeOffset = instructionOffsetMap.getInstructionCodeOffset(i);
94                    int targetOffset = codeOffset + ((OffsetInstruction)instruction).getCodeOffset();
95                    targetOffset = findSwitchPayload(targetOffset, Opcode.SPARSE_SWITCH_PAYLOAD);
96                    sparseSwitchMap.append(targetOffset, codeOffset);
97                }
98            }
99        }catch (Exception ex) {
100            String methodString;
101            try {
102                methodString = MethodUtil.buildFullMethodString(classDef.classDef, method);
103            } catch (Exception ex2) {
104                throw ExceptionWithContext.withContext(ex, "Error while processing method");
105            }
106            throw ExceptionWithContext.withContext(ex, "Error while processing method %s", methodString);
107        }
108    }
109
110    public static void writeEmptyMethodTo(IndentingWriter writer, Method method) throws IOException {
111        writer.write(".method ");
112        writeAccessFlags(writer, method.getAccessFlags());
113        writer.write(method.getName());
114        writer.write("(");
115        ImmutableList<MethodParameter> methodParameters = ImmutableList.copyOf(method.getParameters());
116        for (MethodParameter parameter: methodParameters) {
117            writer.write(parameter.getType());
118        }
119        writer.write(")");
120        writer.write(method.getReturnType());
121        writer.write('\n');
122
123        writer.indent(4);
124        writeParameters(writer, method, methodParameters);
125        AnnotationFormatter.writeTo(writer, method.getAnnotations());
126        writer.deindent(4);
127        writer.write(".end method\n");
128    }
129
130    public void writeTo(IndentingWriter writer) throws IOException {
131        int parameterRegisterCount = 0;
132        if (!AccessFlags.STATIC.isSet(method.getAccessFlags())) {
133            parameterRegisterCount++;
134        }
135
136        writer.write(".method ");
137        writeAccessFlags(writer, method.getAccessFlags());
138        writer.write(method.getName());
139        writer.write("(");
140        for (MethodParameter parameter: methodParameters) {
141            String type = parameter.getType();
142            writer.write(type);
143            parameterRegisterCount++;
144            if (TypeUtils.isWideType(type)) {
145                parameterRegisterCount++;
146            }
147        }
148        writer.write(")");
149        writer.write(method.getReturnType());
150        writer.write('\n');
151
152        writer.indent(4);
153        if (baksmali.useLocalsDirective) {
154            writer.write(".locals ");
155            writer.printSignedIntAsDec(methodImpl.getRegisterCount() - parameterRegisterCount);
156        } else {
157            writer.write(".registers ");
158            writer.printSignedIntAsDec(methodImpl.getRegisterCount());
159        }
160        writer.write('\n');
161        writeParameters(writer, method, methodParameters);
162
163        if (registerFormatter == null) {
164            registerFormatter = new RegisterFormatter(methodImpl.getRegisterCount(), parameterRegisterCount);
165        }
166
167        AnnotationFormatter.writeTo(writer, method.getAnnotations());
168
169        writer.write('\n');
170
171        List<MethodItem> methodItems = getMethodItems();
172        int size = methodItems.size();
173        for (int i=0; i<size; i++) {
174            MethodItem methodItem = methodItems.get(i);
175            if (methodItem.writeTo(writer)) {
176                writer.write('\n');
177            }
178        }
179        writer.deindent(4);
180        writer.write(".end method\n");
181    }
182
183    private int findSwitchPayload(int targetOffset, Opcode type) {
184        int targetIndex = instructionOffsetMap.getInstructionIndexAtCodeOffset(targetOffset);
185
186        //TODO: does dalvik let you pad with multiple nops?
187        //TODO: does dalvik let a switch instruction point to a non-payload instruction?
188
189        Instruction instruction = instructions.get(targetIndex);
190        if (instruction.getOpcode() != type) {
191            // maybe it's pointing to a NOP padding instruction. Look at the next instruction
192            if (instruction.getOpcode() == Opcode.NOP) {
193                targetIndex += 1;
194                if (targetIndex < instructions.size()) {
195                    instruction = instructions.get(targetIndex);
196                    if (instruction.getOpcode() == type) {
197                        return instructionOffsetMap.getInstructionCodeOffset(targetIndex);
198                    }
199                }
200            }
201            throw new ExceptionWithContext("No switch payload at offset 0x%x", targetOffset);
202        } else {
203            return targetOffset;
204        }
205    }
206
207    private static void writeAccessFlags(IndentingWriter writer, int accessFlags)
208            throws IOException {
209        for (AccessFlags accessFlag: AccessFlags.getAccessFlagsForMethod(accessFlags)) {
210            writer.write(accessFlag.toString());
211            writer.write(' ');
212        }
213    }
214
215    private static void writeParameters(IndentingWriter writer, Method method,
216                                        List<? extends MethodParameter> parameters) throws IOException {
217        boolean isStatic = AccessFlags.STATIC.isSet(method.getAccessFlags());
218        int registerNumber = isStatic?0:1;
219        for (MethodParameter parameter: parameters) {
220            String parameterType = parameter.getType();
221            String parameterName = parameter.getName();
222            List<? extends Annotation> annotations = parameter.getAnnotations();
223            if (parameterName != null || annotations.size() != 0) {
224                writer.write(".param p");
225                writer.printSignedIntAsDec(registerNumber);
226                if (parameterName != null) {
227                    writer.write(", ");
228                    // TODO: does dalvik allow non-identifier parameter and/or local names?
229                    writer.write(parameterName);
230                }
231                writer.write("    # ");
232                writer.write(parameterType);
233                if (annotations.size() > 0) {
234                    writer.indent(4);
235                    AnnotationFormatter.writeTo(writer, annotations);
236                    writer.deindent(4);
237                    writer.write(".end param\n");
238                } else {
239                    writer.write("\n");
240                }
241            }
242
243            registerNumber++;
244            if (TypeUtils.isWideType(parameterType)) {
245                registerNumber++;
246            }
247        }
248    }
249
250    @Nonnull public LabelCache getLabelCache() {
251        return labelCache;
252    }
253
254    public int getPackedSwitchBaseAddress(int packedSwitchPayloadCodeOffset) {
255        return packedSwitchMap.get(packedSwitchPayloadCodeOffset, -1);
256    }
257
258    public int getSparseSwitchBaseAddress(int sparseSwitchPayloadCodeOffset) {
259        return sparseSwitchMap.get(sparseSwitchPayloadCodeOffset, -1);
260    }
261
262    private List<MethodItem> getMethodItems() {
263        ArrayList<MethodItem> methodItems = new ArrayList<MethodItem>();
264
265        //TODO: addAnalyzedInstructionMethodItems
266        addInstructionMethodItems(methodItems);
267
268        addTries(methodItems);
269        if (baksmali.outputDebugInfo) {
270            addDebugInfo(methodItems);
271        }
272
273        if (baksmali.useSequentialLabels) {
274            setLabelSequentialNumbers();
275        }
276
277        for (LabelMethodItem labelMethodItem: labelCache.getLabels()) {
278            methodItems.add(labelMethodItem);
279        }
280
281        Collections.sort(methodItems);
282
283        return methodItems;
284    }
285
286    private void addInstructionMethodItems(List<MethodItem> methodItems) {
287        int currentCodeAddress = 0;
288        for (int i=0; i<instructions.size(); i++) {
289            Instruction instruction = instructions.get(i);
290
291            MethodItem methodItem = InstructionMethodItemFactory.makeInstructionFormatMethodItem(this,
292                    currentCodeAddress, instruction);
293
294            methodItems.add(methodItem);
295
296            if (i != instructions.size() - 1) {
297                methodItems.add(new BlankMethodItem(currentCodeAddress));
298            }
299
300            if (baksmali.addCodeOffsets) {
301                methodItems.add(new MethodItem(currentCodeAddress) {
302
303                    @Override
304                    public double getSortOrder() {
305                        return -1000;
306                    }
307
308                    @Override
309                    public boolean writeTo(IndentingWriter writer) throws IOException {
310                        writer.write("#@");
311                        writer.printUnsignedLongAsHex(codeAddress & 0xFFFFFFFFL);
312                        return true;
313                    }
314                });
315            }
316
317            //TODO: uncomment
318            /*if (!baksmali.noAccessorComments && (instruction instanceof InstructionWithReference)) {
319                Opcode opcode = instruction.getOpcode();
320                if (opcode == Opcode.INVOKE_STATIC || opcode == Opcode.INVOKE_STATIC_RANGE) {
321                    MethodIdItem methodIdItem =
322                            (MethodIdItem)((InstructionWithReference) instruction).getReferencedItem();
323
324                    if (SyntheticAccessorResolver.looksLikeSyntheticAccessor(methodIdItem)) {
325                        SyntheticAccessorResolver.AccessedMember accessedMember =
326                                baksmali.syntheticAccessorResolver.getAccessedMember(methodIdItem);
327                        if (accessedMember != null) {
328                            methodItems.add(new SyntheticAccessCommentMethodItem(accessedMember, currentCodeAddress));
329                        }
330                    }
331                }
332            }*/
333
334            currentCodeAddress += instruction.getCodeUnits();
335        }
336    }
337
338    //TODO: uncomment
339    /*private void addAnalyzedInstructionMethodItems(List<MethodItem> methodItems) {
340        methodAnalyzer = new MethodAnalyzer(encodedMethod, baksmali.deodex, baksmali.inlineResolver);
341
342        methodAnalyzer.analyze();
343
344        ValidationException validationException = methodAnalyzer.getValidationException();
345        if (validationException != null) {
346            methodItems.add(new CommentMethodItem(
347                    String.format("ValidationException: %s" ,validationException.getMessage()),
348                    validationException.getCodeAddress(), Integer.MIN_VALUE));
349        } else if (baksmali.verify) {
350            methodAnalyzer.verify();
351
352            validationException = methodAnalyzer.getValidationException();
353            if (validationException != null) {
354                methodItems.add(new CommentMethodItem(
355                        String.format("ValidationException: %s" ,validationException.getMessage()),
356                        validationException.getCodeAddress(), Integer.MIN_VALUE));
357            }
358        }
359
360        List<AnalyzedInstruction> instructions = methodAnalyzer.getInstructions();
361
362        int currentCodeAddress = 0;
363        for (int i=0; i<instructions.size(); i++) {
364            AnalyzedInstruction instruction = instructions.get(i);
365
366            MethodItem methodItem = InstructionMethodItemFactory.makeInstructionFormatMethodItem(this,
367                    encodedMethod.codeItem, currentCodeAddress, instruction.getInstruction());
368
369            methodItems.add(methodItem);
370
371            if (instruction.getInstruction().getFormat() == Format.UnresolvedOdexInstruction) {
372                methodItems.add(new CommentedOutMethodItem(
373                        InstructionMethodItemFactory.makeInstructionFormatMethodItem(this,
374                                encodedMethod.codeItem, currentCodeAddress, instruction.getOriginalInstruction())));
375            }
376
377            if (i != instructions.size() - 1) {
378                methodItems.add(new BlankMethodItem(currentCodeAddress));
379            }
380
381            if (baksmali.addCodeOffsets) {
382                methodItems.add(new MethodItem(currentCodeAddress) {
383
384                    @Override
385                    public double getSortOrder() {
386                        return -1000;
387                    }
388
389                    @Override
390                    public boolean writeTo(IndentingWriter writer) throws IOException {
391                        writer.write("#@");
392                        writer.printUnsignedLongAsHex(codeAddress & 0xFFFFFFFF);
393                        return true;
394                    }
395                });
396            }
397
398            if (baksmali.registerInfo != 0 && !instruction.getInstruction().getFormat().variableSizeFormat) {
399                methodItems.add(
400                        new PreInstructionRegisterInfoMethodItem(instruction, methodAnalyzer, currentCodeAddress));
401
402                methodItems.add(
403                        new PostInstructionRegisterInfoMethodItem(instruction, methodAnalyzer, currentCodeAddress));
404            }
405
406            currentCodeAddress += instruction.getInstruction().getSize(currentCodeAddress);
407        }
408    }*/
409
410    private void addTries(List<MethodItem> methodItems) {
411        List<? extends TryBlock> tryBlocks = methodImpl.getTryBlocks();
412        if (tryBlocks.size() == 0) {
413            return;
414        }
415
416        int lastInstructionAddress = instructionOffsetMap.getInstructionCodeOffset(instructions.size() - 1);
417        int codeSize = lastInstructionAddress + instructions.get(instructions.size() - 1).getCodeUnits();
418
419        for (TryBlock tryBlock: tryBlocks) {
420            int startAddress = tryBlock.getStartCodeOffset();
421            int endAddress = startAddress + tryBlock.getCodeUnitCount();
422
423            if (startAddress >= codeSize) {
424                throw new RuntimeException(String.format("Try start offset %d is past the end of the code block.",
425                        startAddress));
426            }
427            // Note: not >=. endAddress == codeSize is valid, when the try covers the last instruction
428            if (endAddress > codeSize) {
429                throw new RuntimeException(String.format("Try end offset %d is past the end of the code block.",
430                        endAddress));
431            }
432
433            /**
434             * The end address points to the address immediately after the end of the last
435             * instruction that the try block covers. We want the .catch directive and end_try
436             * label to be associated with the last covered instruction, so we need to get
437             * the address for that instruction
438             */
439
440            int lastCoveredIndex = instructionOffsetMap.getInstructionIndexAtCodeOffset(endAddress - 1, false);
441            int lastCoveredAddress = instructionOffsetMap.getInstructionCodeOffset(lastCoveredIndex);
442
443            for (ExceptionHandler handler: tryBlock.getExceptionHandlers()) {
444                int handlerOffset = handler.getHandlerCodeOffset();
445                if (handlerOffset >= codeSize) {
446                    throw new ExceptionWithContext(
447                            "Exception handler offset %d is past the end of the code block.", handlerOffset);
448                }
449
450                //use the address from the last covered instruction
451                CatchMethodItem catchMethodItem = new CatchMethodItem(labelCache, lastCoveredAddress,
452                        handler.getExceptionType(), startAddress, endAddress, handlerOffset);
453                methodItems.add(catchMethodItem);
454            }
455        }
456    }
457
458    private void addDebugInfo(final List<MethodItem> methodItems) {
459        for (DebugItem debugItem: methodImpl.getDebugItems()) {
460            methodItems.add(DebugMethodItem.build(registerFormatter, debugItem));
461        }
462    }
463
464    private void setLabelSequentialNumbers() {
465        HashMap<String, Integer> nextLabelSequenceByType = new HashMap<String, Integer>();
466        ArrayList<LabelMethodItem> sortedLabels = new ArrayList<LabelMethodItem>(labelCache.getLabels());
467
468        //sort the labels by their location in the method
469        Collections.sort(sortedLabels);
470
471        for (LabelMethodItem labelMethodItem: sortedLabels) {
472            Integer labelSequence = nextLabelSequenceByType.get(labelMethodItem.getLabelPrefix());
473            if (labelSequence == null) {
474                labelSequence = 0;
475            }
476            labelMethodItem.setLabelSequence(labelSequence);
477            nextLabelSequenceByType.put(labelMethodItem.getLabelPrefix(), labelSequence + 1);
478        }
479    }
480
481    public static class LabelCache {
482        protected HashMap<LabelMethodItem, LabelMethodItem> labels = new HashMap<LabelMethodItem, LabelMethodItem>();
483
484        public LabelCache() {
485        }
486
487        public LabelMethodItem internLabel(LabelMethodItem labelMethodItem) {
488            LabelMethodItem internedLabelMethodItem = labels.get(labelMethodItem);
489            if (internedLabelMethodItem != null) {
490                return internedLabelMethodItem;
491            }
492            labels.put(labelMethodItem, labelMethodItem);
493            return labelMethodItem;
494        }
495
496
497        public Collection<LabelMethodItem> getLabels() {
498            return labels.values();
499        }
500    }
501}
502