content: /*
   * [The "BSD licence"]
   * Copyright (c) 2010 Ben Gruver (JesusFreke)
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in the
   *    documentation and/or other materials provided with the distribution.
   * 3. The name of the author may not be used to endorse or promote products
   *    derived from this software without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
   * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
   * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  
  package org.jf.dexlib;
  
  import org.jf.dexlib.Code.Format.Instruction20t;
  import org.jf.dexlib.Code.Format.Instruction21c;
  import org.jf.dexlib.Code.Format.Instruction30t;
  import org.jf.dexlib.Code.Format.Instruction31c;
  import org.jf.dexlib.Code.*;
  import org.jf.dexlib.Debug.DebugInstructionIterator;
  import org.jf.dexlib.Debug.DebugOpcode;
  import org.jf.dexlib.Util.*;
  
  import java.util.ArrayList;
  import java.util.List;
  
  public class CodeItem extends Item<CodeItem> {
      public int registerCount;
      public int inWords;
      public int outWords;
      public DebugInfoItem debugInfo;
      public Instruction[] instructions;
      public TryItem[] tries;
      public EncodedCatchHandler[] encodedCatchHandlers;
      private boolean update;
  
      private ClassDataItem.EncodedMethod parent;
  
      /**
       * Creates a new uninitialized <code>CodeItem</code>
       * @param dexFile The <code>DexFile</code> that this item belongs to
       */
      public CodeItem(DexFile dexFile) {
          super(dexFile);
      }
  
      /**
       * Creates a new <code>CodeItem</code> with the given values.
       * @param dexFile The <code>DexFile</code> that this item belongs to
       * @param registerCount the number of registers that the method containing this code uses
       * @param inWords the number of 2-byte words that the parameters to the method containing this code take
       * @param outWords the maximum number of 2-byte words for the arguments of any method call in this code
       * @param debugInfo the debug information for this code/method
       * @param instructions the instructions for this code item
       * @param tries an array of the tries defined for this code/method
       * @param encodedCatchHandlers an array of the exception handlers defined for this code/method
       */
      private CodeItem(DexFile dexFile,
                      int registerCount,
                      int inWords,
                      int outWords,
                      DebugInfoItem debugInfo,
                      Instruction[] instructions,
                      TryItem[] tries,
                      EncodedCatchHandler[] encodedCatchHandlers) {
          super(dexFile);
  
          this.registerCount = registerCount;
          this.inWords = inWords;
          this.outWords = outWords;
          this.debugInfo = debugInfo;
          if (debugInfo != null) {
              debugInfo.setParent(this);
          }
  
          this.instructions = instructions;
          this.tries = tries;
          this.encodedCatchHandlers = encodedCatchHandlers;
      }
  
      /**
       * Returns a new <code>CodeItem</code> with the given values.
       * @param dexFile The <code>DexFile</code> that this item belongs to
       * @param registerCount the number of registers that the method containing this code uses
       * @param inWords the number of 2-byte words that the parameters to the method containing this code take
       * @param outWords the maximum number of 2-byte words for the arguments of any method call in this code
       * @param debugInfo the debug information for this code/method
       * @param instructions the instructions for this code item
       * @param tries a list of the tries defined for this code/method or null if none
       * @param encodedCatchHandlers a list of the exception handlers defined for this code/method or null if none
       * @return a new <code>CodeItem</code> with the given values.
       */
      public static CodeItem internCodeItem(DexFile dexFile,
                      int registerCount,
                      int inWords,
                      int outWords,
                      DebugInfoItem debugInfo,
                      List<Instruction> instructions,
                      List<TryItem> tries,
                      List<EncodedCatchHandler> encodedCatchHandlers) {
          TryItem[] triesArray = null;
          EncodedCatchHandler[] encodedCatchHandlersArray = null;
          Instruction[] instructionsArray = null;
  
          if (tries != null && tries.size() > 0) {
              triesArray = new TryItem[tries.size()];
              tries.toArray(triesArray);
          }
  
          if (encodedCatchHandlers != null && encodedCatchHandlers.size() > 0) {
              encodedCatchHandlersArray = new EncodedCatchHandler[encodedCatchHandlers.size()];
              encodedCatchHandlers.toArray(encodedCatchHandlersArray);
          }
  
          if (instructions != null && instructions.size() > 0) {
              instructionsArray = new Instruction[instructions.size()];
              instructions.toArray(instructionsArray);
          }
  
          CodeItem codeItem = new CodeItem(dexFile, registerCount, inWords, outWords, debugInfo, instructionsArray,
                  triesArray, encodedCatchHandlersArray);
          return dexFile.CodeItemsSection.intern(codeItem);
      }
  
      
      
      
      public CodeItem internCodeItem(DexFile dexFile) {
          /*
             if(update){
          //  System.out.println("update");
          CodeItem codeItem = new CodeItem(
          dexFile,
          registerCount,
          inWords,
          outWords,
          /*debugInfonull,
          instructions,
          tries,
          encodedCatchHandlers
          );
          return dexFile.CodeItemsSection.intern(codeItem);
             }
             */
          int parameterRegisterCount=parent.method.getPrototype().getParameterRegisterCount();
  
          if((parent.accessFlags&AccessFlags.STATIC.getValue()) == 0){
              parameterRegisterCount++;
          }
          inWords=parameterRegisterCount;
  
  
          EncodedCatchHandler[] encodedCatchHandlersArray = null;
          List<CodeItem.EncodedCatchHandler> encodedCatchHandlers=new ArrayList<CodeItem.EncodedCatchHandler>(3);
          TryItem[] tries=copyTryItems(
                  dexFile,
                  encodedCatchHandlers
                  );
          if (encodedCatchHandlers.size() > 0) {
              encodedCatchHandlersArray = new EncodedCatchHandler[encodedCatchHandlers.size()];
              encodedCatchHandlers.toArray(encodedCatchHandlersArray);
          }
  
          CodeItem codeItem = new CodeItem(
                  dexFile,
                  registerCount,
                  inWords,
                  outWords,
                  /*debugInfo*/null,
                  /*instructions*/copyInstruction(
                      dexFile),
                  tries,
                  encodedCatchHandlersArray
                  );
          return dexFile.CodeItemsSection.intern(codeItem);
      }
  
      public Instruction[] copyInstruction(DexFile dexFile){
          Instruction[] instructions=this.instructions;
         // Instruction[] out=new Instruction[instructions.length];
          for(int i=0,len=instructions.length;i<len;i++){
              Instruction instruction=instructions[i];
              switch(instruction.getFormat()){
                  case Format21c:
                  case Format31c:
                  case Format22c:
                  case Format35c:
                  case Format35s:
                  case Format3rc:
                      InstructionWithReference ref=(InstructionWithReference) instruction;
                      switch (instruction.opcode.referenceType) {
                          case field:
                              FieldIdItem field=(FieldIdItem) ref.getReferencedItem();
                              ref.setReferencedItem(
                                      field.internFieldIdItem(
                                          dexFile)
                                      );
                              //                out[i]=instruction;
                              break;
                          case method:
                              MethodIdItem method=(MethodIdItem) ref.getReferencedItem();
                              ref.setReferencedItem(
                                      method.internMethodIdItem(
                                          dexFile)
                                      );
                              //              out[i]=instruction;
                              break;
                          case type:
                              TypeIdItem type=(TypeIdItem) ref.getReferencedItem();
                              ref.setReferencedItem(
                                      TypeIdItem.internTypeIdItem(
                                          dexFile,
                                          type.getTypeDescriptor())
                                      );
                              //            out[i]=instruction;
                              break;
                          case string:
                              StringIdItem string=(StringIdItem)ref.getReferencedItem();
                              string=StringIdItem.internStringIdItem(
                                      dexFile,
                                      string.getStringValue()
                                      );
                              ref.setReferencedItem(string);
                              //          out[i]=instruction;
                              break;
  
                      }
                      //out[i]=instruction;
                      break;
              }
          }
          return instructions;
      }
  
      public static EncodedTypeAddrPair copyEncodedTypeAddrPair(DexFile dexFile,EncodedTypeAddrPair pair){
          return new EncodedTypeAddrPair(
                  TypeIdItem.internTypeIdItem(
                      dexFile,
                      pair.exceptionType.getTypeDescriptor()),
                  pair.getHandlerAddress()
                  );
      }
  
      public static EncodedCatchHandler copyEncodedCatchHandler(DexFile dexFile,EncodedCatchHandler handler){
          if(handler.handlers ==null)
              return null;
  
          EncodedTypeAddrPair[] out=new EncodedTypeAddrPair[handler.handlers.length];
          for(int i=0;i<handler.handlers.length;i++){
              out[i]=copyEncodedTypeAddrPair(
                      dexFile,
                      handler.handlers[i]
                      );
          }
          return new CodeItem.EncodedCatchHandler(
                  out,
                  handler.getCatchAllHandlerAddress()
                  );
      }
  
      public static TryItem copyTryItem(DexFile dexFile,TryItem tryItem,List<EncodedCatchHandler> handlers){
  
                  EncodedCatchHandler handler=copyEncodedCatchHandler(dexFile,tryItem.encodedCatchHandler);
                  handlers.add(handler);
  
          return new CodeItem.TryItem(
                  tryItem.getStartCodeAddress(),
                  tryItem.getTryLength(),
                  handler
                  );
      }
  
      public TryItem[] copyTryItems(DexFile dexFile,List<CodeItem.EncodedCatchHandler> handlers){
          if(tries == null)
              return null;
  
          TryItem[] out=new TryItem[tries.length];
          for(int i=0;i<tries.length;i++){
              out[i]=copyTryItem(
                      dexFile,
                      tries[i],
                      handlers
                      );
          }
          return out;
      }
  
      
  
      /** {@inheritDoc} */
      protected void readItem(Input in, ReadContext readContext) {
          this.registerCount = in.readShort();
          this.inWords = in.readShort();
          this.outWords = in.readShort();
          int triesCount = in.readShort();
          this.debugInfo = (DebugInfoItem)readContext.getOptionalOffsettedItemByOffset(ItemType.TYPE_DEBUG_INFO_ITEM,
                  in.readInt());
          if (this.debugInfo != null) {
              this.debugInfo.setParent(this);
          }
  
          int instructionCount = in.readInt();
  
          final ArrayList<Instruction> instructionList = new ArrayList<Instruction>();
  
          byte[] encodedInstructions = in.readBytes(instructionCount * 2);
          InstructionIterator.IterateInstructions(dexFile, encodedInstructions,
                  new InstructionIterator.ProcessInstructionDelegate() {
                      public void ProcessInstruction(int codeAddress, Instruction instruction) {
                          instructionList.add(instruction);
                      }
                  });
  
          this.instructions = new Instruction[instructionList.size()];
          instructionList.toArray(instructions);
  
          if (triesCount > 0) {
              in.alignTo(4);
  
              //we need to read in the catch handlers first, so save the offset to the try items for future reference
              int triesOffset = in.getCursor();
              in.setCursor(triesOffset + 8 * triesCount);
  
              //read in the encoded catch handlers
              int encodedHandlerStart = in.getCursor();
              int handlerCount = in.readUnsignedLeb128();
              SparseArray<EncodedCatchHandler> handlerMap = new SparseArray<EncodedCatchHandler>(handlerCount);
              encodedCatchHandlers = new EncodedCatchHandler[handlerCount];
              for (int i=0; i<handlerCount; i++) {
                  try {
                      int position = in.getCursor() - encodedHandlerStart;
                      encodedCatchHandlers[i] = new EncodedCatchHandler(dexFile, in);
                      handlerMap.append(position, encodedCatchHandlers[i]);
                  } catch (Exception ex) {
                      throw ExceptionWithContext.withContext(ex, "Error while reading EncodedCatchHandler at index " + i);
                  }
              }
              int codeItemEnd = in.getCursor();
  
              //now go back and read the tries
              in.setCursor(triesOffset);
              tries = new TryItem[triesCount];
              for (int i=0; i<triesCount; i++) {
                  try {
                      tries[i] = new TryItem(in, handlerMap);
                  } catch (Exception ex) {
                      throw ExceptionWithContext.withContext(ex, "Error while reading TryItem at index " + i);
                  }
              }
  
              //and now back to the end of the code item
              in.setCursor(codeItemEnd);
          }
      }
  
      /** {@inheritDoc} */
      protected int placeItem(int offset) {
          offset += 16 + getInstructionsLength() * 2;
  
          if (tries != null && tries.length > 0) {
              offset = AlignmentUtils.alignOffset(offset, 4);
  
              offset += tries.length * 8;
              int encodedCatchHandlerBaseOffset = offset;
              offset += Leb128Utils.unsignedLeb128Size(encodedCatchHandlers.length);
              for (EncodedCatchHandler encodedCatchHandler: encodedCatchHandlers) {
                  offset = encodedCatchHandler.place(offset, encodedCatchHandlerBaseOffset);
              }
          }
          return offset;
      }
  
      /** {@inheritDoc} */
      protected void writeItem(final AnnotatedOutput out) {
          int instructionsLength = getInstructionsLength();
  
          if (out.annotates()) {
              out.annotate(0, parent.method.getMethodString());
              out.annotate(2, "registers_size: 0x" + Integer.toHexString(registerCount) + " (" + registerCount + ")");
              out.annotate(2, "ins_size: 0x" + Integer.toHexString(inWords) + " (" + inWords + ")");
              out.annotate(2, "outs_size: 0x" + Integer.toHexString(outWords) + " (" + outWords + ")");
              int triesLength = tries==null?0:tries.length;
              out.annotate(2, "tries_size: 0x" + Integer.toHexString(triesLength) + " (" + triesLength + ")");
              if (debugInfo == null) {
                  out.annotate(4, "debug_info_off:");
              } else {
                  out.annotate(4, "debug_info_off: 0x" + debugInfo.getOffset());
              }
              out.annotate(4, "insns_size: 0x" + Integer.toHexString(instructionsLength) + " (" +
                      (instructionsLength) + ")");
          }
  
          out.writeShort(registerCount);
          out.writeShort(inWords);
          out.writeShort(outWords);
          if (tries == null) {
              out.writeShort(0);
          } else {
              out.writeShort(tries.length);
          }
          if (debugInfo == null) {
              out.writeInt(0);
          } else {
              out.writeInt(debugInfo.getOffset());
          }
  
          out.writeInt(instructionsLength);
  
          Instruction[] instructions=this.instructions;
          int currentCodeAddress = 0;
          for (Instruction instruction: instructions) {
              currentCodeAddress = instruction.write(out, currentCodeAddress);
          }
  
          if (tries != null && tries.length > 0) {
              if (out.annotates()) {
                  if ((currentCodeAddress % 2) != 0) {
                      out.annotate("padding");
                      out.writeShort(0);
                  }
  
                  int index = 0;
                  for (TryItem tryItem: tries) {
                      out.annotate(0, "[0x" + Integer.toHexString(index++) + "] try_item");
                      out.indent();
                      tryItem.writeTo(out);
                      out.deindent();
                  }
  
                  out.annotate("handler_count: 0x" + Integer.toHexString(encodedCatchHandlers.length) + "(" +
                          encodedCatchHandlers.length + ")");
                  out.writeUnsignedLeb128(encodedCatchHandlers.length);
  
                  index = 0;
                  for (EncodedCatchHandler encodedCatchHandler: encodedCatchHandlers) {
                      out.annotate(0, "[" + Integer.toHexString(index++) + "] encoded_catch_handler");
                      out.indent();
                      encodedCatchHandler.writeTo(out);
                      out.deindent();
                  }
              } else {
                  if ((currentCodeAddress % 2) != 0) {
                      out.writeShort(0);
                  }
  
                  for (TryItem tryItem: tries) {
                      tryItem.writeTo(out);
                  }
  
                  out.writeUnsignedLeb128(encodedCatchHandlers.length);
  
                  for (EncodedCatchHandler encodedCatchHandler: encodedCatchHandlers) {
                      encodedCatchHandler.writeTo(out);
                  }
              }
          }
      }
  
      /** {@inheritDoc} */
      public ItemType getItemType() {
          return ItemType.TYPE_CODE_ITEM;
      }
  
      /** {@inheritDoc} */
      public String getConciseIdentity() {
          if (this.parent == null) {
              return "code_item @0x" + Integer.toHexString(getOffset());
          }
          return "code_item @0x" + Integer.toHexString(getOffset()) + " (" + parent.method.getMethodString() + ")";
      }
  
      /** {@inheritDoc} */
      public int compareTo(CodeItem other) {
          if (parent == null) {
              if (other.parent == null) {
                  return 0;
              }
              return -1;
          }
          if (other.parent == null) {
              return 1;
          }
          return parent.method.compareTo(other.parent.method);
      }
  
      /**
       * @return the register count
       */
      public int getRegisterCount() {
          return registerCount;
      }
  
      /**
       * @return an array of the instructions in this code item
       */
      public Instruction[] getInstructions() {
          return instructions;
      }
  
      /**
       * @return an array of the <code>TryItem</code> objects in this <code>CodeItem</code>
       */
      public TryItem[] getTries() {
          return tries;
      }
  
      /**
       * @return an array of the <code>EncodedCatchHandler</code> objects in this <code>CodeItem</code>
       */
      public EncodedCatchHandler[] getHandlers() {
          return encodedCatchHandlers;
      }
  
      /**
       * @return the <code>DebugInfoItem</code> associated with this <code>CodeItem</code>
       */
      public DebugInfoItem getDebugInfo() {
          return debugInfo;
      }
  
      /**
       * Sets the <code>MethodIdItem</code> of the method that this <code>CodeItem</code> is associated with
       * @param encodedMethod the <code>EncodedMethod</code> of the method that this <code>CodeItem</code> is associated
       * with
       */
      protected void setParent(ClassDataItem.EncodedMethod encodedMethod) {
          this.parent = encodedMethod;
      }
  
      /**
       * @return the MethodIdItem of the method that this CodeItem belongs to
       */
      public ClassDataItem.EncodedMethod getParent() {
          return parent;
      }
  
      /**
       * Used by OdexUtil to update this <code>CodeItem</code> with a deodexed version of the instructions
       * @param newInstructions the new instructions to use for this code item
       */
      public void updateCode(Instruction[] newInstructions) {
          update=true;
          this.instructions = newInstructions;
      }
  
      /**
       * @return The length of the instructions in this CodeItem, in 2-byte code blocks
       */
      private int getInstructionsLength() {
  
          Instruction[] instructions=this.instructions;
          int currentCodeAddress = 0;
          for (Instruction instruction: instructions) {
              currentCodeAddress += instruction.getSize(currentCodeAddress);
          }
          return currentCodeAddress;
      }
  
      /**
       * Go through the instructions and perform any of the following fixes that are applicable
       * - Replace const-string instruction with const-string/jumbo, when the string index is too big
       * - Replace goto and goto/16 with a larger version of goto, when the target is too far away
       * 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
       * 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
       *
       * The above fixes are applied iteratively, until no more fixes have been performed
       */
      public void fixInstructions(boolean fixStringConst, boolean fixGoto) {
          try {
              boolean didSomething = false;
  
              do
              {
                  didSomething = false;
  
                  int currentCodeAddress = 0;
                  for (int i=0; i<instructions.length; i++) {
                      Instruction instruction = instructions[i];
  
                      try {
                          if (fixGoto && instruction.opcode == Opcode.GOTO) {
                              int codeAddress = ((OffsetInstruction)instruction).getTargetAddressOffset();
  
                              if (((byte) codeAddress) != codeAddress) {
                                  //the address doesn't fit within a byte, we need to upgrade to a goto/16 or goto/32
  
                                  if ((short) codeAddress == codeAddress) {
                                      //the address fits in a short, so upgrade to a goto/16
                                      replaceInstructionAtAddress(currentCodeAddress,
                                              new Instruction20t(Opcode.GOTO_16, codeAddress));
                                  }
                                  else {
                                      //The address won't fit into a short, we have to upgrade to a goto/32
                                      replaceInstructionAtAddress(currentCodeAddress,
                                              new Instruction30t(Opcode.GOTO_32, codeAddress));
                                  }
                                  didSomething = true;
                                  break;
                              }
                          } else if (fixGoto && instruction.opcode == Opcode.GOTO_16) {
                              int codeAddress = ((OffsetInstruction)instruction).getTargetAddressOffset();
  
                              if (((short) codeAddress) != codeAddress) {
                                  //the address doesn't fit within a short, we need to upgrade to a goto/32
                                  replaceInstructionAtAddress(currentCodeAddress,
                                          new Instruction30t(Opcode.GOTO_32, codeAddress));
                                  didSomething = true;
                                  break;
                              }
                          } else if (fixStringConst && instruction.opcode == Opcode.CONST_STRING) {
                              Instruction21c constStringInstruction = (Instruction21c)instruction;
                              if (constStringInstruction.getReferencedItem().getIndex() > 0xFFFF) {
                                  replaceInstructionAtAddress(currentCodeAddress,
                                          new Instruction31c(Opcode.CONST_STRING_JUMBO,
                                          (short)constStringInstruction.getRegisterA(),
                                          constStringInstruction.getReferencedItem()));
                                  didSomething = true;
                                  break;
                              }
                          }
  
                          currentCodeAddress += instruction.getSize(currentCodeAddress);
                      } catch (Exception ex) {
                          throw ExceptionWithContext.withContext(ex, "Error while attempting to fix " +
                                  instruction.opcode.name + " instruction at address " + currentCodeAddress);
                      }
                  }
              }while(didSomething);
          } catch (Exception ex) {
              throw this.addExceptionContext(ex);
          }
      }
  
      private void replaceInstructionAtAddress(int codeAddress, Instruction replacementInstruction) {
          Instruction originalInstruction = null;
  
          int[] originalInstructionCodeAddresses = new int[instructions.length+1];
          SparseIntArray originalSwitchAddressByOriginalSwitchDataAddress = new SparseIntArray();
  
          int currentCodeAddress = 0;
          int instructionIndex = 0;
          int i;
          for (i=0; i<instructions.length; i++) {
              Instruction instruction = instructions[i];
  
              if (currentCodeAddress == codeAddress) {
                  originalInstruction = instruction;
                  instructionIndex = i;
              }
  
              if (instruction.opcode == Opcode.PACKED_SWITCH || instruction.opcode == Opcode.SPARSE_SWITCH) {
                  OffsetInstruction offsetInstruction = (OffsetInstruction)instruction;
  
                  int switchDataAddress = currentCodeAddress + offsetInstruction.getTargetAddressOffset();
                  if (originalSwitchAddressByOriginalSwitchDataAddress.indexOfKey(switchDataAddress) < 0) {
                      originalSwitchAddressByOriginalSwitchDataAddress.put(switchDataAddress, currentCodeAddress);
                  }
              }
  
              originalInstructionCodeAddresses[i] = currentCodeAddress;
              currentCodeAddress += instruction.getSize(currentCodeAddress);
          }
          //add the address just past the end of the last instruction, to help when fixing up try blocks that end
          //at the end of the method
          originalInstructionCodeAddresses[i] = currentCodeAddress;
  
          if (originalInstruction == null) {
              throw new RuntimeException("There is no instruction at address " + codeAddress);
          }
  
          instructions[instructionIndex] = replacementInstruction;
  
          //if we're replacing the instruction with one of the same size, we don't have to worry about fixing
          //up any address
          if (originalInstruction.getSize(codeAddress) == replacementInstruction.getSize(codeAddress)) {
              return;
          }
  
          final SparseIntArray originalAddressByNewAddress = new SparseIntArray();
          final SparseIntArray newAddressByOriginalAddress = new SparseIntArray();
  
          currentCodeAddress = 0;
          for (i=0; i<instructions.length; i++) {
              Instruction instruction = instructions[i];
  
              int originalAddress = originalInstructionCodeAddresses[i];
              originalAddressByNewAddress.append(currentCodeAddress, originalAddress);
              newAddressByOriginalAddress.append(originalAddress, currentCodeAddress);
  
              currentCodeAddress += instruction.getSize(currentCodeAddress);
          }
  
          //add the address just past the end of the last instruction, to help when fixing up try blocks that end
          //at the end of the method
          originalAddressByNewAddress.append(currentCodeAddress, originalInstructionCodeAddresses[i]);
          newAddressByOriginalAddress.append(originalInstructionCodeAddresses[i], currentCodeAddress);
  
          //update any "offset" instructions, or switch data instructions
          currentCodeAddress = 0;
          for (i=0; i<instructions.length; i++) {
              Instruction instruction = instructions[i];
  
              if (instruction instanceof OffsetInstruction) {
                  OffsetInstruction offsetInstruction = (OffsetInstruction)instruction;
  
                  assert originalAddressByNewAddress.indexOfKey(currentCodeAddress) >= 0;
                  int originalAddress = originalAddressByNewAddress.get(currentCodeAddress);
  
                  int originalInstructionTarget = originalAddress + offsetInstruction.getTargetAddressOffset();
  
                  assert newAddressByOriginalAddress.indexOfKey(originalInstructionTarget) >= 0;
                  int newInstructionTarget = newAddressByOriginalAddress.get(originalInstructionTarget);
  
                  int newCodeAddress = (newInstructionTarget - currentCodeAddress);
  
                  if (newCodeAddress != offsetInstruction.getTargetAddressOffset()) {
                      offsetInstruction.updateTargetAddressOffset(newCodeAddress);
                  }
              } else if (instruction instanceof MultiOffsetInstruction) {
                  MultiOffsetInstruction multiOffsetInstruction = (MultiOffsetInstruction)instruction;
  
                  assert originalAddressByNewAddress.indexOfKey(currentCodeAddress) >= 0;
                  int originalDataAddress = originalAddressByNewAddress.get(currentCodeAddress);
  
                  int originalSwitchAddress =
                          originalSwitchAddressByOriginalSwitchDataAddress.get(originalDataAddress, -1);
                  if (originalSwitchAddress == -1) {
                      //TODO: maybe we could just remove the unreferenced switch data?
                      throw new RuntimeException("This method contains an unreferenced switch data block at address " +
                              + currentCodeAddress + " and can't be automatically fixed.");
                  }
  
                  assert newAddressByOriginalAddress.indexOfKey(originalSwitchAddress) >= 0;
                  int newSwitchAddress = newAddressByOriginalAddress.get(originalSwitchAddress);
  
                  int[] targets = multiOffsetInstruction.getTargets();
                  for (int t=0; t<targets.length; t++) {
                      int originalTargetCodeAddress = originalSwitchAddress + targets[t];
                      assert newAddressByOriginalAddress.indexOfKey(originalTargetCodeAddress) >= 0;
                      int newTargetCodeAddress = newAddressByOriginalAddress.get(originalTargetCodeAddress);
                      int newCodeAddress = newTargetCodeAddress - newSwitchAddress;
                      if (newCodeAddress != targets[t]) {
                          multiOffsetInstruction.updateTarget(t, newCodeAddress);
                      }
                  }
              }
              currentCodeAddress += instruction.getSize(currentCodeAddress);
          }
  
          if (debugInfo != null) {
              final byte[] encodedDebugInfo = debugInfo.getEncodedDebugInfo();
  
              ByteArrayInput debugInput = new ByteArrayInput(encodedDebugInfo);
  
              DebugInstructionFixer debugInstructionFixer = new DebugInstructionFixer(encodedDebugInfo,
                  newAddressByOriginalAddress);
              DebugInstructionIterator.IterateInstructions(debugInput, debugInstructionFixer);
  
              if (debugInstructionFixer.result != null) {
                  debugInfo.setEncodedDebugInfo(debugInstructionFixer.result);
              }
          }
  
          if (encodedCatchHandlers != null) {
              for (EncodedCatchHandler encodedCatchHandler: encodedCatchHandlers) {
                  if (encodedCatchHandler.catchAllHandlerAddress != -1) {
                      assert newAddressByOriginalAddress.indexOfKey(encodedCatchHandler.catchAllHandlerAddress) >= 0;
                      encodedCatchHandler.catchAllHandlerAddress =
                              newAddressByOriginalAddress.get(encodedCatchHandler.catchAllHandlerAddress);
                  }
  
                  for (EncodedTypeAddrPair handler: encodedCatchHandler.handlers) {
                      assert newAddressByOriginalAddress.indexOfKey(handler.handlerAddress) >= 0;
                      handler.handlerAddress = newAddressByOriginalAddress.get(handler.handlerAddress);
                  }
              }
          }
  
          if (this.tries != null) {
              for (TryItem tryItem: tries) {
                  int startAddress = tryItem.startCodeAddress;
                  int endAddress = tryItem.startCodeAddress + tryItem.tryLength;
  
                  assert newAddressByOriginalAddress.indexOfKey(startAddress) >= 0;
                  tryItem.startCodeAddress = newAddressByOriginalAddress.get(startAddress);
  
                  assert newAddressByOriginalAddress.indexOfKey(endAddress) >= 0;
                  tryItem.tryLength = newAddressByOriginalAddress.get(endAddress) - tryItem.startCodeAddress;
              }
          }
      }
  
      private class DebugInstructionFixer extends DebugInstructionIterator.ProcessRawDebugInstructionDelegate {
          private int currentCodeAddress = 0;
          private SparseIntArray newAddressByOriginalAddress;
          private final byte[] originalEncodedDebugInfo;
          public byte[] result = null;
  
          public DebugInstructionFixer(byte[] originalEncodedDebugInfo, SparseIntArray newAddressByOriginalAddress) {
              this.newAddressByOriginalAddress = newAddressByOriginalAddress;
              this.originalEncodedDebugInfo = originalEncodedDebugInfo;
          }
  
  
          @Override
          public void ProcessAdvancePC(int startDebugOffset, int debugInstructionLength, int codeAddressDelta) {
              currentCodeAddress += codeAddressDelta;
  
              if (result != null) {
                  return;
              }
  
              int newCodeAddress = newAddressByOriginalAddress.get(currentCodeAddress, -1);
  
              //The address might not point to an actual instruction in some cases, for example, if an AdvancePC
              //instruction was inserted just before a "special" instruction, to fix up the addresses for a previous
              //instruction replacement.
              //In this case, it should be safe to skip, because there will be another AdvancePC/SpecialOpcode that will
              //bump up the address to point to a valid instruction before anything (line/local/etc.) is emitted
              if (newCodeAddress == -1) {
                  return;
              }
  
              if (newCodeAddress != currentCodeAddress) {
                  int newCodeAddressDelta = newCodeAddress - (currentCodeAddress - codeAddressDelta);
                  assert newCodeAddressDelta > 0;
                  int codeAddressDeltaLeb128Size = Leb128Utils.unsignedLeb128Size(newCodeAddressDelta);
  
                  //if the length of the new code address delta is the same, we can use the existing buffer
                  if (codeAddressDeltaLeb128Size + 1 == debugInstructionLength) {
                      result = originalEncodedDebugInfo;
                      Leb128Utils.writeUnsignedLeb128(newCodeAddressDelta, result, startDebugOffset+1);
                  } else {
                      //The length of the new code address delta is different, so create a new buffer with enough
                      //additional space to accomodate the new code address delta value.
                      result = new byte[originalEncodedDebugInfo.length + codeAddressDeltaLeb128Size -
                              (debugInstructionLength - 1)];
  
                      System.arraycopy(originalEncodedDebugInfo, 0, result, 0, startDebugOffset);
  
                      result[startDebugOffset] = DebugOpcode.DBG_ADVANCE_PC.value;
                      Leb128Utils.writeUnsignedLeb128(newCodeAddressDelta, result, startDebugOffset+1);
  
                      System.arraycopy(originalEncodedDebugInfo, startDebugOffset + debugInstructionLength, result,
                              startDebugOffset + codeAddressDeltaLeb128Size + 1,
                              originalEncodedDebugInfo.length - (startDebugOffset + codeAddressDeltaLeb128Size + 1));
                  }
              }
          }
  
          @Override
          public void ProcessSpecialOpcode(int startDebugOffset, int debugOpcode, int lineDelta,
                                           int codeAddressDelta) {
              currentCodeAddress += codeAddressDelta;
              if (result != null) {
                  return;
              }
  
              int newCodeAddress = newAddressByOriginalAddress.get(currentCodeAddress, -1);
              assert newCodeAddress != -1;
  
              if (newCodeAddress != currentCodeAddress) {
                  int newCodeAddressDelta = newCodeAddress - (currentCodeAddress - codeAddressDelta);
                  assert newCodeAddressDelta > 0;
  
                  //if the new code address delta won't fit in the special opcode, we need to insert
                  //an additional DBG_ADVANCE_PC opcode
                  if (lineDelta < 2 && newCodeAddressDelta > 16 || lineDelta > 1 && newCodeAddressDelta > 15) {
                      int additionalCodeAddressDelta = newCodeAddress - currentCodeAddress;
                      int additionalCodeAddressDeltaLeb128Size = Leb128Utils.signedLeb128Size(additionalCodeAddressDelta);
  
                      //create a new buffer with enough additional space for the new opcode
                      result = new byte[originalEncodedDebugInfo.length + additionalCodeAddressDeltaLeb128Size + 1];
  
                      System.arraycopy(originalEncodedDebugInfo, 0, result, 0, startDebugOffset);
                      result[startDebugOffset] = 0x01; //DBG_ADVANCE_PC
                      Leb128Utils.writeUnsignedLeb128(additionalCodeAddressDelta, result, startDebugOffset+1);
                      System.arraycopy(originalEncodedDebugInfo, startDebugOffset, result,
                              startDebugOffset+additionalCodeAddressDeltaLeb128Size+1,
                              result.length - (startDebugOffset+additionalCodeAddressDeltaLeb128Size+1));
                  } else {
                      result = originalEncodedDebugInfo;
                      result[startDebugOffset] = DebugInfoBuilder.calculateSpecialOpcode(lineDelta,
                              newCodeAddressDelta);
                  }
              }
          }
      }
  
      public static class TryItem {
          /**
           * The address (in 2-byte words) within the code where the try block starts
           */
          private int startCodeAddress;
  
          /**
           * The number of 2-byte words that the try block covers
           */
          private int tryLength;
  
          /**
           * The associated exception handler
           */
          public final EncodedCatchHandler encodedCatchHandler;
  
          /**
           * Construct a new <code>TryItem</code> with the given values
           * @param startCodeAddress the code address within the code where the try block starts
           * @param tryLength the number of code blocks that the try block covers
           * @param encodedCatchHandler the associated exception handler
           */
          public TryItem(int startCodeAddress, int tryLength, EncodedCatchHandler encodedCatchHandler) {
              this.startCodeAddress = startCodeAddress;
              this.tryLength = tryLength;
              this.encodedCatchHandler = encodedCatchHandler;
          }
  
          /**
           * This is used internally to construct a new <code>TryItem</code> while reading in a <code>DexFile</code>
           * @param in the Input object to read the <code>TryItem</code> from
           * @param encodedCatchHandlers a SparseArray of the EncodedCatchHandlers for this <code>CodeItem</code>. The
           * key should be the offset of the EncodedCatchHandler from the beginning of the encoded_catch_handler_list
           * structure.
           */
          private TryItem(Input in, SparseArray<EncodedCatchHandler> encodedCatchHandlers) {
              startCodeAddress = in.readInt();
              tryLength = in.readShort();
  
              encodedCatchHandler = encodedCatchHandlers.get(in.readShort());
              if (encodedCatchHandler == null) {
                  throw new RuntimeException("Could not find the EncodedCatchHandler referenced by this TryItem");
              }
          }
  
          /**
           * Writes the <code>TryItem</code> to the given <code>AnnotatedOutput</code> object
           * @param out the <code>AnnotatedOutput</code> object to write to
           */
          private void writeTo(AnnotatedOutput out) {
              if (out.annotates()) {
                  out.annotate(4, "start_addr: 0x" + Integer.toHexString(startCodeAddress));
                  out.annotate(2, "try_length: 0x" + Integer.toHexString(tryLength) + " (" + tryLength +
                          ")");
                  out.annotate(2, "handler_off: 0x" + Integer.toHexString(encodedCatchHandler.getOffsetInList()));
              }
  
              out.writeInt(startCodeAddress);
              out.writeShort(tryLength);
              out.writeShort(encodedCatchHandler.getOffsetInList());
          }
  
          /**
           * @return The address (in 2-byte words) within the code where the try block starts
           */
          public int getStartCodeAddress() {
              return startCodeAddress;
          }
  
          /**
           * @return The number of code blocks that the try block covers
           */
          public int getTryLength() {
              return tryLength;
          }
      }
  
      public static class EncodedCatchHandler {
          /**
           * An array of the individual exception handlers
           */
          public final EncodedTypeAddrPair[] handlers;
  
          /**
           * The address within the code (in 2-byte words) for the catch all handler, or -1 if there is no catch all
           * handler
           */
          private int catchAllHandlerAddress;
  
          private int baseOffset;
          private int offset;
  
          /**
           * Constructs a new <code>EncodedCatchHandler</code> with the given values
           * @param handlers an array of the individual exception handlers
           * @param catchAllHandlerAddress The address within the code (in 2-byte words) for the catch all handler, or -1
           * if there is no catch all handler
           */
          public EncodedCatchHandler(EncodedTypeAddrPair[] handlers, int catchAllHandlerAddress) {
              this.handlers = handlers;
              this.catchAllHandlerAddress = catchAllHandlerAddress;
          }
  
          /**
           * This is used internally to construct a new <code>EncodedCatchHandler</code> while reading in a
           * <code>DexFile</code>
           * @param dexFile the <code>DexFile</code> that is being read in
           * @param in the Input object to read the <code>EncodedCatchHandler</code> from
           */
          private EncodedCatchHandler(DexFile dexFile, Input in) {
              int handlerCount = in.readSignedLeb128();
  
              if (handlerCount < 0) {
                  handlers = new EncodedTypeAddrPair[-1 * handlerCount];
              } else {
                  handlers = new EncodedTypeAddrPair[handlerCount];
              }
  
              for (int i=0; i<handlers.length; i++) {
                  try {
                      handlers[i] = new EncodedTypeAddrPair(dexFile, in);
                  } catch (Exception ex) {
                      throw ExceptionWithContext.withContext(ex, "Error while reading EncodedTypeAddrPair at index " + i);
                  }
              }
  
              if (handlerCount <= 0) {
                  catchAllHandlerAddress = in.readUnsignedLeb128();
              } else {
                  catchAllHandlerAddress = -1;
              }
          }
  
          /**
           * @return the "Catch All" handler address for this <code>EncodedCatchHandler</code>, or -1 if there
           * is no "Catch All" handler
           */
          public int getCatchAllHandlerAddress() {
              return catchAllHandlerAddress;
          }
  
          /**
           * @return the offset of this <code>EncodedCatchHandler</code> from the beginning of the
           * encoded_catch_handler_list structure
           */
          private int getOffsetInList() {
              return offset-baseOffset;
          }
  
          /**
           * Places the <code>EncodedCatchHandler</code>, storing the offset and baseOffset, and returning the offset
           * immediately following this <code>EncodedCatchHandler</code>
           * @param offset the offset of this <code>EncodedCatchHandler</code> in the <code>DexFile</code>
           * @param baseOffset the offset of the beginning of the encoded_catch_handler_list structure in the
           * <code>DexFile</code>
           * @return the offset immediately following this <code>EncodedCatchHandler</code>
           */
          private int place(int offset, int baseOffset) {
              this.offset = offset;
              this.baseOffset = baseOffset;
  
              int size = handlers.length;
              if (catchAllHandlerAddress > -1) {
                  size *= -1;
                  offset += Leb128Utils.unsignedLeb128Size(catchAllHandlerAddress);
              }
              offset += Leb128Utils.signedLeb128Size(size);
  
              for (EncodedTypeAddrPair handler: handlers) {
                  offset += handler.getSize();
              }
              return offset;
          }
  
          /**
           * Writes the <code>EncodedCatchHandler</code> to the given <code>AnnotatedOutput</code> object
           * @param out the <code>AnnotatedOutput</code> object to write to
           */
          private void writeTo(AnnotatedOutput out) {
              if (out.annotates()) {
                  out.annotate("size: 0x" + Integer.toHexString(handlers.length) + " (" + handlers.length + ")");
  
                  int size = handlers.length;
                  if (catchAllHandlerAddress > -1) {
                      size = size * -1;
                  }
                  out.writeSignedLeb128(size);
  
                  int index = 0;
                  for (EncodedTypeAddrPair handler: handlers) {
                      out.annotate(0, "[" + index++ + "] encoded_type_addr_pair");
                      out.indent();
                      handler.writeTo(out);
                      out.deindent();
                  }
  
                  if (catchAllHandlerAddress > -1) {
                      out.annotate("catch_all_addr: 0x" + Integer.toHexString(catchAllHandlerAddress));
                      out.writeUnsignedLeb128(catchAllHandlerAddress);
                  }
              } else {
                  int size = handlers.length;
                  if (catchAllHandlerAddress > -1) {
                      size = size * -1;
                  }
                  out.writeSignedLeb128(size);
  
                  for (EncodedTypeAddrPair handler: handlers) {
                      handler.writeTo(out);
                  }
  
                  if (catchAllHandlerAddress > -1) {
                      out.writeUnsignedLeb128(catchAllHandlerAddress);
                  }
              }
          }
  
          @Override
          public int hashCode() {
              int hash = 0;
              for (EncodedTypeAddrPair handler: handlers) {
                  hash = hash * 31 + handler.hashCode();
              }
              hash = hash * 31 + catchAllHandlerAddress;
              return hash;
          }
  
          @Override
          public boolean equals(Object o) {
              if (this==o) {
                  return true;
              }
              if (o==null || !this.getClass().equals(o.getClass())) {
                  return false;
              }
  
              EncodedCatchHandler other = (EncodedCatchHandler)o;
              if (handlers.length != other.handlers.length || catchAllHandlerAddress != other.catchAllHandlerAddress) {
                  return false;
              }
  
              for (int i=0; i<handlers.length; i++) {
                  if (!handlers[i].equals(other.handlers[i])) {
                      return false;
                  }
              }
  
              return true;
          }
      }
  
      public static class EncodedTypeAddrPair {
          /**
           * The type of the <code>Exception</code> that this handler handles
           */
          public final TypeIdItem exceptionType;
  
          /**
           * The address (in 2-byte words) in the code of the handler
           */
          private int handlerAddress;
  
          /**
           * Constructs a new <code>EncodedTypeAddrPair</code> with the given values
           * @param exceptionType the type of the <code>Exception</code> that this handler handles
           * @param handlerAddress the address (in 2-byte words) in the code of the handler
           */
          public EncodedTypeAddrPair(TypeIdItem exceptionType, int handlerAddress) {
              this.exceptionType = exceptionType;
              this.handlerAddress = handlerAddress;
          }
  
          /**
           * This is used internally to construct a new <code>EncodedTypeAddrPair</code> while reading in a
           * <code>DexFile</code>
           * @param dexFile the <code>DexFile</code> that is being read in
           * @param in the Input object to read the <code>EncodedCatchHandler</code> from
           */
          private EncodedTypeAddrPair(DexFile dexFile, Input in) {
              exceptionType = dexFile.TypeIdsSection.getItemByIndex(in.readUnsignedLeb128());
              handlerAddress = in.readUnsignedLeb128();
          }
  
          /**
           * @return the size of this <code>EncodedTypeAddrPair</code>
           */
          private int getSize() {
              return Leb128Utils.unsignedLeb128Size(exceptionType.getIndex()) +
                     Leb128Utils.unsignedLeb128Size(handlerAddress);
          }
  
          /**
           * Writes the <code>EncodedTypeAddrPair</code> to the given <code>AnnotatedOutput</code> object
           * @param out the <code>AnnotatedOutput</code> object to write to
           */
          private void writeTo(AnnotatedOutput out) {
              if (out.annotates()) {
                  out.annotate("exception_type: " + exceptionType.getTypeDescriptor());
                  out.writeUnsignedLeb128(exceptionType.getIndex());
  
                  out.annotate("handler_addr: 0x" + Integer.toHexString(handlerAddress));
                  out.writeUnsignedLeb128(handlerAddress);
              } else {
                  out.writeUnsignedLeb128(exceptionType.getIndex());
                  out.writeUnsignedLeb128(handlerAddress);
              }
          }
  
          public int getHandlerAddress() {
              return handlerAddress;
          }
  
          @Override
          public int hashCode() {
              return exceptionType.hashCode() * 31 + handlerAddress;
          }
  
          @Override
          public boolean equals(Object o) {
              if (this==o) {
                  return true;
              }
              if (o==null || !this.getClass().equals(o.getClass())) {
                  return false;
              }
  
              EncodedTypeAddrPair other = (EncodedTypeAddrPair)o;
              return exceptionType == other.exceptionType && handlerAddress == other.handlerAddress;
          }
      }
  }
  