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