CodeItem.java revision a8ca776c1d369376e7804d4ee2e9a008c705e69a
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                    //TODO: is it safe to skip an unreferenced switch data instruction? Or should it throw an exception?
553                    continue;
554                }
555
556                assert newOffsetsByOriginalOffset.indexOfKey(originalSwitchOffset) >= 0;
557                int newSwitchOffset = newOffsetsByOriginalOffset.get(originalSwitchOffset);
558
559                int[] targets = multiOffsetInstruction.getTargets();
560                for (int t=0; t<targets.length; t++) {
561                    int originalTargetOffset = originalSwitchOffset + targets[t]*2;
562                    assert newOffsetsByOriginalOffset.indexOfKey(originalTargetOffset) >= 0;
563                    int newTargetOffset = newOffsetsByOriginalOffset.get(originalTargetOffset);
564                    int newOffset = (newTargetOffset - newSwitchOffset)/2;
565                    if (newOffset != targets[t]) {
566                        multiOffsetInstruction.updateTarget(t, newOffset);
567                    }
568                }
569            }
570            currentCodeOffset += instruction.getSize(currentCodeOffset);
571        }
572
573        if (debugInfo != null) {
574            final byte[] encodedDebugInfo = debugInfo.getEncodedDebugInfo();
575
576            ByteArrayInput debugInput = new ByteArrayInput(encodedDebugInfo);
577
578            DebugInstructionFixer debugInstructionFixer = new DebugInstructionFixer(encodedDebugInfo,
579                newOffsetsByOriginalOffset, originalOffsetsByNewOffset);
580            DebugInstructionIterator.IterateInstructions(debugInput, debugInstructionFixer);
581
582            assert debugInstructionFixer.result != null;
583
584            if (debugInstructionFixer.result != null) {
585                debugInfo.setEncodedDebugInfo(debugInstructionFixer.result);
586            }
587        }
588
589        if (encodedCatchHandlers != null) {
590            for (EncodedCatchHandler encodedCatchHandler: encodedCatchHandlers) {
591                if (encodedCatchHandler.catchAllHandlerAddress != -1) {
592                    assert newOffsetsByOriginalOffset.indexOfKey(encodedCatchHandler.catchAllHandlerAddress*2) >= 0;
593                    encodedCatchHandler.catchAllHandlerAddress =
594                            newOffsetsByOriginalOffset.get(encodedCatchHandler.catchAllHandlerAddress*2)/2;
595                }
596
597                for (EncodedTypeAddrPair handler: encodedCatchHandler.handlers) {
598                    assert newOffsetsByOriginalOffset.indexOfKey(handler.handlerAddress*2) >= 0;
599                    handler.handlerAddress = newOffsetsByOriginalOffset.get(handler.handlerAddress*2)/2;
600                }
601            }
602        }
603
604        if (this.tries != null) {
605            for (TryItem tryItem: tries) {
606                int startAddress = tryItem.startAddress;
607                int endAddress = tryItem.startAddress + tryItem.instructionCount;
608
609                assert newOffsetsByOriginalOffset.indexOfKey(startAddress * 2) >= 0;
610                tryItem.startAddress = newOffsetsByOriginalOffset.get(startAddress * 2)/2;
611
612                assert newOffsetsByOriginalOffset.indexOfKey(endAddress * 2) >= 0;
613                tryItem.instructionCount = newOffsetsByOriginalOffset.get(endAddress * 2)/2 - tryItem.startAddress;
614            }
615        }
616    }
617
618    private class DebugInstructionFixer extends DebugInstructionIterator.ProcessRawDebugInstructionDelegate {
619        private int address = 0;
620        private SparseIntArray newOffsetsByOriginalOffset;
621        private SparseIntArray originalOffsetsByNewOffset;
622        private final byte[] originalEncodedDebugInfo;
623        public byte[] result = null;
624
625        public DebugInstructionFixer(byte[] originalEncodedDebugInfo, SparseIntArray newOffsetsByOriginalOffset,
626                                     SparseIntArray originalOffsetsByNewOffset) {
627            this.newOffsetsByOriginalOffset = newOffsetsByOriginalOffset;
628            this.originalOffsetsByNewOffset = originalOffsetsByNewOffset;
629            this.originalEncodedDebugInfo = originalEncodedDebugInfo;
630        }
631
632
633        @Override
634        public void ProcessAdvancePC(int startOffset, int length, int addressDelta) {
635            address += addressDelta;
636
637            if (result != null) {
638                return;
639            }
640
641            int newOffset = newOffsetsByOriginalOffset.get(address*2, -1);
642
643            //The address might not point to an actual instruction in some cases, for example, if an AdvancePC
644            //instruction was inserted just before a "special" instruction, to fix up the offsets for a previous
645            //instruction replacement.
646            //In this case, it should be safe to skip, because there will be another AdvancePC/SpecialOpcode that will
647            //bump up the address to point to a valid instruction before anything (line/local/etc.) is emitted
648            if (newOffset == -1) {
649                return;
650            }
651
652            assert newOffset != -1;
653            newOffset = newOffset / 2;
654
655            if (newOffset != address) {
656                int newAddressDelta = newOffset - (address - addressDelta);
657                assert newAddressDelta > 0;
658                int addressDiffSize = Leb128Utils.unsignedLeb128Size(newAddressDelta);
659
660                result = new byte[originalEncodedDebugInfo.length + addressDiffSize - (length - 1)];
661
662                System.arraycopy(originalEncodedDebugInfo, 0, result, 0, startOffset);
663
664                result[startOffset] = 0x01; //DBG_ADVANCE_PC debug opcode
665                Leb128Utils.writeUnsignedLeb128(newAddressDelta, result, startOffset+1);
666
667                System.arraycopy(originalEncodedDebugInfo, startOffset+length, result,
668                        startOffset + addressDiffSize + 1,
669                        originalEncodedDebugInfo.length - (startOffset + addressDiffSize + 1));
670            }
671        }
672
673        @Override
674        public void ProcessSpecialOpcode(int startOffset, int debugOpcode, int lineDelta,
675                                         int addressDelta) {
676            address += addressDelta;
677            if (result != null) {
678                return;
679            }
680
681            int newOffset = newOffsetsByOriginalOffset.get(address*2, -1);
682            assert newOffset != -1;
683            newOffset = newOffset / 2;
684
685            if (newOffset != address) {
686                int newAddressDelta = newOffset - (address - addressDelta);
687                assert newAddressDelta > 0;
688
689                //if the new address delta won't fit in the special opcode, we need to insert
690                //an additional DBG_ADVANCE_PC opcode
691                if (lineDelta < 2 && newAddressDelta > 16 || lineDelta > 1 && newAddressDelta > 15) {
692                    int additionalAddressDelta = newOffset - address;
693                    int additionalAddressDeltaSize = Leb128Utils.signedLeb128Size(additionalAddressDelta);
694
695                    result = new byte[originalEncodedDebugInfo.length + additionalAddressDeltaSize + 1];
696
697                    System.arraycopy(originalEncodedDebugInfo, 0, result, 0, startOffset);
698                    result[startOffset] = 0x01; //DBG_ADVANCE_PC
699                    Leb128Utils.writeUnsignedLeb128(additionalAddressDelta, result, startOffset+1);
700                    System.arraycopy(originalEncodedDebugInfo, startOffset, result,
701                            startOffset+additionalAddressDeltaSize+1,
702                            result.length - (startOffset+additionalAddressDeltaSize+1));
703                } else {
704                    result = new byte[originalEncodedDebugInfo.length];
705                    System.arraycopy(originalEncodedDebugInfo, 0, result, 0, result.length);
706                    result[startOffset] = DebugInfoBuilder.calculateSpecialOpcode(lineDelta,
707                            newAddressDelta);
708                }
709            }
710        }
711    }
712
713    public static class TryItem {
714        /**
715         * The address (in 2-byte words) within the code where the try block starts
716         */
717        private int startAddress;
718
719        /**
720         * The number of 2-byte words that the try block covers
721         */
722        private int instructionCount;
723
724        /**
725         * The associated exception handler
726         */
727        public final EncodedCatchHandler encodedCatchHandler;
728
729        /**
730         * Construct a new <code>TryItem</code> with the given values
731         * @param startAddress the address (in 2-byte words) within the code where the try block starts
732         * @param instructionCount the number of 2-byte words that the try block covers
733         * @param encodedCatchHandler the associated exception handler
734         */
735        public TryItem(int startAddress, int instructionCount, EncodedCatchHandler encodedCatchHandler) {
736            this.startAddress = startAddress;
737            this.instructionCount = instructionCount;
738            this.encodedCatchHandler = encodedCatchHandler;
739        }
740
741        /**
742         * This is used internally to construct a new <code>TryItem</code> while reading in a <code>DexFile</code>
743         * @param in the Input object to read the <code>TryItem</code> from
744         * @param encodedCatchHandlers a SparseArray of the EncodedCatchHandlers for this <code>CodeItem</code>. The
745         * key should be the offset of the EncodedCatchHandler from the beginning of the encoded_catch_handler_list
746         * structure.
747         */
748        private TryItem(Input in, SparseArray<EncodedCatchHandler> encodedCatchHandlers) {
749            startAddress = in.readInt();
750            instructionCount = in.readShort();
751
752            encodedCatchHandler = encodedCatchHandlers.get(in.readShort());
753            if (encodedCatchHandler == null) {
754                throw new RuntimeException("Could not find the EncodedCatchHandler referenced by this TryItem");
755            }
756        }
757
758        /**
759         * Writes the <code>TryItem</code> to the given <code>AnnotatedOutput</code> object
760         * @param out the <code>AnnotatedOutput</code> object to write to
761         */
762        private void writeTo(AnnotatedOutput out) {
763            if (out.annotates()) {
764                out.annotate(4, "start_addr: 0x" + Integer.toHexString(startAddress));
765                out.annotate(2, "insn_count: 0x" + Integer.toHexString(instructionCount) + " (" + instructionCount +
766                        ")");
767                out.annotate(2, "handler_off: 0x" + Integer.toHexString(encodedCatchHandler.getOffsetInList()));
768            }
769
770            out.writeInt(startAddress);
771            out.writeShort(instructionCount);
772            out.writeShort(encodedCatchHandler.getOffsetInList());
773        }
774
775        /**
776         * @return The address (in 2-byte words) within the code where the try block starts
777         */
778        public int getStartAddress() {
779            return startAddress;
780        }
781
782        /**
783         * @return The number of 2-byte words that the try block covers
784         */
785        public int getInstructionCount() {
786            return instructionCount;
787        }
788    }
789
790    public static class EncodedCatchHandler {
791        /**
792         * An array of the individual exception handlers
793         */
794        public final EncodedTypeAddrPair[] handlers;
795
796        /**
797         * The address within the code (in 2-byte words) for the catch all handler, or -1 if there is no catch all
798         * handler
799         */
800        private int catchAllHandlerAddress;
801
802        private int baseOffset;
803        private int offset;
804
805        /**
806         * Constructs a new <code>EncodedCatchHandler</code> with the given values
807         * @param handlers an array of the individual exception handlers
808         * @param catchAllHandlerAddress The address within the code (in 2-byte words) for the catch all handler, or -1
809         * if there is no catch all handler
810         */
811        public EncodedCatchHandler(EncodedTypeAddrPair[] handlers, int catchAllHandlerAddress) {
812            this.handlers = handlers;
813            this.catchAllHandlerAddress = catchAllHandlerAddress;
814        }
815
816        /**
817         * This is used internally to construct a new <code>EncodedCatchHandler</code> while reading in a
818         * <code>DexFile</code>
819         * @param dexFile the <code>DexFile</code> that is being read in
820         * @param in the Input object to read the <code>EncodedCatchHandler</code> from
821         */
822        private EncodedCatchHandler(DexFile dexFile, Input in) {
823            int handlerCount = in.readSignedLeb128();
824
825            if (handlerCount < 0) {
826                handlers = new EncodedTypeAddrPair[-1 * handlerCount];
827            } else {
828                handlers = new EncodedTypeAddrPair[handlerCount];
829            }
830
831            for (int i=0; i<handlers.length; i++) {
832                handlers[i] = new EncodedTypeAddrPair(dexFile, in);
833            }
834
835            if (handlerCount <= 0) {
836                catchAllHandlerAddress = in.readUnsignedLeb128();
837            } else {
838                catchAllHandlerAddress = -1;
839            }
840        }
841
842        /**
843         * Returns the "Catch All" handler address for this <code>EncodedCatchHandler</code>
844         * @return
845         */
846        public int getCatchAllHandlerAddress() {
847            return catchAllHandlerAddress;
848        }
849
850        /**
851         * @return the offset of this <code>EncodedCatchHandler</code> from the beginning of the
852         * encoded_catch_handler_list structure
853         */
854        private int getOffsetInList() {
855            return offset-baseOffset;
856        }
857
858        /**
859         * Places the <code>EncodedCatchHandler</code>, storing the offset and baseOffset, and returning the offset
860         * immediately following this <code>EncodedCatchHandler</code>
861         * @param offset the offset of this <code>EncodedCatchHandler</code> in the <code>DexFile</code>
862         * @param baseOffset the offset of the beginning of the encoded_catch_handler_list structure in the
863         * <code>DexFile</code>
864         * @return the offset immediately following this <code>EncodedCatchHandler</code>
865         */
866        private int place(int offset, int baseOffset) {
867            this.offset = offset;
868            this.baseOffset = baseOffset;
869
870            int size = handlers.length;
871            if (catchAllHandlerAddress > -1) {
872                size *= -1;
873                offset += Leb128Utils.unsignedLeb128Size(catchAllHandlerAddress);
874            }
875            offset += Leb128Utils.signedLeb128Size(size);
876
877            for (EncodedTypeAddrPair handler: handlers) {
878                offset += handler.getSize();
879            }
880            return offset;
881        }
882
883        /**
884         * Writes the <code>EncodedCatchHandler</code> to the given <code>AnnotatedOutput</code> object
885         * @param out the <code>AnnotatedOutput</code> object to write to
886         */
887        private void writeTo(AnnotatedOutput out) {
888            if (out.annotates()) {
889                out.annotate("size: 0x" + Integer.toHexString(handlers.length) + " (" + handlers.length + ")");
890
891                int size = handlers.length;
892                if (catchAllHandlerAddress > -1) {
893                    size = size * -1;
894                }
895                out.writeSignedLeb128(size);
896
897                int index = 0;
898                for (EncodedTypeAddrPair handler: handlers) {
899                    out.annotate(0, "[" + index++ + "] encoded_type_addr_pair");
900                    out.indent();
901                    handler.writeTo(out);
902                    out.deindent();
903                }
904
905                if (catchAllHandlerAddress > -1) {
906                    out.annotate("catch_all_addr: 0x" + Integer.toHexString(catchAllHandlerAddress));
907                    out.writeUnsignedLeb128(catchAllHandlerAddress);
908                }
909            } else {
910                int size = handlers.length;
911                if (catchAllHandlerAddress > -1) {
912                    size = size * -1;
913                }
914                out.writeSignedLeb128(size);
915
916                for (EncodedTypeAddrPair handler: handlers) {
917                    handler.writeTo(out);
918                }
919
920                if (catchAllHandlerAddress > -1) {
921                    out.writeUnsignedLeb128(catchAllHandlerAddress);
922                }
923            }
924        }
925
926        @Override
927        public int hashCode() {
928            int hash = 0;
929            for (EncodedTypeAddrPair handler: handlers) {
930                hash = hash * 31 + handler.hashCode();
931            }
932            hash = hash * 31 + catchAllHandlerAddress;
933            return hash;
934        }
935
936        @Override
937        public boolean equals(Object o) {
938            if (this==o) {
939                return true;
940            }
941            if (o==null || !this.getClass().equals(o.getClass())) {
942                return false;
943            }
944
945            EncodedCatchHandler other = (EncodedCatchHandler)o;
946            if (handlers.length != other.handlers.length || catchAllHandlerAddress != other.catchAllHandlerAddress) {
947                return false;
948            }
949
950            for (int i=0; i<handlers.length; i++) {
951                if (!handlers[i].equals(other.handlers[i])) {
952                    return false;
953                }
954            }
955
956            return true;
957        }
958    }
959
960    public static class EncodedTypeAddrPair {
961        /**
962         * The type of the <code>Exception</code> that this handler handles
963         */
964        public final TypeIdItem exceptionType;
965
966        /**
967         * The address (in 2-byte words) in the code of the handler
968         */
969        private int handlerAddress;
970
971        /**
972         * Constructs a new <code>EncodedTypeAddrPair</code> with the given values
973         * @param exceptionType the type of the <code>Exception</code> that this handler handles
974         * @param handlerAddress the address (in 2-byte words) in the code of the handler
975         */
976        public EncodedTypeAddrPair(TypeIdItem exceptionType, int handlerAddress) {
977            this.exceptionType = exceptionType;
978            this.handlerAddress = handlerAddress;
979        }
980
981        /**
982         * This is used internally to construct a new <code>EncodedTypeAddrPair</code> while reading in a
983         * <code>DexFile</code>
984         * @param dexFile the <code>DexFile</code> that is being read in
985         * @param in the Input object to read the <code>EncodedCatchHandler</code> from
986         */
987        private EncodedTypeAddrPair(DexFile dexFile, Input in) {
988            exceptionType = dexFile.TypeIdsSection.getItemByIndex(in.readUnsignedLeb128());
989            handlerAddress = in.readUnsignedLeb128();
990        }
991
992        /**
993         * @return the size of this <code>EncodedTypeAddrPair</code>
994         */
995        private int getSize() {
996            return Leb128Utils.unsignedLeb128Size(exceptionType.getIndex()) +
997                   Leb128Utils.unsignedLeb128Size(handlerAddress);
998        }
999
1000        /**
1001         * Writes the <code>EncodedTypeAddrPair</code> to the given <code>AnnotatedOutput</code> object
1002         * @param out the <code>AnnotatedOutput</code> object to write to
1003         */
1004        private void writeTo(AnnotatedOutput out) {
1005            if (out.annotates()) {
1006                out.annotate("exception_type: " + exceptionType.getTypeDescriptor());
1007                out.writeUnsignedLeb128(exceptionType.getIndex());
1008
1009                out.annotate("handler_addr: 0x" + Integer.toHexString(handlerAddress));
1010                out.writeUnsignedLeb128(handlerAddress);
1011            } else {
1012                out.writeUnsignedLeb128(exceptionType.getIndex());
1013                out.writeUnsignedLeb128(handlerAddress);
1014            }
1015        }
1016
1017        public int getHandlerAddress() {
1018            return handlerAddress;
1019        }
1020
1021        @Override
1022        public int hashCode() {
1023            return exceptionType.hashCode() * 31 + handlerAddress;
1024        }
1025
1026        @Override
1027        public boolean equals(Object o) {
1028            if (this==o) {
1029                return true;
1030            }
1031            if (o==null || !this.getClass().equals(o.getClass())) {
1032                return false;
1033            }
1034
1035            EncodedTypeAddrPair other = (EncodedTypeAddrPair)o;
1036            return exceptionType == other.exceptionType && handlerAddress == other.handlerAddress;
1037        }
1038    }
1039}
1040