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