MethodDefinition.java revision 4b72225e9d81201838f387171a68a832486903f9
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.Format.InstructionMethodItemFactory;
32import org.jf.util.IndentingWriter;
33import org.jf.baksmali.baksmali;
34import org.jf.dexlib.*;
35import org.jf.dexlib.Code.Analysis.AnalyzedInstruction;
36import org.jf.dexlib.Code.Analysis.MethodAnalyzer;
37import org.jf.dexlib.Code.Analysis.ValidationException;
38import org.jf.dexlib.Code.Format.Format;
39import org.jf.dexlib.Code.Instruction;
40import org.jf.dexlib.Code.OffsetInstruction;
41import org.jf.dexlib.Code.Opcode;
42import org.jf.dexlib.Debug.DebugInstructionIterator;
43import org.jf.dexlib.Util.AccessFlags;
44import org.jf.dexlib.Util.ExceptionWithContext;
45import org.jf.dexlib.Util.SparseIntArray;
46
47import java.io.IOException;
48import java.util.*;
49
50public class MethodDefinition {
51    private final ClassDataItem.EncodedMethod encodedMethod;
52    private MethodAnalyzer methodAnalyzer;
53
54    private final LabelCache labelCache = new LabelCache();
55
56    private final SparseIntArray packedSwitchMap;
57    private final SparseIntArray sparseSwitchMap;
58    private final SparseIntArray instructionMap;
59
60    public MethodDefinition(ClassDataItem.EncodedMethod encodedMethod) {
61
62
63        try {
64            this.encodedMethod = encodedMethod;
65
66            //TODO: what about try/catch blocks inside the dead code? those will need to be commented out too. ugh.
67
68            if (encodedMethod.codeItem != null) {
69                Instruction[] instructions = encodedMethod.codeItem.getInstructions();
70
71                packedSwitchMap = new SparseIntArray(1);
72                sparseSwitchMap = new SparseIntArray(1);
73                instructionMap = new SparseIntArray(instructions.length);
74
75                int currentCodeAddress = 0;
76                for (int i=0; i<instructions.length; i++) {
77                    Instruction instruction = instructions[i];
78                    if (instruction.opcode == Opcode.PACKED_SWITCH) {
79                        packedSwitchMap.append(
80                                currentCodeAddress +
81                                        ((OffsetInstruction)instruction).getTargetAddressOffset(),
82                                currentCodeAddress);
83                    } else if (instruction.opcode == Opcode.SPARSE_SWITCH) {
84                        sparseSwitchMap.append(
85                                currentCodeAddress +
86                                        ((OffsetInstruction)instruction).getTargetAddressOffset(),
87                                currentCodeAddress);
88                    }
89                    instructionMap.append(currentCodeAddress, i);
90                    currentCodeAddress += instruction.getSize(currentCodeAddress);
91                }
92            } else {
93                packedSwitchMap = null;
94                sparseSwitchMap = null;
95                instructionMap = null;
96                methodAnalyzer = null;
97            }
98        }catch (Exception ex) {
99            throw ExceptionWithContext.withContext(ex, String.format("Error while processing method %s",
100                    encodedMethod.method.getMethodString()));
101        }
102    }
103
104    public void writeTo(IndentingWriter writer, AnnotationSetItem annotationSet,
105                        AnnotationSetRefList parameterAnnotations) throws IOException {
106        final CodeItem codeItem = encodedMethod.codeItem;
107
108        writer.write(".method ");
109        writeAccessFlags(writer, encodedMethod);
110        writer.write(encodedMethod.method.getMethodName().getStringValue());
111        writer.write(encodedMethod.method.getPrototype().getPrototypeString());
112        writer.write('\n');
113
114        writer.indent(4);
115        if (codeItem != null) {
116            if (baksmali.useLocalsDirective) {
117                writer.write(".locals ");
118            } else {
119                writer.write(".registers ");
120            }
121            writer.printIntAsDec(getRegisterCount(encodedMethod));
122            writer.write('\n');
123            writeParameters(writer, codeItem, parameterAnnotations);
124            if (annotationSet != null) {
125                AnnotationFormatter.writeTo(writer, annotationSet);
126            }
127
128            writer.write('\n');
129
130            for (MethodItem methodItem: getMethodItems()) {
131                if (methodItem.writeTo(writer)) {
132                    writer.write('\n');
133                }
134            }
135        } else {
136            if (annotationSet != null) {
137                AnnotationFormatter.writeTo(writer, annotationSet);
138            }
139        }
140        writer.deindent(4);
141        writer.write(".end method\n");
142    }
143
144    private static int getRegisterCount(ClassDataItem.EncodedMethod encodedMethod)
145    {
146        int totalRegisters = encodedMethod.codeItem.getRegisterCount();
147        if (baksmali.useLocalsDirective) {
148            int parameterRegisters = encodedMethod.method.getPrototype().getParameterRegisterCount();
149            if ((encodedMethod.accessFlags & AccessFlags.STATIC.getValue()) == 0) {
150                parameterRegisters++;
151            }
152            return totalRegisters - parameterRegisters;
153        }
154        return totalRegisters;
155    }
156
157    private static void writeAccessFlags(IndentingWriter writer, ClassDataItem.EncodedMethod encodedMethod)
158                                                                                            throws IOException {
159        for (AccessFlags accessFlag: AccessFlags.getAccessFlagsForMethod(encodedMethod.accessFlags)) {
160            writer.write(accessFlag.toString());
161            writer.write(' ');
162        }
163    }
164
165    private static void writeParameters(IndentingWriter writer, CodeItem codeItem,
166                                        AnnotationSetRefList parameterAnnotations) throws IOException {
167        DebugInfoItem debugInfoItem = null;
168        if (baksmali.outputDebugInfo && codeItem != null) {
169            debugInfoItem = codeItem.getDebugInfo();
170        }
171
172        int parameterCount = 0;
173        AnnotationSetItem[] annotations;
174        StringIdItem[] parameterNames = null;
175
176        if (parameterAnnotations != null) {
177            annotations = parameterAnnotations.getAnnotationSets();
178            parameterCount = annotations.length;
179        } else {
180            annotations = new AnnotationSetItem[0];
181        }
182
183        if (debugInfoItem != null) {
184            parameterNames = debugInfoItem.getParameterNames();
185        }
186        if (parameterNames == null) {
187            parameterNames = new StringIdItem[0];
188        }
189
190        if (parameterCount < parameterNames.length) {
191            parameterCount = parameterNames.length;
192        }
193
194        for (int i=0; i<parameterCount; i++) {
195            AnnotationSetItem annotationSet = null;
196            if (i < annotations.length) {
197                annotationSet = annotations[i];
198            }
199
200            StringIdItem parameterName = null;
201            if (i < parameterNames.length) {
202                parameterName = parameterNames[i];
203            }
204
205            writer.write(".parameter");
206
207            if (parameterName != null) {
208                writer.write(" \"");
209                writer.write(parameterName.getStringValue());
210                writer.write('"');
211            }
212
213            writer.write('\n');
214            if (annotationSet != null) {
215                writer.indent(4);
216                AnnotationFormatter.writeTo(writer, annotationSet);
217                writer.deindent(4);
218
219                writer.write(".end parameter\n");
220            }
221        }
222    }
223
224    public LabelCache getLabelCache() {
225        return labelCache;
226    }
227
228    public ValidationException getValidationException() {
229        if (methodAnalyzer == null) {
230            return null;
231        }
232
233        return methodAnalyzer.getValidationException();
234    }
235
236    public int getPackedSwitchBaseAddress(int packedSwitchDataAddress) {
237        int packedSwitchBaseAddress = this.packedSwitchMap.get(packedSwitchDataAddress, -1);
238
239        if (packedSwitchBaseAddress == -1) {
240            throw new RuntimeException("Could not find the packed switch statement corresponding to the packed " +
241                    "switch data at address " + packedSwitchDataAddress);
242        }
243
244        return packedSwitchBaseAddress;
245    }
246
247    public int getSparseSwitchBaseAddress(int sparseSwitchDataAddress) {
248        int sparseSwitchBaseAddress = this.sparseSwitchMap.get(sparseSwitchDataAddress, -1);
249
250        if (sparseSwitchBaseAddress == -1) {
251            throw new RuntimeException("Could not find the sparse switch statement corresponding to the sparse " +
252                    "switch data at address " + sparseSwitchDataAddress);
253        }
254
255        return sparseSwitchBaseAddress;
256    }
257
258    /**
259     * @param instructions The instructions array for this method
260     * @param instruction The instruction
261     * @return true if the specified instruction is a NOP, and the next instruction is one of the variable sized
262     * switch/array data structures
263     */
264    private boolean isInstructionPaddingNop(List<AnalyzedInstruction> instructions, AnalyzedInstruction instruction) {
265        if (instruction.getInstruction().opcode != Opcode.NOP ||
266            instruction.getInstruction().getFormat().variableSizeFormat) {
267
268            return false;
269        }
270
271        if (instruction.getInstructionIndex() == instructions.size()-1) {
272            return false;
273        }
274
275        AnalyzedInstruction nextInstruction = instructions.get(instruction.getInstructionIndex()+1);
276        if (nextInstruction.getInstruction().getFormat().variableSizeFormat) {
277            return true;
278        }
279        return false;
280    }
281
282    private List<MethodItem> getMethodItems() {
283        ArrayList<MethodItem> methodItems = new ArrayList<MethodItem>();
284
285        if (encodedMethod.codeItem == null) {
286            return methodItems;
287        }
288
289        if (baksmali.registerInfo != 0 || baksmali.deodex || baksmali.verify) {
290            addAnalyzedInstructionMethodItems(methodItems);
291        } else {
292            addInstructionMethodItems(methodItems);
293        }
294
295        addTries(methodItems);
296        if (baksmali.outputDebugInfo) {
297            addDebugInfo(methodItems);
298        }
299
300        if (baksmali.useSequentialLabels) {
301            setLabelSequentialNumbers();
302        }
303
304        for (LabelMethodItem labelMethodItem: labelCache.getLabels()) {
305            methodItems.add(labelMethodItem);
306        }
307
308        Collections.sort(methodItems);
309
310        return methodItems;
311    }
312
313    private void addInstructionMethodItems(List<MethodItem> methodItems) {
314        Instruction[] instructions = encodedMethod.codeItem.getInstructions();
315
316        int currentCodeAddress = 0;
317        for (int i=0; i<instructions.length; i++) {
318            Instruction instruction = instructions[i];
319
320            MethodItem methodItem = InstructionMethodItemFactory.makeInstructionFormatMethodItem(this,
321                    encodedMethod.codeItem, currentCodeAddress, instruction);
322
323            methodItems.add(methodItem);
324
325            if (i != instructions.length - 1) {
326                methodItems.add(new BlankMethodItem(currentCodeAddress));
327            }
328
329            if (baksmali.addCodeOffsets) {
330                methodItems.add(new MethodItem(currentCodeAddress) {
331
332                    @Override
333                    public double getSortOrder() {
334                        return -1000;
335                    }
336
337                    @Override
338                    public boolean writeTo(IndentingWriter writer) throws IOException {
339                        writer.write("#@");
340                        writer.printLongAsHex(codeAddress & 0xFFFFFFFF);
341                        return true;
342                    }
343                });
344            }
345
346            currentCodeAddress += instruction.getSize(currentCodeAddress);
347        }
348    }
349
350    private void addAnalyzedInstructionMethodItems(List<MethodItem> methodItems) {
351        methodAnalyzer = new MethodAnalyzer(encodedMethod, baksmali.deodex);
352
353        methodAnalyzer.analyze();
354
355        ValidationException validationException = methodAnalyzer.getValidationException();
356        if (validationException != null) {
357            methodItems.add(new CommentMethodItem(
358                    String.format("ValidationException: %s" ,validationException.getMessage()),
359                    validationException.getCodeAddress(), Integer.MIN_VALUE));
360        } else if (baksmali.verify) {
361            methodAnalyzer.verify();
362
363            validationException = methodAnalyzer.getValidationException();
364            if (validationException != null) {
365                methodItems.add(new CommentMethodItem(
366                        String.format("ValidationException: %s" ,validationException.getMessage()),
367                        validationException.getCodeAddress(), Integer.MIN_VALUE));
368            }
369        }
370
371        List<AnalyzedInstruction> instructions = methodAnalyzer.getInstructions();
372
373        int currentCodeAddress = 0;
374        for (int i=0; i<instructions.size(); i++) {
375            AnalyzedInstruction instruction = instructions.get(i);
376
377            MethodItem methodItem = InstructionMethodItemFactory.makeInstructionFormatMethodItem(this,
378                    encodedMethod.codeItem, currentCodeAddress, instruction.getInstruction());
379
380            methodItems.add(methodItem);
381
382            if (instruction.getInstruction().getFormat() == Format.UnresolvedOdexInstruction) {
383                methodItems.add(new CommentedOutMethodItem(
384                        InstructionMethodItemFactory.makeInstructionFormatMethodItem(this,
385                                encodedMethod.codeItem, currentCodeAddress, instruction.getOriginalInstruction())));
386            }
387
388            if (i != instructions.size() - 1) {
389                methodItems.add(new BlankMethodItem(currentCodeAddress));
390            }
391
392            if (baksmali.addCodeOffsets) {
393                methodItems.add(new MethodItem(currentCodeAddress) {
394
395                    @Override
396                    public double getSortOrder() {
397                        return -1000;
398                    }
399
400                    @Override
401                    public boolean writeTo(IndentingWriter writer) throws IOException {
402                        writer.write("#@");
403                        writer.printLongAsHex(codeAddress & 0xFFFFFFFF);
404                        return true;
405                    }
406                });
407            }
408
409            if (baksmali.registerInfo != 0 && !instruction.getInstruction().getFormat().variableSizeFormat) {
410                methodItems.add(
411                        new PreInstructionRegisterInfoMethodItem(instruction, methodAnalyzer, currentCodeAddress));
412
413                methodItems.add(
414                        new PostInstructionRegisterInfoMethodItem(instruction, methodAnalyzer, currentCodeAddress));
415            }
416
417            currentCodeAddress += instruction.getInstruction().getSize(currentCodeAddress);
418        }
419    }
420
421    private void addTries(List<MethodItem> methodItems) {
422        if (encodedMethod.codeItem == null || encodedMethod.codeItem.getTries() == null) {
423            return;
424        }
425
426        Instruction[] instructions = encodedMethod.codeItem.getInstructions();
427
428        for (CodeItem.TryItem tryItem: encodedMethod.codeItem.getTries()) {
429            int startAddress = tryItem.getStartCodeAddress();
430            int endAddress = tryItem.getStartCodeAddress() + tryItem.getTryLength();
431
432            /**
433             * The end address points to the address immediately after the end of the last
434             * instruction that the try block covers. We want the .catch directive and end_try
435             * label to be associated with the last covered instruction, so we need to get
436             * the address for that instruction
437             */
438
439            int index = instructionMap.get(endAddress, -1);
440            int lastInstructionAddress;
441
442            /**
443             * If we couldn't find the index, then the try block probably extends to the last instruction in the
444             * method, and so endAddress would be the address immediately after the end of the last instruction.
445             * Check to make sure this is the case, if not, throw an exception.
446             */
447            if (index == -1) {
448                Instruction lastInstruction = instructions[instructions.length - 1];
449                lastInstructionAddress = instructionMap.keyAt(instructionMap.size() - 1);
450
451                if (endAddress != lastInstructionAddress + lastInstruction.getSize(lastInstructionAddress)) {
452                    throw new RuntimeException("Invalid code offset " + endAddress + " for the try block end address");
453                }
454            } else {
455                if (index == 0) {
456                    throw new RuntimeException("Unexpected instruction index");
457                }
458                Instruction lastInstruction = instructions[index - 1];
459
460                if (lastInstruction.getFormat().variableSizeFormat) {
461                    throw new RuntimeException("This try block unexpectedly ends on a switch/array data block.");
462                }
463
464                //getSize for non-variable size formats should return the same size regardless of code address, so just
465                //use a dummy address of "0"
466                lastInstructionAddress = endAddress - lastInstruction.getSize(0);
467            }
468
469            //add the catch all handler if it exists
470            int catchAllAddress = tryItem.encodedCatchHandler.getCatchAllHandlerAddress();
471            if (catchAllAddress != -1) {
472                CatchMethodItem catchAllMethodItem = new CatchMethodItem(labelCache, lastInstructionAddress, null,
473                        startAddress, endAddress, catchAllAddress);
474                methodItems.add(catchAllMethodItem);
475            }
476
477            //add the rest of the handlers
478            for (CodeItem.EncodedTypeAddrPair handler: tryItem.encodedCatchHandler.handlers) {
479                //use the address from the last covered instruction
480                CatchMethodItem catchMethodItem = new CatchMethodItem(labelCache, lastInstructionAddress,
481                        handler.exceptionType, startAddress, endAddress, handler.getHandlerAddress());
482                methodItems.add(catchMethodItem);
483            }
484        }
485    }
486
487    private void addDebugInfo(final List<MethodItem> methodItems) {
488        if (encodedMethod.codeItem == null || encodedMethod.codeItem.getDebugInfo() == null) {
489            return;
490        }
491
492        final CodeItem codeItem = encodedMethod.codeItem;
493        DebugInfoItem debugInfoItem = codeItem.getDebugInfo();
494
495        DebugInstructionIterator.DecodeInstructions(debugInfoItem, codeItem.getRegisterCount(),
496                new DebugInstructionIterator.ProcessDecodedDebugInstructionDelegate() {
497                    @Override
498                    public void ProcessStartLocal(final int codeAddress, final int length, final int registerNum,
499                                                  final StringIdItem name, final TypeIdItem type) {
500                        methodItems.add(new DebugMethodItem(codeAddress, -1) {
501                            @Override
502                            public boolean writeTo(IndentingWriter writer) throws IOException {
503                                writeStartLocal(writer, codeItem, registerNum, name, type, null);
504                                return true;
505                            }
506                        });
507                    }
508
509                    @Override
510                    public void ProcessStartLocalExtended(final int codeAddress, final int length,
511                                                          final int registerNum, final StringIdItem name,
512                                                          final TypeIdItem type, final StringIdItem signature) {
513                        methodItems.add(new DebugMethodItem(codeAddress, -1) {
514                            @Override
515                            public boolean writeTo(IndentingWriter writer) throws IOException {
516                                writeStartLocal(writer, codeItem, registerNum, name, type, signature);
517                                return true;
518                            }
519                        });
520                    }
521
522                    @Override
523                    public void ProcessEndLocal(final int codeAddress, final int length, final int registerNum,
524                                                final StringIdItem name, final TypeIdItem type,
525                                                final StringIdItem signature) {
526                        methodItems.add(new DebugMethodItem(codeAddress, -1) {
527                            @Override
528                            public boolean writeTo(IndentingWriter writer) throws IOException {
529                                writeEndLocal(writer, codeItem, registerNum, name, type, signature);
530                                return true;
531                            }
532                        });
533                    }
534
535                    @Override
536                    public void ProcessRestartLocal(final int codeAddress, final int length, final int registerNum,
537                                                final StringIdItem name, final TypeIdItem type,
538                                                final StringIdItem signature) {
539                        methodItems.add(new DebugMethodItem(codeAddress, -1) {
540                            @Override
541                            public boolean writeTo(IndentingWriter writer) throws IOException {
542                                writeRestartLocal(writer, codeItem, registerNum, name, type, signature);
543                                return true;
544                            }
545                        });
546                    }
547
548                    @Override
549                    public void ProcessSetPrologueEnd(int codeAddress) {
550                        methodItems.add(new DebugMethodItem(codeAddress, -4) {
551                            @Override
552                            public boolean writeTo(IndentingWriter writer) throws IOException {
553                                writeEndPrologue(writer);
554                                return true;
555                            }
556                        });
557                    }
558
559                    @Override
560                    public void ProcessSetEpilogueBegin(int codeAddress) {
561                        methodItems.add(new DebugMethodItem(codeAddress, -4) {
562                            @Override
563                            public boolean writeTo(IndentingWriter writer) throws IOException {
564                                writeBeginEpilogue(writer);
565                                return true;
566                            }
567                        });
568                    }
569
570                    @Override
571                    public void ProcessSetFile(int codeAddress, int length, final StringIdItem name) {
572                        methodItems.add(new DebugMethodItem(codeAddress, -3) {
573                            @Override
574                            public boolean writeTo(IndentingWriter writer) throws IOException {
575                                writeSetFile(writer, name.getStringValue());
576                                return true;
577                            }
578                        });
579                    }
580
581                    @Override
582                    public void ProcessLineEmit(int codeAddress, final int line) {
583                        methodItems.add(new DebugMethodItem(codeAddress, -2) {
584                            @Override
585                            public boolean writeTo(IndentingWriter writer) throws IOException {
586                                writeLine(writer, line);
587                                return true;
588                            }
589                         });
590                    }
591                });
592    }
593
594    private void setLabelSequentialNumbers() {
595        HashMap<String, Integer> nextLabelSequenceByType = new HashMap<String, Integer>();
596        ArrayList<LabelMethodItem> sortedLabels = new ArrayList<LabelMethodItem>(labelCache.getLabels());
597
598        //sort the labels by their location in the method
599        Collections.sort(sortedLabels);
600
601        for (LabelMethodItem labelMethodItem: sortedLabels) {
602            Integer labelSequence = nextLabelSequenceByType.get(labelMethodItem.getLabelPrefix());
603            if (labelSequence == null) {
604                labelSequence = 0;
605            }
606            labelMethodItem.setLabelSequence(labelSequence);
607            nextLabelSequenceByType.put(labelMethodItem.getLabelPrefix(), labelSequence + 1);
608        }
609    }
610
611    public static class LabelCache {
612        protected HashMap<LabelMethodItem, LabelMethodItem> labels = new HashMap<LabelMethodItem, LabelMethodItem>();
613
614        public LabelCache() {
615        }
616
617        public LabelMethodItem internLabel(LabelMethodItem labelMethodItem) {
618            LabelMethodItem internedLabelMethodItem = labels.get(labelMethodItem);
619            if (internedLabelMethodItem != null) {
620                return internedLabelMethodItem;
621            }
622            labels.put(labelMethodItem, labelMethodItem);
623            return labelMethodItem;
624        }
625
626
627        public Collection<LabelMethodItem> getLabels() {
628            return labels.values();
629        }
630    }
631}
632