CodeItem.java revision b145895c49a0c06d248b3f10daadb9a61fd0e962
1/*
2 * [The "BSD licence"]
3 * Copyright (c) 2009 Ben Gruver
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.dexlib;
30
31import org.jf.dexlib.Code.*;
32import org.jf.dexlib.Code.Format.Instruction20t;
33import org.jf.dexlib.Code.Format.Instruction30t;
34import org.jf.dexlib.Code.Format.Instruction21c;
35import org.jf.dexlib.Code.Format.Instruction31c;
36import org.jf.dexlib.Util.*;
37import org.jf.dexlib.Debug.DebugInstructionIterator;
38
39import java.util.List;
40import java.util.ArrayList;
41
42public class CodeItem extends Item<CodeItem> {
43    private int registerCount;
44    private int inWords;
45    private int outWords;
46    private DebugInfoItem debugInfo;
47    private Instruction[] instructions;
48    private TryItem[] tries;
49    private EncodedCatchHandler[] encodedCatchHandlers;
50
51    private ClassDataItem.EncodedMethod parent;
52
53    /**
54     * Creates a new uninitialized <code>CodeItem</code>
55     * @param dexFile The <code>DexFile</code> that this item belongs to
56     */
57    public CodeItem(DexFile dexFile) {
58        super(dexFile);
59    }
60
61    /**
62     * Creates a new <code>CodeItem</code> with the given values.
63     * @param dexFile The <code>DexFile</code> that this item belongs to
64     * @param registerCount the number of registers that the method containing this code uses
65     * @param inWords the number of 2-byte words that the parameters to the method containing this code take
66     * @param outWords the maximum number of 2-byte words for the arguments of any method call in this code
67     * @param debugInfo the debug information for this code/method
68     * @param instructions the instructions for this code item
69     * @param tries an array of the tries defined for this code/method
70     * @param encodedCatchHandlers an array of the exception handlers defined for this code/method
71     */
72    private CodeItem(DexFile dexFile,
73                    int registerCount,
74                    int inWords,
75                    int outWords,
76                    DebugInfoItem debugInfo,
77                    Instruction[] instructions,
78                    TryItem[] tries,
79                    EncodedCatchHandler[] encodedCatchHandlers) {
80        super(dexFile);
81
82        this.registerCount = registerCount;
83        this.inWords = inWords;
84        this.outWords = outWords;
85        this.debugInfo = debugInfo;
86        if (debugInfo != null) {
87            debugInfo.setParent(this);
88        }
89
90        this.instructions = instructions;
91        this.tries = tries;
92        this.encodedCatchHandlers = encodedCatchHandlers;
93    }
94
95    /**
96     * Returns a new <code>CodeItem</code> with the given values.
97     * @param dexFile The <code>DexFile</code> that this item belongs to
98     * @param registerCount the number of registers that the method containing this code uses
99     * @param inWords the number of 2-byte words that the parameters to the method containing this code take
100     * @param outWords the maximum number of 2-byte words for the arguments of any method call in this code
101     * @param debugInfo the debug information for this code/method
102     * @param instructions the instructions for this code item
103     * @param tries a list of the tries defined for this code/method or null if none
104     * @param encodedCatchHandlers a list of the exception handlers defined for this code/method or null if none
105     * @return a new <code>CodeItem</code> with the given values.
106     */
107    public static CodeItem getInternedCodeItem(DexFile dexFile,
108                    int registerCount,
109                    int inWords,
110                    int outWords,
111                    DebugInfoItem debugInfo,
112                    List<Instruction> instructions,
113                    List<TryItem> tries,
114                    List<EncodedCatchHandler> encodedCatchHandlers) {
115        TryItem[] triesArray = null;
116        EncodedCatchHandler[] encodedCatchHandlersArray = null;
117        Instruction[] instructionsArray = null;
118
119        if (tries != null && tries.size() > 0) {
120            triesArray = new TryItem[tries.size()];
121            tries.toArray(triesArray);
122        }
123
124        if (encodedCatchHandlers != null && encodedCatchHandlers.size() > 0) {
125            encodedCatchHandlersArray = new EncodedCatchHandler[encodedCatchHandlers.size()];
126            encodedCatchHandlers.toArray(encodedCatchHandlersArray);
127        }
128
129        if (instructions != null && instructions.size() > 0) {
130            instructionsArray = new Instruction[instructions.size()];
131            instructions.toArray(instructionsArray);
132        }
133
134        CodeItem codeItem = new CodeItem(dexFile, registerCount, inWords, outWords, debugInfo, instructionsArray,
135                triesArray, encodedCatchHandlersArray);
136        return dexFile.CodeItemsSection.intern(codeItem);
137    }
138
139    /** {@inheritDoc} */
140    protected void readItem(Input in, ReadContext readContext) {
141        this.registerCount = in.readShort();
142        this.inWords = in.readShort();
143        this.outWords = in.readShort();
144        int triesCount = in.readShort();
145        this.debugInfo = (DebugInfoItem)readContext.getOffsettedItemByOffset(ItemType.TYPE_DEBUG_INFO_ITEM,
146                in.readInt());
147        if (this.debugInfo != null) {
148            this.debugInfo.setParent(this);
149        }
150        int instructionCount = in.readInt();
151
152        final ArrayList<Instruction> instructionList = new ArrayList<Instruction>();
153
154        byte[] encodedInstructions = in.readBytes(instructionCount * 2);
155        InstructionIterator.IterateInstructions(dexFile, encodedInstructions,
156                new InstructionIterator.ProcessInstructionDelegate() {
157                    public void ProcessInstruction(int index, Instruction instruction) {
158                        instructionList.add(instruction);
159                    }
160                });
161
162        this.instructions = new Instruction[instructionList.size()];
163        instructionList.toArray(instructions);
164
165        if (triesCount > 0) {
166            in.alignTo(4);
167
168            //we need to read in the catch handlers first, so save the offset to the try items for future reference
169            int triesOffset = in.getCursor();
170            in.setCursor(triesOffset + 8 * triesCount);
171
172            //read in the encoded catch handlers
173            int encodedHandlerStart = in.getCursor();
174            int handlerCount = in.readUnsignedLeb128();
175            SparseArray<EncodedCatchHandler> handlerMap = new SparseArray<EncodedCatchHandler>(handlerCount);
176            encodedCatchHandlers = new EncodedCatchHandler[handlerCount];
177            for (int i=0; i<handlerCount; i++) {
178                int position = in.getCursor() - encodedHandlerStart;
179                encodedCatchHandlers[i] = new EncodedCatchHandler(dexFile, in);
180                handlerMap.append(position, encodedCatchHandlers[i]);
181            }
182            int codeItemEnd = in.getCursor();
183
184            //now go back and read the tries
185            in.setCursor(triesOffset);
186            tries = new TryItem[triesCount];
187            for (int i=0; i<triesCount; i++) {
188                tries[i] = new TryItem(in, handlerMap);
189            }
190
191            //and now back to the end of the code item
192            in.setCursor(codeItemEnd);
193        }
194    }
195
196    /** {@inheritDoc} */
197    protected int placeItem(int offset) {
198        offset += 16 + getInstructionsLength();
199
200        if (tries != null && tries.length > 0) {
201            if (offset % 4 != 0) {
202                offset+=2;
203            }
204
205            offset += tries.length * 8;
206            int encodedCatchHandlerBaseOffset = offset;
207            offset += Leb128Utils.unsignedLeb128Size(encodedCatchHandlers.length);
208            for (EncodedCatchHandler encodedCatchHandler: encodedCatchHandlers) {
209                offset = encodedCatchHandler.place(offset, encodedCatchHandlerBaseOffset);
210            }
211        }
212        return offset;
213    }
214
215    /** {@inheritDoc} */
216    protected void writeItem(final AnnotatedOutput out) {
217        int instructionsLength = getInstructionsLength()/2;
218
219        if (out.annotates()) {
220            out.annotate(0, parent.method.getMethodString());
221            out.annotate(2, "registers_size: 0x" + Integer.toHexString(registerCount) + " (" + registerCount + ")");
222            out.annotate(2, "ins_size: 0x" + Integer.toHexString(inWords) + " (" + inWords + ")");
223            out.annotate(2, "outs_size: 0x" + Integer.toHexString(outWords) + " (" + outWords + ")");
224            int triesLength = tries==null?0:tries.length;
225            out.annotate(2, "tries_size: 0x" + Integer.toHexString(triesLength) + " (" + triesLength + ")");
226            if (debugInfo == null) {
227                out.annotate(4, "debug_info_off:");
228            } else {
229                out.annotate(4, "debug_info_off: 0x" + debugInfo.getOffset());
230            }
231            out.annotate(4, "insns_size: 0x" + Integer.toHexString(instructionsLength) + " (" +
232                    (instructionsLength) + ")");
233        }
234
235        out.writeShort(registerCount);
236        out.writeShort(inWords);
237        out.writeShort(outWords);
238        if (tries == null) {
239            out.writeShort(0);
240        } else {
241            out.writeShort(tries.length);
242        }
243        if (debugInfo == null) {
244            out.writeInt(0);
245        } else {
246            out.writeInt(debugInfo.getOffset());
247        }
248
249        int currentCodeOffset = 0;
250        for (Instruction instruction: instructions) {
251            currentCodeOffset += instruction.getSize(currentCodeOffset);
252        }
253
254        out.writeInt(instructionsLength);
255
256        currentCodeOffset = 0;
257        for (Instruction instruction: instructions) {
258            currentCodeOffset = instruction.write(out, currentCodeOffset);
259        }
260
261        if (tries != null && tries.length > 0) {
262            if (out.annotates()) {
263                if ((currentCodeOffset % 4) != 0) {
264                    out.annotate("padding");
265                    out.writeShort(0);
266                }
267
268                int index = 0;
269                for (TryItem tryItem: tries) {
270                    out.annotate(0, "[0x" + Integer.toHexString(index++) + "] try_item");
271                    out.indent();
272                    tryItem.writeTo(out);
273                    out.deindent();
274                }
275
276                out.annotate("handler_count: 0x" + Integer.toHexString(encodedCatchHandlers.length) + "(" +
277                        encodedCatchHandlers.length + ")");
278                out.writeUnsignedLeb128(encodedCatchHandlers.length);
279
280                index = 0;
281                for (EncodedCatchHandler encodedCatchHandler: encodedCatchHandlers) {
282                    out.annotate(0, "[" + Integer.toHexString(index++) + "] encoded_catch_handler");
283                    out.indent();
284                    encodedCatchHandler.writeTo(out);
285                    out.deindent();
286                }
287            } else {
288                if ((currentCodeOffset % 4) != 0) {
289                    out.writeShort(0);
290                }
291
292                for (TryItem tryItem: tries) {
293                    tryItem.writeTo(out);
294                }
295
296                out.writeUnsignedLeb128(encodedCatchHandlers.length);
297
298                for (EncodedCatchHandler encodedCatchHandler: encodedCatchHandlers) {
299                    encodedCatchHandler.writeTo(out);
300                }
301            }
302        }
303    }
304
305    /** {@inheritDoc} */
306    public ItemType getItemType() {
307        return ItemType.TYPE_CODE_ITEM;
308    }
309
310    /** {@inheritDoc} */
311    public String getConciseIdentity() {
312        return "code_item @0x" + Integer.toHexString(getOffset());
313    }
314
315    /** {@inheritDoc} */
316    public int compareTo(CodeItem other) {
317        if (parent == null) {
318            if (other.parent == null) {
319                return 0;
320            }
321            return -1;
322        }
323        if (other.parent == null) {
324            return 1;
325        }
326        return parent.method.compareTo(other.parent.method);
327    }
328
329    /**
330     * @return the register count
331     */
332    public int getRegisterCount() {
333        return registerCount;
334    }
335
336    /**
337     * @return an array of the instructions in this code item
338     */
339    public Instruction[] getInstructions() {
340        return instructions;
341    }
342
343    /**
344     * @return an array of the <code>TryItem</code> objects in this <code>CodeItem</code>
345     */
346    public TryItem[] getTries() {
347        return tries;
348    }
349
350    /**
351     * @return an array of the <code>EncodedCatchHandler</code> objects in this <code>CodeItem</code>
352     */
353    public EncodedCatchHandler[] getHandlers() {
354        return encodedCatchHandlers;
355    }
356
357    /**
358     * @return the <code>DebugInfoItem</code> associated with this <code>CodeItem</code>
359     */
360    public DebugInfoItem getDebugInfo() {
361        return debugInfo;
362    }
363
364    /**
365     * Sets the <code>MethodIdItem</code> of the method that this <code>CodeItem</code> is associated with
366     * @param encodedMethod the <code>EncodedMethod</code> of the method that this <code>CodeItem</code> is associated
367     * with
368     */
369    protected void setParent(ClassDataItem.EncodedMethod encodedMethod) {
370        this.parent = encodedMethod;
371    }
372
373    /**
374     * @return the MethodIdItem of the method that this CodeItem belongs to
375     */
376    public ClassDataItem.EncodedMethod getParent() {
377        return parent;
378    }
379
380    /**
381     * Used by OdexUtil to update this <code>CodeItem</code> with a deodexed version of the instructions
382     * @param newInstructions the new instructions to use for this code item
383     */
384    public void updateCode(Instruction[] newInstructions) {
385        this.instructions = newInstructions;
386    }
387
388    private int getInstructionsLength() {
389        int offset = 0;
390        for (Instruction instruction: instructions) {
391            offset += instruction.getSize(offset);
392        }
393        return offset;
394    }
395
396    /**
397     * Go through the instructions and perform any of the following fixes that are applicable
398     * - Replace const-string instruction with const-string/jumbo, when the string index is too big
399     * - Replace goto and goto/16 with a larger version of goto, when the target is too far away
400     * TODO: we should be able to replace if-* instructions with targets that are too far away with a negated if followed by a goto/32 to the original target
401     * TODO: remove multiple nops that occur before a switch/array data pseudo instruction. In some cases, multiple smali-baksmali cycles with changes in between could cause nops to start piling up
402     *
403     * The above fixes are applied iteratively, until no more fixes have been performed
404     */
405    public void fixInstructions(boolean fixStringConst, boolean fixGoto) {
406        boolean didSomething = false;
407
408        do
409        {
410            didSomething = false;
411
412            int currentCodeOffset = 0;
413            for (int i=0; i<instructions.length; i++) {
414                Instruction instruction = instructions[i];
415
416                if (fixGoto && instruction.opcode == Opcode.GOTO) {
417                    int offset = ((OffsetInstruction)instruction).getOffset();
418
419                    if (((byte)offset) != offset) {
420                        //the offset doesn't fit within a byte, we need to upgrade to a goto/16 or goto/32
421
422                        if ((short)offset == offset) {
423                            //the offset fits in a short, so upgrade to a goto/16            h
424                            replaceInstructionAtOffset(currentCodeOffset, new Instruction20t(Opcode.GOTO_16, offset));
425                        }
426                        else {
427                            //The offset won't fit into a short, we have to upgrade to a goto/32
428                            replaceInstructionAtOffset(currentCodeOffset, new Instruction30t(Opcode.GOTO_32, offset));
429                        }
430                        didSomething = true;
431                        break;
432                    }
433                } else if (fixGoto && instruction.opcode == Opcode.GOTO_16) {
434                    int offset = ((OffsetInstruction)instruction).getOffset();
435
436                    if (((short)offset) != offset) {
437                        //the offset doesn't fit within a short, we need to upgrade to a goto/32
438                        replaceInstructionAtOffset(currentCodeOffset, new Instruction30t(Opcode.GOTO_32, offset));
439                        didSomething = true;
440                        break;
441                    }
442                } else if (fixStringConst && instruction.opcode == Opcode.CONST_STRING) {
443                    Instruction21c constStringInstruction = (Instruction21c)instruction;
444                    if (constStringInstruction.getReferencedItem().getIndex() > 0xFFFF) {
445                        replaceInstructionAtOffset(currentCodeOffset, new Instruction31c(Opcode.CONST_STRING_JUMBO,
446                                (short)constStringInstruction.getRegisterA(),
447                                constStringInstruction.getReferencedItem()));
448                        didSomething = true;
449                        break;
450                    }
451                }
452
453                currentCodeOffset += instruction.getSize(currentCodeOffset);
454            }
455        }while(didSomething);
456    }
457
458    private void replaceInstructionAtOffset(int offset, Instruction replacementInstruction) {
459        Instruction originalInstruction = null;
460
461        int[] originalInstructionOffsets = new int[instructions.length+1];
462        SparseIntArray originalSwitchOffsetByOriginalSwitchDataOffset = new SparseIntArray();
463
464        int currentCodeOffset = 0;
465        int instructionIndex = 0;
466        int i;
467        for (i=0; i<instructions.length; i++) {
468            Instruction instruction = instructions[i];
469
470            if (currentCodeOffset == offset) {
471                originalInstruction = instruction;
472                instructionIndex = i;
473            }
474
475            if (instruction.opcode == Opcode.PACKED_SWITCH || instruction.opcode == Opcode.SPARSE_SWITCH) {
476                OffsetInstruction offsetInstruction = (OffsetInstruction)instruction;
477
478                int switchDataOffset = currentCodeOffset + offsetInstruction.getOffset() * 2;
479                if (originalSwitchOffsetByOriginalSwitchDataOffset.indexOfKey(switchDataOffset) < 0) {
480                    originalSwitchOffsetByOriginalSwitchDataOffset.put(switchDataOffset, currentCodeOffset);
481                }
482            }
483
484            originalInstructionOffsets[i] = currentCodeOffset;
485            currentCodeOffset += instruction.getSize(currentCodeOffset);
486        }
487        //add the offset just past the end of the last instruction, to help when fixing up try blocks that end
488        //at the end of the method
489        originalInstructionOffsets[i] = currentCodeOffset;
490
491        if (originalInstruction == null) {
492            throw new RuntimeException("There is no instruction at offset " + offset);
493        }
494
495        instructions[instructionIndex] = replacementInstruction;
496
497        //if we're replacing the instruction with one of the same size, we don't have to worry about fixing
498        //up any offsets
499        if (originalInstruction.getSize(offset) == replacementInstruction.getSize(offset)) {
500            return;
501        }
502
503        //TODO: replace these with a callable delegate
504        final SparseIntArray originalOffsetsByNewOffset = new SparseIntArray();
505        final SparseIntArray newOffsetsByOriginalOffset = new SparseIntArray();
506
507        currentCodeOffset = 0;
508        for (i=0; i<instructions.length; i++) {
509            Instruction instruction = instructions[i];
510
511            int originalOffset = originalInstructionOffsets[i];
512            originalOffsetsByNewOffset.append(currentCodeOffset, originalOffset);
513            newOffsetsByOriginalOffset.append(originalOffset, currentCodeOffset);
514
515            currentCodeOffset += instruction.getSize(currentCodeOffset);
516        }
517
518        //add the offset just past the end of the last instruction, to help when fixing up try blocks that end
519        //at the end of the method
520        originalOffsetsByNewOffset.append(currentCodeOffset, originalInstructionOffsets[i]);
521        newOffsetsByOriginalOffset.append(originalInstructionOffsets[i], currentCodeOffset);
522
523        //update any "offset" instructions, or switch data instructions
524        currentCodeOffset = 0;
525        for (i=0; i<instructions.length; i++) {
526            Instruction instruction = instructions[i];
527
528            if (instruction instanceof OffsetInstruction) {
529                OffsetInstruction offsetInstruction = (OffsetInstruction)instruction;
530
531                assert originalOffsetsByNewOffset.indexOfKey(currentCodeOffset) >= 0;
532                int originalOffset = originalOffsetsByNewOffset.get(currentCodeOffset);
533
534                int originalInstructionTarget = originalOffset + offsetInstruction.getOffset() * 2;
535
536                assert newOffsetsByOriginalOffset.indexOfKey(originalInstructionTarget) >= 0;
537                int newInstructionTarget = newOffsetsByOriginalOffset.get(originalInstructionTarget);
538
539                int newOffset = (newInstructionTarget - currentCodeOffset) / 2;
540
541                if (newOffset != offsetInstruction.getOffset()) {
542                    offsetInstruction.updateOffset(newOffset);
543                }
544            } else if (instruction instanceof MultiOffsetInstruction) {
545                MultiOffsetInstruction multiOffsetInstruction = (MultiOffsetInstruction)instruction;
546
547                assert originalOffsetsByNewOffset.indexOfKey(currentCodeOffset) >= 0;
548                int originalDataOffset = originalOffsetsByNewOffset.get(currentCodeOffset);
549
550                int originalSwitchOffset = originalSwitchOffsetByOriginalSwitchDataOffset.get(originalDataOffset);
551                if (originalSwitchOffset == 0) {
552                    throw new RuntimeException("This method contains an unreferenced switch data block, and can't be automatically fixed.");
553                }
554
555                assert newOffsetsByOriginalOffset.indexOfKey(originalSwitchOffset) >= 0;
556                int newSwitchOffset = newOffsetsByOriginalOffset.get(originalSwitchOffset);
557
558                int[] targets = multiOffsetInstruction.getTargets();
559                for (int t=0; t<targets.length; t++) {
560                    int originalTargetOffset = originalSwitchOffset + targets[t]*2;
561                    assert newOffsetsByOriginalOffset.indexOfKey(originalTargetOffset) >= 0;
562                    int newTargetOffset = newOffsetsByOriginalOffset.get(originalTargetOffset);
563                    int newOffset = (newTargetOffset - newSwitchOffset)/2;
564                    if (newOffset != targets[t]) {
565                        multiOffsetInstruction.updateTarget(t, newOffset);
566                    }
567                }
568            }
569            currentCodeOffset += instruction.getSize(currentCodeOffset);
570        }
571
572        if (debugInfo != null) {
573            final byte[] encodedDebugInfo = debugInfo.getEncodedDebugInfo();
574
575            ByteArrayInput debugInput = new ByteArrayInput(encodedDebugInfo);
576
577            DebugInstructionFixer debugInstructionFixer = new DebugInstructionFixer(encodedDebugInfo,
578                newOffsetsByOriginalOffset, originalOffsetsByNewOffset);
579            DebugInstructionIterator.IterateInstructions(debugInput, debugInstructionFixer);
580
581            if (debugInstructionFixer.result != null) {
582                debugInfo.setEncodedDebugInfo(debugInstructionFixer.result);
583            }
584        }
585
586        if (encodedCatchHandlers != null) {
587            for (EncodedCatchHandler encodedCatchHandler: encodedCatchHandlers) {
588                if (encodedCatchHandler.catchAllHandlerAddress != -1) {
589                    assert newOffsetsByOriginalOffset.indexOfKey(encodedCatchHandler.catchAllHandlerAddress*2) >= 0;
590                    encodedCatchHandler.catchAllHandlerAddress =
591                            newOffsetsByOriginalOffset.get(encodedCatchHandler.catchAllHandlerAddress*2)/2;
592                }
593
594                for (EncodedTypeAddrPair handler: encodedCatchHandler.handlers) {
595                    assert newOffsetsByOriginalOffset.indexOfKey(handler.handlerAddress*2) >= 0;
596                    handler.handlerAddress = newOffsetsByOriginalOffset.get(handler.handlerAddress*2)/2;
597                }
598            }
599        }
600
601        if (this.tries != null) {
602            for (TryItem tryItem: tries) {
603                int startAddress = tryItem.startAddress;
604                int endAddress = tryItem.startAddress + tryItem.instructionCount;
605
606                assert newOffsetsByOriginalOffset.indexOfKey(startAddress * 2) >= 0;
607                tryItem.startAddress = newOffsetsByOriginalOffset.get(startAddress * 2)/2;
608
609                assert newOffsetsByOriginalOffset.indexOfKey(endAddress * 2) >= 0;
610                tryItem.instructionCount = newOffsetsByOriginalOffset.get(endAddress * 2)/2 - tryItem.startAddress;
611            }
612        }
613    }
614
615    private class DebugInstructionFixer extends DebugInstructionIterator.ProcessRawDebugInstructionDelegate {
616        private int address = 0;
617        private SparseIntArray newOffsetsByOriginalOffset;
618        private SparseIntArray originalOffsetsByNewOffset;
619        private final byte[] originalEncodedDebugInfo;
620        public byte[] result = null;
621
622        public DebugInstructionFixer(byte[] originalEncodedDebugInfo, SparseIntArray newOffsetsByOriginalOffset,
623                                     SparseIntArray originalOffsetsByNewOffset) {
624            this.newOffsetsByOriginalOffset = newOffsetsByOriginalOffset;
625            this.originalOffsetsByNewOffset = originalOffsetsByNewOffset;
626            this.originalEncodedDebugInfo = originalEncodedDebugInfo;
627        }
628
629
630        @Override
631        public void ProcessAdvancePC(int startOffset, int length, int addressDelta) {
632            address += addressDelta;
633
634            if (result != null) {
635                return;
636            }
637
638            int newOffset = newOffsetsByOriginalOffset.get(address*2, -1);
639
640            //The address might not point to an actual instruction in some cases, for example, if an AdvancePC
641            //instruction was inserted just before a "special" instruction, to fix up the offsets for a previous
642            //instruction replacement.
643            //In this case, it should be safe to skip, because there will be another AdvancePC/SpecialOpcode that will
644            //bump up the address to point to a valid instruction before anything (line/local/etc.) is emitted
645            if (newOffset == -1) {
646                return;
647            }
648
649            assert newOffset != -1;
650            newOffset = newOffset / 2;
651
652            if (newOffset != address) {
653                int newAddressDelta = newOffset - (address - addressDelta);
654                assert newAddressDelta > 0;
655                int addressDiffSize = Leb128Utils.unsignedLeb128Size(newAddressDelta);
656
657                result = new byte[originalEncodedDebugInfo.length + addressDiffSize - (length - 1)];
658
659                System.arraycopy(originalEncodedDebugInfo, 0, result, 0, startOffset);
660
661                result[startOffset] = 0x01; //DBG_ADVANCE_PC debug opcode
662                Leb128Utils.writeUnsignedLeb128(newAddressDelta, result, startOffset+1);
663
664                System.arraycopy(originalEncodedDebugInfo, startOffset+length, result,
665                        startOffset + addressDiffSize + 1,
666                        originalEncodedDebugInfo.length - (startOffset + addressDiffSize + 1));
667            }
668        }
669
670        @Override
671        public void ProcessSpecialOpcode(int startOffset, int debugOpcode, int lineDelta,
672                                         int addressDelta) {
673            address += addressDelta;
674            if (result != null) {
675                return;
676            }
677
678            int newOffset = newOffsetsByOriginalOffset.get(address*2, -1);
679            assert newOffset != -1;
680            newOffset = newOffset / 2;
681
682            if (newOffset != address) {
683                int newAddressDelta = newOffset - (address - addressDelta);
684                assert newAddressDelta > 0;
685
686                //if the new address delta won't fit in the special opcode, we need to insert
687                //an additional DBG_ADVANCE_PC opcode
688                if (lineDelta < 2 && newAddressDelta > 16 || lineDelta > 1 && newAddressDelta > 15) {
689                    int additionalAddressDelta = newOffset - address;
690                    int additionalAddressDeltaSize = Leb128Utils.signedLeb128Size(additionalAddressDelta);
691
692                    result = new byte[originalEncodedDebugInfo.length + additionalAddressDeltaSize + 1];
693
694                    System.arraycopy(originalEncodedDebugInfo, 0, result, 0, startOffset);
695                    result[startOffset] = 0x01; //DBG_ADVANCE_PC
696                    Leb128Utils.writeUnsignedLeb128(additionalAddressDelta, result, startOffset+1);
697                    System.arraycopy(originalEncodedDebugInfo, startOffset, result,
698                            startOffset+additionalAddressDeltaSize+1,
699                            result.length - (startOffset+additionalAddressDeltaSize+1));
700                } else {
701                    result = new byte[originalEncodedDebugInfo.length];
702                    System.arraycopy(originalEncodedDebugInfo, 0, result, 0, result.length);
703                    result[startOffset] = DebugInfoBuilder.calculateSpecialOpcode(lineDelta,
704                            newAddressDelta);
705                }
706            }
707        }
708    }
709
710    public static class TryItem {
711        /**
712         * The address (in 2-byte words) within the code where the try block starts
713         */
714        private int startAddress;
715
716        /**
717         * The number of 2-byte words that the try block covers
718         */
719        private int instructionCount;
720
721        /**
722         * The associated exception handler
723         */
724        public final EncodedCatchHandler encodedCatchHandler;
725
726        /**
727         * Construct a new <code>TryItem</code> with the given values
728         * @param startAddress the address (in 2-byte words) within the code where the try block starts
729         * @param instructionCount the number of 2-byte words that the try block covers
730         * @param encodedCatchHandler the associated exception handler
731         */
732        public TryItem(int startAddress, int instructionCount, EncodedCatchHandler encodedCatchHandler) {
733            this.startAddress = startAddress;
734            this.instructionCount = instructionCount;
735            this.encodedCatchHandler = encodedCatchHandler;
736        }
737
738        /**
739         * This is used internally to construct a new <code>TryItem</code> while reading in a <code>DexFile</code>
740         * @param in the Input object to read the <code>TryItem</code> from
741         * @param encodedCatchHandlers a SparseArray of the EncodedCatchHandlers for this <code>CodeItem</code>. The
742         * key should be the offset of the EncodedCatchHandler from the beginning of the encoded_catch_handler_list
743         * structure.
744         */
745        private TryItem(Input in, SparseArray<EncodedCatchHandler> encodedCatchHandlers) {
746            startAddress = in.readInt();
747            instructionCount = in.readShort();
748
749            encodedCatchHandler = encodedCatchHandlers.get(in.readShort());
750            if (encodedCatchHandler == null) {
751                throw new RuntimeException("Could not find the EncodedCatchHandler referenced by this TryItem");
752            }
753        }
754
755        /**
756         * Writes the <code>TryItem</code> to the given <code>AnnotatedOutput</code> object
757         * @param out the <code>AnnotatedOutput</code> object to write to
758         */
759        private void writeTo(AnnotatedOutput out) {
760            if (out.annotates()) {
761                out.annotate(4, "start_addr: 0x" + Integer.toHexString(startAddress));
762                out.annotate(2, "insn_count: 0x" + Integer.toHexString(instructionCount) + " (" + instructionCount +
763                        ")");
764                out.annotate(2, "handler_off: 0x" + Integer.toHexString(encodedCatchHandler.getOffsetInList()));
765            }
766
767            out.writeInt(startAddress);
768            out.writeShort(instructionCount);
769            out.writeShort(encodedCatchHandler.getOffsetInList());
770        }
771
772        /**
773         * @return The address (in 2-byte words) within the code where the try block starts
774         */
775        public int getStartAddress() {
776            return startAddress;
777        }
778
779        /**
780         * @return The number of 2-byte words that the try block covers
781         */
782        public int getInstructionCount() {
783            return instructionCount;
784        }
785    }
786
787    public static class EncodedCatchHandler {
788        /**
789         * An array of the individual exception handlers
790         */
791        public final EncodedTypeAddrPair[] handlers;
792
793        /**
794         * The address within the code (in 2-byte words) for the catch all handler, or -1 if there is no catch all
795         * handler
796         */
797        private int catchAllHandlerAddress;
798
799        private int baseOffset;
800        private int offset;
801
802        /**
803         * Constructs a new <code>EncodedCatchHandler</code> with the given values
804         * @param handlers an array of the individual exception handlers
805         * @param catchAllHandlerAddress The address within the code (in 2-byte words) for the catch all handler, or -1
806         * if there is no catch all handler
807         */
808        public EncodedCatchHandler(EncodedTypeAddrPair[] handlers, int catchAllHandlerAddress) {
809            this.handlers = handlers;
810            this.catchAllHandlerAddress = catchAllHandlerAddress;
811        }
812
813        /**
814         * This is used internally to construct a new <code>EncodedCatchHandler</code> while reading in a
815         * <code>DexFile</code>
816         * @param dexFile the <code>DexFile</code> that is being read in
817         * @param in the Input object to read the <code>EncodedCatchHandler</code> from
818         */
819        private EncodedCatchHandler(DexFile dexFile, Input in) {
820            int handlerCount = in.readSignedLeb128();
821
822            if (handlerCount < 0) {
823                handlers = new EncodedTypeAddrPair[-1 * handlerCount];
824            } else {
825                handlers = new EncodedTypeAddrPair[handlerCount];
826            }
827
828            for (int i=0; i<handlers.length; i++) {
829                handlers[i] = new EncodedTypeAddrPair(dexFile, in);
830            }
831
832            if (handlerCount <= 0) {
833                catchAllHandlerAddress = in.readUnsignedLeb128();
834            } else {
835                catchAllHandlerAddress = -1;
836            }
837        }
838
839        /**
840         * Returns the "Catch All" handler address for this <code>EncodedCatchHandler</code>
841         * @return
842         */
843        public int getCatchAllHandlerAddress() {
844            return catchAllHandlerAddress;
845        }
846
847        /**
848         * @return the offset of this <code>EncodedCatchHandler</code> from the beginning of the
849         * encoded_catch_handler_list structure
850         */
851        private int getOffsetInList() {
852            return offset-baseOffset;
853        }
854
855        /**
856         * Places the <code>EncodedCatchHandler</code>, storing the offset and baseOffset, and returning the offset
857         * immediately following this <code>EncodedCatchHandler</code>
858         * @param offset the offset of this <code>EncodedCatchHandler</code> in the <code>DexFile</code>
859         * @param baseOffset the offset of the beginning of the encoded_catch_handler_list structure in the
860         * <code>DexFile</code>
861         * @return the offset immediately following this <code>EncodedCatchHandler</code>
862         */
863        private int place(int offset, int baseOffset) {
864            this.offset = offset;
865            this.baseOffset = baseOffset;
866
867            int size = handlers.length;
868            if (catchAllHandlerAddress > -1) {
869                size *= -1;
870                offset += Leb128Utils.unsignedLeb128Size(catchAllHandlerAddress);
871            }
872            offset += Leb128Utils.signedLeb128Size(size);
873
874            for (EncodedTypeAddrPair handler: handlers) {
875                offset += handler.getSize();
876            }
877            return offset;
878        }
879
880        /**
881         * Writes the <code>EncodedCatchHandler</code> to the given <code>AnnotatedOutput</code> object
882         * @param out the <code>AnnotatedOutput</code> object to write to
883         */
884        private void writeTo(AnnotatedOutput out) {
885            if (out.annotates()) {
886                out.annotate("size: 0x" + Integer.toHexString(handlers.length) + " (" + handlers.length + ")");
887
888                int size = handlers.length;
889                if (catchAllHandlerAddress > -1) {
890                    size = size * -1;
891                }
892                out.writeSignedLeb128(size);
893
894                int index = 0;
895                for (EncodedTypeAddrPair handler: handlers) {
896                    out.annotate(0, "[" + index++ + "] encoded_type_addr_pair");
897                    out.indent();
898                    handler.writeTo(out);
899                    out.deindent();
900                }
901
902                if (catchAllHandlerAddress > -1) {
903                    out.annotate("catch_all_addr: 0x" + Integer.toHexString(catchAllHandlerAddress));
904                    out.writeUnsignedLeb128(catchAllHandlerAddress);
905                }
906            } else {
907                int size = handlers.length;
908                if (catchAllHandlerAddress > -1) {
909                    size = size * -1;
910                }
911                out.writeSignedLeb128(size);
912
913                for (EncodedTypeAddrPair handler: handlers) {
914                    handler.writeTo(out);
915                }
916
917                if (catchAllHandlerAddress > -1) {
918                    out.writeUnsignedLeb128(catchAllHandlerAddress);
919                }
920            }
921        }
922
923        @Override
924        public int hashCode() {
925            int hash = 0;
926            for (EncodedTypeAddrPair handler: handlers) {
927                hash = hash * 31 + handler.hashCode();
928            }
929            hash = hash * 31 + catchAllHandlerAddress;
930            return hash;
931        }
932
933        @Override
934        public boolean equals(Object o) {
935            if (this==o) {
936                return true;
937            }
938            if (o==null || !this.getClass().equals(o.getClass())) {
939                return false;
940            }
941
942            EncodedCatchHandler other = (EncodedCatchHandler)o;
943            if (handlers.length != other.handlers.length || catchAllHandlerAddress != other.catchAllHandlerAddress) {
944                return false;
945            }
946
947            for (int i=0; i<handlers.length; i++) {
948                if (!handlers[i].equals(other.handlers[i])) {
949                    return false;
950                }
951            }
952
953            return true;
954        }
955    }
956
957    public static class EncodedTypeAddrPair {
958        /**
959         * The type of the <code>Exception</code> that this handler handles
960         */
961        public final TypeIdItem exceptionType;
962
963        /**
964         * The address (in 2-byte words) in the code of the handler
965         */
966        private int handlerAddress;
967
968        /**
969         * Constructs a new <code>EncodedTypeAddrPair</code> with the given values
970         * @param exceptionType the type of the <code>Exception</code> that this handler handles
971         * @param handlerAddress the address (in 2-byte words) in the code of the handler
972         */
973        public EncodedTypeAddrPair(TypeIdItem exceptionType, int handlerAddress) {
974            this.exceptionType = exceptionType;
975            this.handlerAddress = handlerAddress;
976        }
977
978        /**
979         * This is used internally to construct a new <code>EncodedTypeAddrPair</code> while reading in a
980         * <code>DexFile</code>
981         * @param dexFile the <code>DexFile</code> that is being read in
982         * @param in the Input object to read the <code>EncodedCatchHandler</code> from
983         */
984        private EncodedTypeAddrPair(DexFile dexFile, Input in) {
985            exceptionType = dexFile.TypeIdsSection.getItemByIndex(in.readUnsignedLeb128());
986            handlerAddress = in.readUnsignedLeb128();
987        }
988
989        /**
990         * @return the size of this <code>EncodedTypeAddrPair</code>
991         */
992        private int getSize() {
993            return Leb128Utils.unsignedLeb128Size(exceptionType.getIndex()) +
994                   Leb128Utils.unsignedLeb128Size(handlerAddress);
995        }
996
997        /**
998         * Writes the <code>EncodedTypeAddrPair</code> to the given <code>AnnotatedOutput</code> object
999         * @param out the <code>AnnotatedOutput</code> object to write to
1000         */
1001        private void writeTo(AnnotatedOutput out) {
1002            if (out.annotates()) {
1003                out.annotate("exception_type: " + exceptionType.getTypeDescriptor());
1004                out.writeUnsignedLeb128(exceptionType.getIndex());
1005
1006                out.annotate("handler_addr: 0x" + Integer.toHexString(handlerAddress));
1007                out.writeUnsignedLeb128(handlerAddress);
1008            } else {
1009                out.writeUnsignedLeb128(exceptionType.getIndex());
1010                out.writeUnsignedLeb128(handlerAddress);
1011            }
1012        }
1013
1014        public int getHandlerAddress() {
1015            return handlerAddress;
1016        }
1017
1018        @Override
1019        public int hashCode() {
1020            return exceptionType.hashCode() * 31 + handlerAddress;
1021        }
1022
1023        @Override
1024        public boolean equals(Object o) {
1025            if (this==o) {
1026                return true;
1027            }
1028            if (o==null || !this.getClass().equals(o.getClass())) {
1029                return false;
1030            }
1031
1032            EncodedTypeAddrPair other = (EncodedTypeAddrPair)o;
1033            return exceptionType == other.exceptionType && handlerAddress == other.handlerAddress;
1034        }
1035    }
1036}
1037