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.Instruction20t;
     32 import org.jf.dexlib.Code.Format.Instruction21c;
     33 import org.jf.dexlib.Code.Format.Instruction30t;
     34 import org.jf.dexlib.Code.Format.Instruction31c;
     35 import org.jf.dexlib.Code.*;
     36 import org.jf.dexlib.Debug.DebugInstructionIterator;
     37 import org.jf.dexlib.Debug.DebugOpcode;
     38 import org.jf.dexlib.Util.*;
     39 
     40 import java.util.ArrayList;
     41 import java.util.List;
     42 
     43 public 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