src/main/java/org/plumelib/bcelutil/InstructionListUtils.java
protected final void replace_instructions(
      MethodGen mg, InstructionList il, InstructionHandle ih, @Nullable InstructionList new_il) {

    if (new_il == null) {
      return;
    }

    InstructionHandle new_end;
    InstructionHandle new_start;
    int old_length = ih.getInstruction().getLength();

    new_il.setPositions();
    InstructionHandle end = new_il.getEnd();
    int new_length = end.getPosition() + end.getInstruction().getLength();

    debug_instrument.log("  replace_inst: %s %d%n%s%n", ih, new_il.getLength(), new_il);
    print_il(ih, "Before replace_inst");

    // If there is only one new instruction, just replace it in the handle
    if (new_il.getLength() == 1) {
      ih.setInstruction(new_il.getEnd().getInstruction());
      if (old_length == new_length) {
        // no possible changes downstream, so we can exit now
        return;
      }
      print_stack_map_table("replace_inst_with_single_inst B");
      il.setPositions();
      new_end = ih;
      // Update stack map for change in length of instruction bytes.
      update_stack_map_offset(ih.getPosition(), (new_length - old_length));

      // We need to see if inserting the additional instructions caused
      // a change in the amount of switch instruction padding bytes.
      // If so, we may need to update the corresponding stackmap.
      modify_stack_maps_for_switches(new_end, il);
    } else {
      print_stack_map_table("replace_inst_with_inst_list B");
      // We are inserting more than one instruction.
      // Get the start and end handles of the new instruction list.
      new_end = new_il.getEnd();
      new_start = il.insert(ih, new_il);
      il.setPositions();

      // Just in case there is a switch instruction in new_il we need
      // to recalculate new_length as the padding may have changed.
      new_length = new_end.getNext().getPosition() - new_start.getPosition();

      // Move all of the branches from the old instruction to the new start
      il.redirectBranches(ih, new_start);

      print_il(new_end, "replace_inst #1");

      // Move other targets to the new instuctions.
      if (ih.hasTargeters()) {
        for (InstructionTargeter it : ih.getTargeters()) {
          if (it instanceof LineNumberGen) {
            it.updateTarget(ih, new_start);
          } else if (it instanceof LocalVariableGen) {
            it.updateTarget(ih, new_end);
          } else if (it instanceof CodeExceptionGen) {
            CodeExceptionGen exc = (CodeExceptionGen) it;
            if (exc.getStartPC() == ih) exc.updateTarget(ih, new_start);
            else if (exc.getEndPC() == ih) exc.updateTarget(ih, new_end);
            else if (exc.getHandlerPC() == ih) exc.setHandlerPC(new_start);
            else System.out.printf("Malformed CodeException: %s%n", exc);
          } else {
            System.out.printf("unexpected target %s%n", it);
          }
        }
      }

      print_il(new_end, "replace_inst #2");

      // Remove the old handle.  There should be no targeters left to it.
      try {
        il.delete(ih);
      } catch (Exception e) {
        System.out.printf("Can't delete instruction: %s at %s%n", mg.getClassName(), mg.getName());
        throw new Error("Can't delete instruction", e);
      }
      // Need to update instruction address due to delete above.
      il.setPositions();

      print_il(new_end, "replace_inst #3");

      if (needStackMap) {
        // Before we look for branches in the inserted code we need
        // to update any existing stack maps for locations in the old
        // code that are after the inserted code.
        update_stack_map_offset(new_start.getPosition(), (new_length - old_length));

        // We need to see if inserting the additional instructions caused
        // a change in the amount of switch instruction padding bytes.
        // If so, we may need to update the corresponding stackmap.
        modify_stack_maps_for_switches(new_end, il);
        print_stack_map_table("replace_inst_with_inst_list C");

        // Look for branches within the new il; i.e., both the source
        // and target must be within the new il.  If we find any, the
        // target will need a stack map entry.
        // This situation is caused by a call to "instrument_object_call".
        InstructionHandle nih = new_start;
        int target_count = 0;
        int target_offsets[] = new int[2]; // see note below for why '2'

        // Any targeters on the first instruction will be from 'outside'
        // the new il so we start with the second instruction. (We already
        // know there is more than one instruction in the new il.)
        nih = nih.getNext();

        // We assume there is more code after the new il insertion point
        // so this getNext will not fail.
        new_end = new_end.getNext();
        while (nih != new_end) {
          if (nih.hasTargeters()) {
            for (InstructionTargeter it : nih.getTargeters()) {
              if (it instanceof BranchInstruction) {
                target_offsets[target_count++] = nih.getPosition();
                debug_instrument.log("New branch target: %s %n", nih);
              }
            }
          }
          nih = nih.getNext();
        }

        print_il(new_end, "replace_inst #4");

        if (target_count != 0) {
          // Currently, target_count is always 2; but code is
          // written to allow more.
          int cur_loc = new_start.getPosition();
          int orig_size = stack_map_table.length;
          StackMapEntry[] new_stack_map_table = new StackMapEntry[orig_size + target_count];

          // Calculate the operand stack value(s) for revised code.
          mg.setMaxStack();
          StackTypes stack_types = bcel_calc_stack_types(mg);
          OperandStack stack;

          // Find last stack map entry prior to first new branch target;
          // returns -1 if there isn't one. Also sets running_offset and number_active_locals.
          // The '+1' below means new_index points to the first stack map entry after our
          // inserted code.  There may not be one; in which case new_index == orig_size.
          int new_index = find_stack_map_index_before(target_offsets[0]) + 1;

          // The Java compiler can 'simplfy' the generated class file by not
          // inserting a stack map entry every time a local is defined if
          // that stack map entry is not needed as a branch target.  Thus,
          // there can be more live locals than defined by the stack maps.
          // This leads to serious complications if we wish to insert instrumentation
          // code, that contains internal branches and hence needs stack
          // map entries, into a section of code with 'extra' live locals.
          // If we don't include these extra locals in our inserted stack
          // map entries and they are subsequently referenced, that would
          // cause a verification error.  But if we do include them the locals
          // state might not be correct when execution reaches the first
          // stack map entry after our inserted code and that
          // would also cause a verification error. Dicey stuff.

          // I think one possibllity would be to insert a nop instruction
          // with a stack map of CHOP right after the last use of an 'extra'
          // local prior to the next stack map entry. An interesting alternative
          // might be, right as we start to process a method, make a pass over
          // the byte codes and add any 'missing' stack maps. This would
          // probably be too heavy as most instrumentation does not contain branches.
          // Not sure which of these two methods is 'easier' at this point.

          // I'm going to try a simplification.  If there are 'extra' locals
          // and we need to generate a FULL stack map - then go through and
          // make all subsequent StackMaps FULL as well.

          // The inserted code has pushed an object reference on the stack.
          // The StackMap(s) we create as targets of an internal branch
          // must account for this item.  Normally, we would use
          // SAME_LOCALS_1_STACK_ITEM_FRAME for this case, but there is a
          // possibility that the compiler has already allocated extra local items
          // that it plans to identify in a subsequent StackMap APPEND entry.

          // First, lets calculate the number and types of the live locals.
          StackMapType[] local_map_types = calculate_live_local_types(mg, cur_loc);
          int local_map_index = local_map_types.length;

          // local_map_index now contains the number of live locals.
          // number_active_locals has been calculated from the existing StackMap.
          // If these two are equal, we should be ok.
          int number_extra_locals = local_map_index - number_active_locals;
          // lets do a sanity check
          assert number_extra_locals >= 0
              : "invalid extra locals count: " + number_active_locals + ", " + local_map_index;

          // Copy any existing stack maps prior to inserted code.
          System.arraycopy(stack_map_table, 0, new_stack_map_table, 0, new_index);

          boolean need_full_maps = false;
          for (int i = 0; i < target_count; i++) {
            stack = stack_types.get(target_offsets[i]);
            debug_instrument.log("stack: %s %n", stack);

            if (number_extra_locals == 0 && stack.size() == 1 && !need_full_maps) {
              // the simple case
              StackMapType stack_map_type0 = generate_StackMapType_from_Type(stack.peek(0));
              StackMapType[] stack_map_types0 = {stack_map_type0};
              new_stack_map_table[new_index + i] =
                  new StackMapEntry(
                      Const.SAME_LOCALS_1_STACK_ITEM_FRAME,
                      0, // byte_code_offset set below
                      null,
                      stack_map_types0,
                      pool.getConstantPool());
            } else {
              // need a FULL_FRAME stack map entry
              need_full_maps = true;
              new_stack_map_table[new_index + i] =
                  new StackMapEntry(
                      Const.FULL_FRAME,
                      0, // byte_code_offset set below
                      calculate_live_local_types(mg, target_offsets[i]),
                      calculate_live_stack_types(stack),
                      pool.getConstantPool());
            }
            // now set the offset from the previous Stack Map entry to our new one.
            new_stack_map_table[new_index + i].updateByteCodeOffset(
                target_offsets[i] - (running_offset + 1));
            running_offset = target_offsets[i];
          }

          // now copy remaining 'old' stack maps
          int remainder = orig_size - new_index;
          if (remainder > 0) {
            // before we copy, we need to update first map after insert
            l1:
            while (nih != null) {
              if (nih.hasTargeters()) {
                for (InstructionTargeter it : nih.getTargeters()) {
                  if (it instanceof BranchInstruction) {
                    stack_map_table[new_index].updateByteCodeOffset(
                        nih.getPosition()
                            - target_offsets[target_count - 1]
                            - 1
                            - stack_map_table[new_index].getByteCodeOffset());
                    break l1;
                  } else if (it instanceof CodeExceptionGen) {
                    CodeExceptionGen exc = (CodeExceptionGen) it;
                    if (exc.getHandlerPC() == nih) {
                      stack_map_table[new_index].updateByteCodeOffset(
                          nih.getPosition()
                              - target_offsets[target_count - 1]
                              - 1
                              - stack_map_table[new_index].getByteCodeOffset());
                      break l1;
                    }
                  }
                }
              }
              nih = nih.getNext();
            }

            // Now we can copy the remaining stack map entries.
            if (need_full_maps) {
              // Must convert all remaining stack map entries to FULL.
              while (remainder > 0) {
                int stack_map_offset = stack_map_table[new_index].getByteCodeOffset();
                running_offset = running_offset + stack_map_offset + 1;
                stack = stack_types.get(running_offset);
                // System.out.printf("running_offset: %d, stack: %s%n", running_offset, stack);
                new_stack_map_table[new_index + target_count] =
                    new StackMapEntry(
                        Const.FULL_FRAME,
                        stack_map_offset,
                        calculate_live_local_types(mg, running_offset),
                        calculate_live_stack_types(stack),
                        pool.getConstantPool());
                new_index++;
                remainder--;
              }
            } else {
              System.arraycopy(
                  stack_map_table,
                  new_index,
                  new_stack_map_table,
                  new_index + target_count,
                  remainder);
            }
          }
          stack_map_table = new_stack_map_table;
        }
      }
    }

    debug_instrument.log("%n");
    print_il(new_end, "replace_inst #5");
  }
protected final void replace_instructions(
      MethodGen mg, InstructionList il, InstructionHandle ih, @Nullable InstructionList new_il) {

    if (new_il == null) {
      return;
    }

    InstructionHandle new_end;
    InstructionHandle new_start;
    int old_length = ih.getInstruction().getLength();

    new_il.setPositions();
    InstructionHandle end = new_il.getEnd();
    int new_length = end.getPosition() + end.getInstruction().getLength();

    debug_instrument.log("  replace_inst: %s %d%n%s%n", ih, new_il.getLength(), new_il);
    print_il(ih, "Before replace_inst");

    // If there is only one new instruction, just replace it in the handle
    if (new_il.getLength() == 1) {
      ih.setInstruction(new_il.getEnd().getInstruction());
      if (old_length == new_length) {
        // no possible changes downstream, so we can exit now
        return;
      }
      print_stack_map_table("replace_inst_with_single_inst B");
      il.setPositions();
      new_end = ih;
      // Update stack map for change in length of instruction bytes.
      update_stack_map_offset(ih.getPosition(), (new_length - old_length));

      // We need to see if inserting the additional instructions caused
      // a change in the amount of switch instruction padding bytes.
      // If so, we may need to update the corresponding stackmap.
      modify_stack_maps_for_switches(new_end, il);
    } else {
      print_stack_map_table("replace_inst_with_inst_list B");
      // We are inserting more than one instruction.
      // Get the start and end handles of the new instruction list.
      new_end = new_il.getEnd();
      new_start = il.insert(ih, new_il);
      il.setPositions();

      // Just in case there is a switch instruction in new_il we need
      // to recalculate new_length as the padding may have changed.
      new_length = new_end.getNext().getPosition() - new_start.getPosition();

      // Move all of the branches from the old instruction to the new start
      il.redirectBranches(ih, new_start);

      print_il(new_end, "replace_inst #1");

      // Move other targets to the new instuctions.
      if (ih.hasTargeters()) {
        for (InstructionTargeter it : ih.getTargeters()) {
          if (it instanceof LineNumberGen) {
            it.updateTarget(ih, new_start);
          } else if (it instanceof LocalVariableGen) {
            it.updateTarget(ih, new_end);
          } else if (it instanceof CodeExceptionGen) {
            CodeExceptionGen exc = (CodeExceptionGen) it;
            if (exc.getStartPC() == ih) exc.updateTarget(ih, new_start);
            else if (exc.getEndPC() == ih) exc.updateTarget(ih, new_end);
            else if (exc.getHandlerPC() == ih) exc.setHandlerPC(new_start);
            else System.out.printf("Malformed CodeException: %s%n", exc);
          } else {
            System.out.printf("unexpected target %s%n", it);
          }
        }
      }

      print_il(new_end, "replace_inst #2");

      // Remove the old handle.  There should be no targeters left to it.
      try {
        il.delete(ih);
      } catch (Exception e) {
        System.out.printf("Can't delete instruction: %s at %s%n", mg.getClassName(), mg.getName());
        throw new Error("Can't delete instruction", e);
      }
      // Need to update instruction address due to delete above.
      il.setPositions();

      print_il(new_end, "replace_inst #3");

      if (needStackMap) {
        // Before we look for branches in the inserted code we need
        // to update any existing stack maps for locations in the old
        // code that are after the inserted code.
        update_stack_map_offset(new_start.getPosition(), (new_length - old_length));

        // We need to see if inserting the additional instructions caused
        // a change in the amount of switch instruction padding bytes.
        // If so, we may need to update the corresponding stackmap.
        modify_stack_maps_for_switches(new_end, il);
        print_stack_map_table("replace_inst_with_inst_list C");

        // Look for branches within the new il; i.e., both the source
        // and target must be within the new il.  If we find any, the
        // target will need a stack map entry.
        // This situation is caused by a call to "instrument_object_call".
        InstructionHandle nih = new_start;
        int target_count = 0;
        int target_offsets[] = new int[2]; // see note below for why '2'

        // Any targeters on the first instruction will be from 'outside'
        // the new il so we start with the second instruction. (We already
        // know there is more than one instruction in the new il.)
        nih = nih.getNext();

        // We assume there is more code after the new il insertion point
        // so this getNext will not fail.
        new_end = new_end.getNext();
        while (nih != new_end) {
          if (nih.hasTargeters()) {
            for (InstructionTargeter it : nih.getTargeters()) {
              if (it instanceof BranchInstruction) {
                target_offsets[target_count++] = nih.getPosition();
                debug_instrument.log("New branch target: %s %n", nih);
              }
            }
          }
          nih = nih.getNext();
        }

        print_il(new_end, "replace_inst #4");

        if (target_count != 0) {
          // Currently, target_count is always 2; but code is
          // written to allow more.
          int cur_loc = new_start.getPosition();
          int orig_size = stack_map_table.length;
          StackMapEntry[] new_stack_map_table = new StackMapEntry[orig_size + target_count];

          // Calculate the operand stack value(s) for revised code.
          mg.setMaxStack();
          StackTypes stack_types = bcel_calc_stack_types(mg);
          OperandStack stack;

          // Find last stack map entry prior to first new branch target;
          // returns -1 if there isn't one. Also sets running_offset and number_active_locals.
          // The '+1' below means new_index points to the first stack map entry after our
          // inserted code.  There may not be one; in which case new_index == orig_size.
          int new_index = find_stack_map_index_before(target_offsets[0]) + 1;

          // The Java compiler can 'simplfy' the generated class file by not
          // inserting a stack map entry every time a local is defined if
          // that stack map entry is not needed as a branch target.  Thus,
          // there can be more live locals than defined by the stack maps.
          // This leads to serious complications if we wish to insert instrumentation
          // code, that contains internal branches and hence needs stack
          // map entries, into a section of code with 'extra' live locals.
          // If we don't include these extra locals in our inserted stack
          // map entries and they are subsequently referenced, that would
          // cause a verification error.  But if we do include them the locals
          // state might not be correct when execution reaches the first
          // stack map entry after our inserted code and that
          // would also cause a verification error. Dicey stuff.

          // I think one possibllity would be to insert a nop instruction
          // with a stack map of CHOP right after the last use of an 'extra'
          // local prior to the next stack map entry. An interesting alternative
          // might be, right as we start to process a method, make a pass over
          // the byte codes and add any 'missing' stack maps. This would
          // probably be too heavy as most instrumentation does not contain branches.
          // Not sure which of these two methods is 'easier' at this point.

          // I'm going to try a simplification.  If there are 'extra' locals
          // and we need to generate a FULL stack map - then go through and
          // make all subsequent StackMaps FULL as well.

          // The inserted code has pushed an object reference on the stack.
          // The StackMap(s) we create as targets of an internal branch
          // must account for this item.  Normally, we would use
          // SAME_LOCALS_1_STACK_ITEM_FRAME for this case, but there is a
          // possibility that the compiler has already allocated extra local items
          // that it plans to identify in a subsequent StackMap APPEND entry.

          // First, lets calculate the number and types of the live locals.
          StackMapType[] local_map_types = calculate_live_local_types(mg, cur_loc);
          int local_map_index = local_map_types.length;

          // local_map_index now contains the number of live locals.
          // number_active_locals has been calculated from the existing StackMap.
          // If these two are equal, we should be ok.
          int number_extra_locals = local_map_index - number_active_locals;
          // lets do a sanity check
          assert number_extra_locals >= 0
              : "invalid extra locals count: " + number_active_locals + ", " + local_map_index;

          // Copy any existing stack maps prior to inserted code.
          System.arraycopy(stack_map_table, 0, new_stack_map_table, 0, new_index);

          boolean need_full_maps = false;
          for (int i = 0; i < target_count; i++) {
            stack = stack_types.get(target_offsets[i]);
            debug_instrument.log("stack: %s %n", stack);

            if (number_extra_locals == 0 && stack.size() == 1 && !need_full_maps) {
              // the simple case
              StackMapType stack_map_type0 = generate_StackMapType_from_Type(stack.peek(0));
              StackMapType[] stack_map_types0 = {stack_map_type0};
              new_stack_map_table[new_index + i] =
                  new StackMapEntry(
                      Const.SAME_LOCALS_1_STACK_ITEM_FRAME,
                      0, // byte_code_offset set below
                      null,
                      stack_map_types0,
                      pool.getConstantPool());
            } else {
              // need a FULL_FRAME stack map entry
              need_full_maps = true;
              new_stack_map_table[new_index + i] =
                  new StackMapEntry(
                      Const.FULL_FRAME,
                      0, // byte_code_offset set below
                      calculate_live_local_types(mg, target_offsets[i]),
                      calculate_live_stack_types(stack),
                      pool.getConstantPool());
            }
            // now set the offset from the previous Stack Map entry to our new one.
            new_stack_map_table[new_index + i].updateByteCodeOffset(
                target_offsets[i] - (running_offset + 1));
            running_offset = target_offsets[i];
          }

          // now copy remaining 'old' stack maps
          int remainder = orig_size - new_index;
          if (remainder > 0) {
            // before we copy, we need to update first map after insert
            l1:
            while (nih != null) {
              if (nih.hasTargeters()) {
                for (InstructionTargeter it : nih.getTargeters()) {
                  if (it instanceof BranchInstruction) {
                    stack_map_table[new_index].updateByteCodeOffset(
                        nih.getPosition()
                            - target_offsets[target_count - 1]
                            - 1
                            - stack_map_table[new_index].getByteCodeOffset());
                    break l1;
                  } else if (it instanceof CodeExceptionGen) {
                    CodeExceptionGen exc = (CodeExceptionGen) it;
                    if (exc.getHandlerPC() == nih) {
                      stack_map_table[new_index].updateByteCodeOffset(
                          nih.getPosition()
                              - target_offsets[target_count - 1]
                              - 1
                              - stack_map_table[new_index].getByteCodeOffset());
                      break l1;
                    }
                  }
                }
              }
              nih = nih.getNext();
            }

            // Now we can copy the remaining stack map entries.
            if (need_full_maps) {
              // Must convert all remaining stack map entries to FULL.
              while (remainder > 0) {
                int stack_map_offset = stack_map_table[new_index].getByteCodeOffset();
                running_offset = running_offset + stack_map_offset + 1;
                stack = stack_types.get(running_offset);
                // System.out.printf("running_offset: %d, stack: %s%n", running_offset, stack);
                new_stack_map_table[new_index + target_count] =
                    new StackMapEntry(
                        Const.FULL_FRAME,
                        stack_map_offset,
                        calculate_live_local_types(mg, running_offset),
                        calculate_live_stack_types(stack),
                        pool.getConstantPool());
                new_index++;
                remainder--;
              }
            } else {
              System.arraycopy(
                  stack_map_table,
                  new_index,
                  new_stack_map_table,
                  new_index + target_count,
                  remainder);
            }
          }
          stack_map_table = new_stack_map_table;
        }
      }
    }

    debug_instrument.log("%n");
    print_il(new_end, "replace_inst #5");
  }
Replace instruction ih in list il with the instructions in new_il. If new_il is null, do
nothing.

@param mg MethodGen containing the instruction handle
@param il InstructionList containing ih
@param ih InstructionHandle indicating where to insert new code
@param new_il InstructionList holding the new code