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