MethodDefinition.java revision 754b3c4dc009b7a02e39001560c3f0fd6a7cc2c0
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(".parameter p");
214                writer.printSignedIntAsDec(registerNumber);
215                if (parameterName != null) {
216                    writer.write(" ");
217                    writer.write(parameterName);
218                }
219                if (annotations.size() > 0) {
220                    writer.indent(4);
221                    AnnotationFormatter.writeTo(writer, annotations);
222                    writer.deindent(4);
223                    writer.write(".end parameter\n");
224                } else {
225                    writer.write("\n");
226                }
227            }
228
229            registerNumber++;
230            if (TypeUtils.isWideType(parameterType)) {
231                registerNumber++;
232            }
233        }
234    }
235
236    @Nonnull public LabelCache getLabelCache() {
237        return labelCache;
238    }
239
240    public int getPackedSwitchBaseAddress(int packedSwitchPayloadCodeOffset) {
241        return packedSwitchMap.get(packedSwitchPayloadCodeOffset, -1);
242    }
243
244    public int getSparseSwitchBaseAddress(int sparseSwitchPayloadCodeOffset) {
245        return sparseSwitchMap.get(sparseSwitchPayloadCodeOffset, -1);
246    }
247
248    private List<MethodItem> getMethodItems() {
249        ArrayList<MethodItem> methodItems = new ArrayList<MethodItem>();
250
251        //TODO: addAnalyzedInstructionMethodItems
252        addInstructionMethodItems(methodItems);
253
254        addTries(methodItems);
255        if (baksmali.outputDebugInfo) {
256            addDebugInfo(methodItems);
257        }
258
259        if (baksmali.useSequentialLabels) {
260            setLabelSequentialNumbers();
261        }
262
263        for (LabelMethodItem labelMethodItem: labelCache.getLabels()) {
264            methodItems.add(labelMethodItem);
265        }
266
267        Collections.sort(methodItems);
268
269        return methodItems;
270    }
271
272    private void addInstructionMethodItems(List<MethodItem> methodItems) {
273        List<? extends Instruction> instructions = methodImpl.getInstructions();
274
275        int currentCodeAddress = 0;
276        for (int i=0; i<instructions.size(); i++) {
277            Instruction instruction = instructions.get(i);
278
279            MethodItem methodItem = InstructionMethodItemFactory.makeInstructionFormatMethodItem(this,
280                    currentCodeAddress, instruction);
281
282            methodItems.add(methodItem);
283
284            if (i != instructions.size() - 1) {
285                methodItems.add(new BlankMethodItem(currentCodeAddress));
286            }
287
288            if (baksmali.addCodeOffsets) {
289                methodItems.add(new MethodItem(currentCodeAddress) {
290
291                    @Override
292                    public double getSortOrder() {
293                        return -1000;
294                    }
295
296                    @Override
297                    public boolean writeTo(IndentingWriter writer) throws IOException {
298                        writer.write("#@");
299                        writer.printUnsignedLongAsHex(codeAddress & 0xFFFFFFFFL);
300                        return true;
301                    }
302                });
303            }
304
305            //TODO: uncomment
306            /*if (!baksmali.noAccessorComments && (instruction instanceof InstructionWithReference)) {
307                Opcode opcode = instruction.getOpcode();
308                if (opcode == Opcode.INVOKE_STATIC || opcode == Opcode.INVOKE_STATIC_RANGE) {
309                    MethodIdItem methodIdItem =
310                            (MethodIdItem)((InstructionWithReference) instruction).getReferencedItem();
311
312                    if (SyntheticAccessorResolver.looksLikeSyntheticAccessor(methodIdItem)) {
313                        SyntheticAccessorResolver.AccessedMember accessedMember =
314                                baksmali.syntheticAccessorResolver.getAccessedMember(methodIdItem);
315                        if (accessedMember != null) {
316                            methodItems.add(new SyntheticAccessCommentMethodItem(accessedMember, currentCodeAddress));
317                        }
318                    }
319                }
320            }*/
321
322            currentCodeAddress += instruction.getCodeUnits();
323        }
324    }
325
326    //TODO: uncomment
327    /*private void addAnalyzedInstructionMethodItems(List<MethodItem> methodItems) {
328        methodAnalyzer = new MethodAnalyzer(encodedMethod, baksmali.deodex, baksmali.inlineResolver);
329
330        methodAnalyzer.analyze();
331
332        ValidationException validationException = methodAnalyzer.getValidationException();
333        if (validationException != null) {
334            methodItems.add(new CommentMethodItem(
335                    String.format("ValidationException: %s" ,validationException.getMessage()),
336                    validationException.getCodeAddress(), Integer.MIN_VALUE));
337        } else if (baksmali.verify) {
338            methodAnalyzer.verify();
339
340            validationException = methodAnalyzer.getValidationException();
341            if (validationException != null) {
342                methodItems.add(new CommentMethodItem(
343                        String.format("ValidationException: %s" ,validationException.getMessage()),
344                        validationException.getCodeAddress(), Integer.MIN_VALUE));
345            }
346        }
347
348        List<AnalyzedInstruction> instructions = methodAnalyzer.getInstructions();
349
350        int currentCodeAddress = 0;
351        for (int i=0; i<instructions.size(); i++) {
352            AnalyzedInstruction instruction = instructions.get(i);
353
354            MethodItem methodItem = InstructionMethodItemFactory.makeInstructionFormatMethodItem(this,
355                    encodedMethod.codeItem, currentCodeAddress, instruction.getInstruction());
356
357            methodItems.add(methodItem);
358
359            if (instruction.getInstruction().getFormat() == Format.UnresolvedOdexInstruction) {
360                methodItems.add(new CommentedOutMethodItem(
361                        InstructionMethodItemFactory.makeInstructionFormatMethodItem(this,
362                                encodedMethod.codeItem, currentCodeAddress, instruction.getOriginalInstruction())));
363            }
364
365            if (i != instructions.size() - 1) {
366                methodItems.add(new BlankMethodItem(currentCodeAddress));
367            }
368
369            if (baksmali.addCodeOffsets) {
370                methodItems.add(new MethodItem(currentCodeAddress) {
371
372                    @Override
373                    public double getSortOrder() {
374                        return -1000;
375                    }
376
377                    @Override
378                    public boolean writeTo(IndentingWriter writer) throws IOException {
379                        writer.write("#@");
380                        writer.printUnsignedLongAsHex(codeAddress & 0xFFFFFFFF);
381                        return true;
382                    }
383                });
384            }
385
386            if (baksmali.registerInfo != 0 && !instruction.getInstruction().getFormat().variableSizeFormat) {
387                methodItems.add(
388                        new PreInstructionRegisterInfoMethodItem(instruction, methodAnalyzer, currentCodeAddress));
389
390                methodItems.add(
391                        new PostInstructionRegisterInfoMethodItem(instruction, methodAnalyzer, currentCodeAddress));
392            }
393
394            currentCodeAddress += instruction.getInstruction().getSize(currentCodeAddress);
395        }
396    }*/
397
398    private void addTries(List<MethodItem> methodItems) {
399        List<? extends TryBlock> tryBlocks = methodImpl.getTryBlocks();
400        if (tryBlocks.size() == 0) {
401            return;
402        }
403
404        List<? extends Instruction> instructions = methodImpl.getInstructions();
405        int lastInstructionAddress = instructionOffsetMap.getInstructionCodeOffset(instructions.size() - 1);
406        int codeSize = lastInstructionAddress + instructions.get(instructions.size() - 1).getCodeUnits();
407
408        for (TryBlock tryBlock: tryBlocks) {
409            int startAddress = tryBlock.getStartCodeOffset();
410            int endAddress = startAddress + tryBlock.getCodeUnitCount();
411
412            if (startAddress >= codeSize) {
413                throw new RuntimeException(String.format("Try start offset %d is past the end of the code block.",
414                        startAddress));
415            }
416            // Note: not >=. endAddress == codeSize is valid, when the try covers the last instruction
417            if (endAddress > codeSize) {
418                throw new RuntimeException(String.format("Try end offset %d is past the end of the code block.",
419                        endAddress));
420            }
421
422            /**
423             * The end address points to the address immediately after the end of the last
424             * instruction that the try block covers. We want the .catch directive and end_try
425             * label to be associated with the last covered instruction, so we need to get
426             * the address for that instruction
427             */
428
429            int lastCoveredIndex = instructionOffsetMap.getInstructionIndexAtCodeOffset(endAddress - 1, false);
430            int lastCoveredAddress = instructionOffsetMap.getInstructionCodeOffset(lastCoveredIndex);
431
432            for (ExceptionHandler handler: tryBlock.getExceptionHandlers()) {
433                int handlerOffset = handler.getHandlerCodeOffset();
434                if (handlerOffset >= codeSize) {
435                    throw new ExceptionWithContext(
436                            "Exception handler offset %d is past the end of the code block.", handlerOffset);
437                }
438
439                //use the address from the last covered instruction
440                CatchMethodItem catchMethodItem = new CatchMethodItem(labelCache, lastCoveredAddress,
441                        handler.getExceptionType(), startAddress, endAddress, handlerOffset);
442                methodItems.add(catchMethodItem);
443            }
444        }
445    }
446
447    private void addDebugInfo(final List<MethodItem> methodItems) {
448        for (DebugItem debugItem: methodImpl.getDebugItems()) {
449            methodItems.add(DebugMethodItem.build(registerFormatter, debugItem));
450        }
451    }
452
453    private void setLabelSequentialNumbers() {
454        HashMap<String, Integer> nextLabelSequenceByType = new HashMap<String, Integer>();
455        ArrayList<LabelMethodItem> sortedLabels = new ArrayList<LabelMethodItem>(labelCache.getLabels());
456
457        //sort the labels by their location in the method
458        Collections.sort(sortedLabels);
459
460        for (LabelMethodItem labelMethodItem: sortedLabels) {
461            Integer labelSequence = nextLabelSequenceByType.get(labelMethodItem.getLabelPrefix());
462            if (labelSequence == null) {
463                labelSequence = 0;
464            }
465            labelMethodItem.setLabelSequence(labelSequence);
466            nextLabelSequenceByType.put(labelMethodItem.getLabelPrefix(), labelSequence + 1);
467        }
468    }
469
470    public static class LabelCache {
471        protected HashMap<LabelMethodItem, LabelMethodItem> labels = new HashMap<LabelMethodItem, LabelMethodItem>();
472
473        public LabelCache() {
474        }
475
476        public LabelMethodItem internLabel(LabelMethodItem labelMethodItem) {
477            LabelMethodItem internedLabelMethodItem = labels.get(labelMethodItem);
478            if (internedLabelMethodItem != null) {
479                return internedLabelMethodItem;
480            }
481            labels.put(labelMethodItem, labelMethodItem);
482            return labelMethodItem;
483        }
484
485
486        public Collection<LabelMethodItem> getLabels() {
487            return labels.values();
488        }
489    }
490}
491