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