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