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