Home | History | Annotate | Download | only in dexlib
      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 
     29 package org.jf.dexlib;
     30 
     31 import org.jf.dexlib.Code.Format.*;
     32 import org.jf.dexlib.Code.*;
     33 import org.jf.dexlib.Debug.DebugInstructionIterator;
     34 import org.jf.dexlib.Debug.DebugOpcode;
     35 import org.jf.dexlib.Util.*;
     36 
     37 import java.util.ArrayList;
     38 import java.util.List;
     39 
     40 public 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