CodeItem.java revision 2904f4060318acebfa5a1c8d43b362dcfdd063b0
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        //TODO: should mention the method name here
313        return "code_item @0x" + Integer.toHexString(getOffset());
314    }
315
316    /** {@inheritDoc} */
317    public int compareTo(CodeItem other) {
318        if (parent == null) {
319            if (other.parent == null) {
320                return 0;
321            }
322            return -1;
323        }
324        if (other.parent == null) {
325            return 1;
326        }
327        return parent.method.compareTo(other.parent.method);
328    }
329
330    /**
331     * @return the register count
332     */
333    public int getRegisterCount() {
334        return registerCount;
335    }
336
337    /**
338     * @return an array of the instructions in this code item
339     */
340    public Instruction[] getInstructions() {
341        return instructions;
342    }
343
344    /**
345     * @return an array of the <code>TryItem</code> objects in this <code>CodeItem</code>
346     */
347    public TryItem[] getTries() {
348        return tries;
349    }
350
351    /**
352     * @return an array of the <code>EncodedCatchHandler</code> objects in this <code>CodeItem</code>
353     */
354    public EncodedCatchHandler[] getHandlers() {
355        return encodedCatchHandlers;
356    }
357
358    /**
359     * @return the <code>DebugInfoItem</code> associated with this <code>CodeItem</code>
360     */
361    public DebugInfoItem getDebugInfo() {
362        return debugInfo;
363    }
364
365    /**
366     * Sets the <code>MethodIdItem</code> of the method that this <code>CodeItem</code> is associated with
367     * @param encodedMethod the <code>EncodedMethod</code> of the method that this <code>CodeItem</code> is associated
368     * with
369     */
370    protected void setParent(ClassDataItem.EncodedMethod encodedMethod) {
371        this.parent = encodedMethod;
372    }
373
374    /**
375     * @return the MethodIdItem of the method that this CodeItem belongs to
376     */
377    public ClassDataItem.EncodedMethod getParent() {
378        return parent;
379    }
380
381    /**
382     * Used by OdexUtil to update this <code>CodeItem</code> with a deodexed version of the instructions
383     * @param newInstructions the new instructions to use for this code item
384     */
385    public void updateCode(Instruction[] newInstructions) {
386        this.instructions = newInstructions;
387    }
388
389    private int getInstructionsLength() {
390        int offset = 0;
391        for (Instruction instruction: instructions) {
392            offset += instruction.getSize(offset);
393        }
394        return offset;
395    }
396
397    /**
398     * Go through the instructions and perform any of the following fixes that are applicable
399     * - Replace const-string instruction with const-string/jumbo, when the string index is too big
400     * - Replace goto and goto/16 with a larger version of goto, when the target is too far away
401     * 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
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        //TODO: would it be possible to get away without having these? and generate/create these values while writing?
803        private int baseOffset;
804        private int offset;
805
806        /**
807         * Constructs a new <code>EncodedCatchHandler</code> with the given values
808         * @param handlers an array of the individual exception handlers
809         * @param catchAllHandlerAddress The address within the code (in 2-byte words) for the catch all handler, or -1
810         * if there is no catch all handler
811         */
812        public EncodedCatchHandler(EncodedTypeAddrPair[] handlers, int catchAllHandlerAddress) {
813            this.handlers = handlers;
814            this.catchAllHandlerAddress = catchAllHandlerAddress;
815        }
816
817        /**
818         * This is used internally to construct a new <code>EncodedCatchHandler</code> while reading in a
819         * <code>DexFile</code>
820         * @param dexFile the <code>DexFile</code> that is being read in
821         * @param in the Input object to read the <code>EncodedCatchHandler</code> from
822         */
823        private EncodedCatchHandler(DexFile dexFile, Input in) {
824            int handlerCount = in.readSignedLeb128();
825
826            if (handlerCount < 0) {
827                handlers = new EncodedTypeAddrPair[-1 * handlerCount];
828            } else {
829                handlers = new EncodedTypeAddrPair[handlerCount];
830            }
831
832            for (int i=0; i<handlers.length; i++) {
833                handlers[i] = new EncodedTypeAddrPair(dexFile, in);
834            }
835
836            if (handlerCount <= 0) {
837                catchAllHandlerAddress = in.readUnsignedLeb128();
838            } else {
839                catchAllHandlerAddress = -1;
840            }
841        }
842
843        /**
844         * Returns the "Catch All" handler address for this <code>EncodedCatchHandler</code>
845         * @return
846         */
847        public int getCatchAllHandlerAddress() {
848            return catchAllHandlerAddress;
849        }
850
851        /**
852         * @return the offset of this <code>EncodedCatchHandler</code> from the beginning of the
853         * encoded_catch_handler_list structure
854         */
855        private int getOffsetInList() {
856            return offset-baseOffset;
857        }
858
859        /**
860         * Places the <code>EncodedCatchHandler</code>, storing the offset and baseOffset, and returning the offset
861         * immediately following this <code>EncodedCatchHandler</code>
862         * @param offset the offset of this <code>EncodedCatchHandler</code> in the <code>DexFile</code>
863         * @param baseOffset the offset of the beginning of the encoded_catch_handler_list structure in the
864         * <code>DexFile</code>
865         * @return the offset immediately following this <code>EncodedCatchHandler</code>
866         */
867        private int place(int offset, int baseOffset) {
868            this.offset = offset;
869            this.baseOffset = baseOffset;
870
871            int size = handlers.length;
872            if (catchAllHandlerAddress > -1) {
873                size *= -1;
874                offset += Leb128Utils.unsignedLeb128Size(catchAllHandlerAddress);
875            }
876            offset += Leb128Utils.signedLeb128Size(size);
877
878            for (EncodedTypeAddrPair handler: handlers) {
879                offset += handler.getSize();
880            }
881            return offset;
882        }
883
884        /**
885         * Writes the <code>EncodedCatchHandler</code> to the given <code>AnnotatedOutput</code> object
886         * @param out the <code>AnnotatedOutput</code> object to write to
887         */
888        private void writeTo(AnnotatedOutput out) {
889            if (out.annotates()) {
890                out.annotate("size: 0x" + Integer.toHexString(handlers.length) + " (" + handlers.length + ")");
891
892                int size = handlers.length;
893                if (catchAllHandlerAddress > -1) {
894                    size = size * -1;
895                }
896                out.writeSignedLeb128(size);
897
898                int index = 0;
899                for (EncodedTypeAddrPair handler: handlers) {
900                    out.annotate(0, "[" + index++ + "] encoded_type_addr_pair");
901                    out.indent();
902                    handler.writeTo(out);
903                    out.deindent();
904                }
905
906                if (catchAllHandlerAddress > -1) {
907                    out.annotate("catch_all_addr: 0x" + Integer.toHexString(catchAllHandlerAddress));
908                    out.writeUnsignedLeb128(catchAllHandlerAddress);
909                }
910            } else {
911                int size = handlers.length;
912                if (catchAllHandlerAddress > -1) {
913                    size = size * -1;
914                }
915                out.writeSignedLeb128(size);
916
917                for (EncodedTypeAddrPair handler: handlers) {
918                    handler.writeTo(out);
919                }
920
921                if (catchAllHandlerAddress > -1) {
922                    out.writeUnsignedLeb128(catchAllHandlerAddress);
923                }
924            }
925        }
926
927        @Override
928        public int hashCode() {
929            int hash = 0;
930            for (EncodedTypeAddrPair handler: handlers) {
931                hash = hash * 31 + handler.hashCode();
932            }
933            hash = hash * 31 + catchAllHandlerAddress;
934            return hash;
935        }
936
937        @Override
938        public boolean equals(Object o) {
939            if (this==o) {
940                return true;
941            }
942            if (o==null || !this.getClass().equals(o.getClass())) {
943                return false;
944            }
945
946            EncodedCatchHandler other = (EncodedCatchHandler)o;
947            if (handlers.length != other.handlers.length || catchAllHandlerAddress != other.catchAllHandlerAddress) {
948                return false;
949            }
950
951            for (int i=0; i<handlers.length; i++) {
952                if (!handlers[i].equals(other.handlers[i])) {
953                    return false;
954                }
955            }
956
957            return true;
958        }
959    }
960
961    public static class EncodedTypeAddrPair {
962        /**
963         * The type of the <code>Exception</code> that this handler handles
964         */
965        public final TypeIdItem exceptionType;
966
967        /**
968         * The address (in 2-byte words) in the code of the handler
969         */
970        private int handlerAddress;
971
972        /**
973         * Constructs a new <code>EncodedTypeAddrPair</code> with the given values
974         * @param exceptionType the type of the <code>Exception</code> that this handler handles
975         * @param handlerAddress the address (in 2-byte words) in the code of the handler
976         */
977        public EncodedTypeAddrPair(TypeIdItem exceptionType, int handlerAddress) {
978            this.exceptionType = exceptionType;
979            this.handlerAddress = handlerAddress;
980        }
981
982        /**
983         * This is used internally to construct a new <code>EncodedTypeAddrPair</code> while reading in a
984         * <code>DexFile</code>
985         * @param dexFile the <code>DexFile</code> that is being read in
986         * @param in the Input object to read the <code>EncodedCatchHandler</code> from
987         */
988        private EncodedTypeAddrPair(DexFile dexFile, Input in) {
989            exceptionType = dexFile.TypeIdsSection.getItemByIndex(in.readUnsignedLeb128());
990            handlerAddress = in.readUnsignedLeb128();
991        }
992
993        /**
994         * @return the size of this <code>EncodedTypeAddrPair</code>
995         */
996        private int getSize() {
997            return Leb128Utils.unsignedLeb128Size(exceptionType.getIndex()) +
998                   Leb128Utils.unsignedLeb128Size(handlerAddress);
999        }
1000
1001        /**
1002         * Writes the <code>EncodedTypeAddrPair</code> to the given <code>AnnotatedOutput</code> object
1003         * @param out the <code>AnnotatedOutput</code> object to write to
1004         */
1005        private void writeTo(AnnotatedOutput out) {
1006            if (out.annotates()) {
1007                out.annotate("exception_type: " + exceptionType.getTypeDescriptor());
1008                out.writeUnsignedLeb128(exceptionType.getIndex());
1009
1010                out.annotate("handler_addr: 0x" + Integer.toHexString(handlerAddress));
1011                out.writeUnsignedLeb128(handlerAddress);
1012            } else {
1013                out.writeUnsignedLeb128(exceptionType.getIndex());
1014                out.writeUnsignedLeb128(handlerAddress);
1015            }
1016        }
1017
1018        public int getHandlerAddress() {
1019            return handlerAddress;
1020        }
1021
1022        @Override
1023        public int hashCode() {
1024            return exceptionType.hashCode() * 31 + handlerAddress;
1025        }
1026
1027        @Override
1028        public boolean equals(Object o) {
1029            if (this==o) {
1030                return true;
1031            }
1032            if (o==null || !this.getClass().equals(o.getClass())) {
1033                return false;
1034            }
1035
1036            EncodedTypeAddrPair other = (EncodedTypeAddrPair)o;
1037            return exceptionType == other.exceptionType && handlerAddress == other.handlerAddress;
1038        }
1039    }
1040}
1041