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