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