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