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