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