MethodDefinition.java revision db81d89c2acbd8569f62a941ee2947eb5cd4c5b5
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.ReferenceType;
37import org.jf.dexlib2.iface.*;
38import org.jf.dexlib2.iface.debug.DebugItem;
39import org.jf.dexlib2.iface.instruction.Instruction;
40import org.jf.dexlib2.iface.instruction.OffsetInstruction;
41import org.jf.dexlib2.iface.instruction.ReferenceInstruction;
42import org.jf.dexlib2.iface.reference.MethodReference;
43import org.jf.dexlib2.util.*;
44import org.jf.util.IndentingWriter;
45import org.jf.baksmali.baksmali;
46import org.jf.util.ExceptionWithContext;
47import org.jf.dexlib.Util.SparseIntArray;
48
49import javax.annotation.Nonnull;
50import java.io.IOException;
51import java.util.*;
52
53public class MethodDefinition {
54    @Nonnull public final ClassDefinition classDef;
55    @Nonnull public final Method method;
56    @Nonnull public final MethodImplementation methodImpl;
57    @Nonnull public final ImmutableList<Instruction> instructions;
58    @Nonnull public final ImmutableList<MethodParameter> methodParameters;
59    public RegisterFormatter registerFormatter;
60
61    @Nonnull private final LabelCache labelCache = new LabelCache();
62
63    @Nonnull private final SparseIntArray packedSwitchMap;
64    @Nonnull private final SparseIntArray sparseSwitchMap;
65    @Nonnull private final InstructionOffsetMap instructionOffsetMap;
66
67    public MethodDefinition(@Nonnull ClassDefinition classDef, @Nonnull Method method,
68                            @Nonnull MethodImplementation methodImpl) {
69        this.classDef = classDef;
70        this.method = method;
71        this.methodImpl = methodImpl;
72
73
74        try {
75            //TODO: what about try/catch blocks inside the dead code? those will need to be commented out too. ugh.
76
77            instructions = ImmutableList.copyOf(methodImpl.getInstructions());
78            methodParameters = ImmutableList.copyOf(method.getParameters());
79
80            packedSwitchMap = new SparseIntArray(0);
81            sparseSwitchMap = new SparseIntArray(0);
82            instructionOffsetMap = new InstructionOffsetMap(instructions);
83
84            for (int i=0; i<instructions.size(); i++) {
85                Instruction instruction = instructions.get(i);
86
87                Opcode opcode = instruction.getOpcode();
88                if (opcode == Opcode.PACKED_SWITCH) {
89                    int codeOffset = instructionOffsetMap.getInstructionCodeOffset(i);
90                    int targetOffset = codeOffset + ((OffsetInstruction)instruction).getCodeOffset();
91                    targetOffset = findSwitchPayload(targetOffset, Opcode.PACKED_SWITCH_PAYLOAD);
92                    packedSwitchMap.append(targetOffset, codeOffset);
93                } else if (opcode == Opcode.SPARSE_SWITCH) {
94                    int codeOffset = instructionOffsetMap.getInstructionCodeOffset(i);
95                    int targetOffset = codeOffset + ((OffsetInstruction)instruction).getCodeOffset();
96                    targetOffset = findSwitchPayload(targetOffset, Opcode.SPARSE_SWITCH_PAYLOAD);
97                    sparseSwitchMap.append(targetOffset, codeOffset);
98                }
99            }
100        }catch (Exception ex) {
101            String methodString;
102            try {
103                methodString = ReferenceUtil.getMethodDescriptor(method);
104            } catch (Exception ex2) {
105                throw ExceptionWithContext.withContext(ex, "Error while processing method");
106            }
107            throw ExceptionWithContext.withContext(ex, "Error while processing method %s", methodString);
108        }
109    }
110
111    public static void writeEmptyMethodTo(IndentingWriter writer, Method method) throws IOException {
112        writer.write(".method ");
113        writeAccessFlags(writer, method.getAccessFlags());
114        writer.write(method.getName());
115        writer.write("(");
116        ImmutableList<MethodParameter> methodParameters = ImmutableList.copyOf(method.getParameters());
117        for (MethodParameter parameter: methodParameters) {
118            writer.write(parameter.getType());
119        }
120        writer.write(")");
121        writer.write(method.getReturnType());
122        writer.write('\n');
123
124        writer.indent(4);
125        writeParameters(writer, method, methodParameters);
126        AnnotationFormatter.writeTo(writer, method.getAnnotations());
127        writer.deindent(4);
128        writer.write(".end method\n");
129    }
130
131    public void writeTo(IndentingWriter writer) throws IOException {
132        int parameterRegisterCount = 0;
133        if (!AccessFlags.STATIC.isSet(method.getAccessFlags())) {
134            parameterRegisterCount++;
135        }
136
137        writer.write(".method ");
138        writeAccessFlags(writer, method.getAccessFlags());
139        writer.write(method.getName());
140        writer.write("(");
141        for (MethodParameter parameter: methodParameters) {
142            String type = parameter.getType();
143            writer.write(type);
144            parameterRegisterCount++;
145            if (TypeUtils.isWideType(type)) {
146                parameterRegisterCount++;
147            }
148        }
149        writer.write(")");
150        writer.write(method.getReturnType());
151        writer.write('\n');
152
153        writer.indent(4);
154        if (baksmali.useLocalsDirective) {
155            writer.write(".locals ");
156            writer.printSignedIntAsDec(methodImpl.getRegisterCount() - parameterRegisterCount);
157        } else {
158            writer.write(".registers ");
159            writer.printSignedIntAsDec(methodImpl.getRegisterCount());
160        }
161        writer.write('\n');
162        writeParameters(writer, method, methodParameters);
163
164        if (registerFormatter == null) {
165            registerFormatter = new RegisterFormatter(methodImpl.getRegisterCount(), parameterRegisterCount);
166        }
167
168        AnnotationFormatter.writeTo(writer, method.getAnnotations());
169
170        writer.write('\n');
171
172        List<MethodItem> methodItems = getMethodItems();
173        int size = methodItems.size();
174        for (int i=0; i<size; i++) {
175            MethodItem methodItem = methodItems.get(i);
176            if (methodItem.writeTo(writer)) {
177                writer.write('\n');
178            }
179        }
180        writer.deindent(4);
181        writer.write(".end method\n");
182    }
183
184    private int findSwitchPayload(int targetOffset, Opcode type) {
185        int targetIndex = instructionOffsetMap.getInstructionIndexAtCodeOffset(targetOffset);
186
187        //TODO: does dalvik let you pad with multiple nops?
188        //TODO: does dalvik let a switch instruction point to a non-payload instruction?
189
190        Instruction instruction = instructions.get(targetIndex);
191        if (instruction.getOpcode() != type) {
192            // maybe it's pointing to a NOP padding instruction. Look at the next instruction
193            if (instruction.getOpcode() == Opcode.NOP) {
194                targetIndex += 1;
195                if (targetIndex < instructions.size()) {
196                    instruction = instructions.get(targetIndex);
197                    if (instruction.getOpcode() == type) {
198                        return instructionOffsetMap.getInstructionCodeOffset(targetIndex);
199                    }
200                }
201            }
202            throw new ExceptionWithContext("No switch payload at offset 0x%x", targetOffset);
203        } else {
204            return targetOffset;
205        }
206    }
207
208    private static void writeAccessFlags(IndentingWriter writer, int accessFlags)
209            throws IOException {
210        for (AccessFlags accessFlag: AccessFlags.getAccessFlagsForMethod(accessFlags)) {
211            writer.write(accessFlag.toString());
212            writer.write(' ');
213        }
214    }
215
216    private static void writeParameters(IndentingWriter writer, Method method,
217                                        List<? extends MethodParameter> parameters) throws IOException {
218        boolean isStatic = AccessFlags.STATIC.isSet(method.getAccessFlags());
219        int registerNumber = isStatic?0:1;
220        for (MethodParameter parameter: parameters) {
221            String parameterType = parameter.getType();
222            String parameterName = parameter.getName();
223            Collection<? extends Annotation> annotations = parameter.getAnnotations();
224            if (parameterName != null || annotations.size() != 0) {
225                writer.write(".param p");
226                writer.printSignedIntAsDec(registerNumber);
227                if (parameterName != null) {
228                    writer.write(", ");
229                    // TODO: does dalvik allow non-identifier parameter and/or local names?
230                    writer.write(parameterName);
231                }
232                writer.write("    # ");
233                writer.write(parameterType);
234                writer.write("\n");
235                if (annotations.size() > 0) {
236                    writer.indent(4);
237                    AnnotationFormatter.writeTo(writer, annotations);
238                    writer.deindent(4);
239                    writer.write(".end param\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            if (!baksmali.noAccessorComments && (instruction instanceof ReferenceInstruction)) {
318                Opcode opcode = instruction.getOpcode();
319
320                if (opcode.referenceType == ReferenceType.METHOD) {
321                    MethodReference methodReference =
322                            (MethodReference)((ReferenceInstruction)instruction).getReference();
323
324                    if (SyntheticAccessorResolver.looksLikeSyntheticAccessor(methodReference.getName())) {
325                        SyntheticAccessorResolver.AccessedMember accessedMember =
326                                baksmali.syntheticAccessorResolver.getAccessedMember(methodReference);
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.getStartCodeAddress();
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 handlerAddress = handler.getHandlerCodeAddress();
445                if (handlerAddress >= codeSize) {
446                    throw new ExceptionWithContext(
447                            "Exception handler offset %d is past the end of the code block.", handlerAddress);
448                }
449
450                //use the address from the last covered instruction
451                CatchMethodItem catchMethodItem = new CatchMethodItem(labelCache, lastCoveredAddress,
452                        handler.getExceptionType(), startAddress, endAddress, handlerAddress);
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