package org.plumelib.bcelutil;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.apache.bcel.Const;
import org.apache.bcel.classfile.Attribute;
import org.apache.bcel.classfile.Constant;
import org.apache.bcel.classfile.ConstantUtf8;
import org.apache.bcel.classfile.StackMap;
import org.apache.bcel.classfile.StackMapEntry;
import org.apache.bcel.classfile.StackMapType;
import org.apache.bcel.generic.BasicType;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.IINC;
import org.apache.bcel.generic.IndexedInstruction;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.LocalVariableGen;
import org.apache.bcel.generic.LocalVariableInstruction;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.RET;
import org.apache.bcel.generic.Type;
import org.apache.bcel.verifier.VerificationResult;
import org.checkerframework.checker.index.qual.IndexOrLow;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.interning.qual.InternedDistinct;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.signature.qual.ClassGetName;
import org.checkerframework.dataflow.qual.Pure;

/**
 * This class provides utility methods to maintain and modify a method's StackMapTable within a Java
 * class file. It can be thought of as an extension to BCEL.
 *
 *
BCEL ought to automatically build and maintain the StackMapTable in a manner similar to the
 * LineNumberTable and the LocalVariableTable. However, for historical reasons, it does not.
 *
 *
This class cannot be a set of static methods (like {@link BcelUtil}) as it maintains state
 * during the client's processing of a method that must be available on a per thread basis. Thus it
 * is an abstract class extended by {@link org.plumelib.bcelutil.InstructionListUtils}. A client
 * would not normally extend this class directly.
 */
@SuppressWarnings("nullness")
public abstract class StackMapUtils {

  /*
   * NOMENCLATURE
   *
   * 'index' is an item's subscript into a data structure.
   *
   * 'offset' is used to describe two different address types:
   *   * the offset of a byte code from the start of a method's byte codes
   *   * the offset of a variable from the start of a method's stack frame
   *
   *     The Java Virtual Machine Specification uses
   *     'index into the local variable array of the current frame'
   *     or 'slot number' to describe this second case.
   *
   * Unfortunately, BCEL uses the method names getIndex and setIndex
   * to refer to 'offset's into the local stack frame.
   * It uses getPosition and setPosition to refer to 'offset's into
   * the byte codes.
   */

  /**
   * The pool for the method currently being processed. Must be set by the client. See the sample
   * code in {@link InstructionListUtils} for when and how to set this value.
   */
  protected @Nullable ConstantPoolGen pool = null;

  /** A log to which to print debugging information about program instrumentation. */
  protected SimpleLog debug_instrument = new SimpleLog(false);

  /** Whether or not the current method needs a StackMap. */
  protected boolean needStackMap = false;

  /** Working copy of StackMapTable; set by set_current_stack_map_table. */
  protected StackMapEntry @Nullable [] stack_map_table = null;

  /** Original stack map table attribute; set by set_current_stack_map_table. */
  protected @Nullable StackMap smta = null;

  /** Initial state of StackMapTypes for locals on method entry. */
  protected StackMapType[] initial_type_list;

  /** The number of local variables in the current method prior to any modifications. */
  protected int initial_locals_count;

  /**
   * A number of methods in this class search and locate a particular StackMap within the current
   * method. This variable contains the number of live local variables within the range of byte code
   * instructions covered by this StackMap. Set by update_stack_map_offset, find_stack_map_equal,
   * find_stack_map_index_before, or find_stack_map_index_after.
   */
  protected @NonNegative int number_active_locals;

  /**
   * Offset into code that corresponds to the current StackMap of interest. Set by
   * find_stack_map_index_before.
   */
  protected int running_offset;

  /**
   * The index of the first 'true' local in the local variable table. That is, after 'this' and any
   * parameters.
   */
  protected int first_local_index;

  /** An empty StackMap used for initialization. */
  @SuppressWarnings("interning") // @InternedDistinct initalization with fresh object
  private StackMapEntry @InternedDistinct [] empty_stack_map_table = {};

  /**
   * A map from instructions that create uninitialized NEW objects to the corresponding StackMap
   * entry. Set by build_unitialized_NEW_map.
   */
  private Map<InstructionHandle, Integer> uninitialized_NEW_map = new HashMap<>();

  /**
   * Returns a String array with new_string added to the end of arr.
   *
   * @param arr original string array
   * @param new_string string to be added
   * @return the new string array
   */
  protected String[] add_string(String[] arr, String new_string) {
    String[] new_arr = new String[arr.length + 1];
    for (int ii = 0; ii < arr.length; ii++) {
      new_arr[ii] = arr[ii];
    }
    new_arr[arr.length] = new_string;
    return new_arr;
  }

  /**
   * Return the attribute name for the specified attribute.
   *
   * @param a the attribute
   * @return the attribute name for the specified attribute
   */
  @Pure
  protected final String get_attribute_name(Attribute a) {
    int con_index = a.getNameIndex();
    Constant c = pool.getConstant(con_index);
    String att_name = ((ConstantUtf8) c).getBytes();
    return att_name;
  }

  /**
   * Returns whether or not the specified attribute is a LocalVariableTypeTable.
   *
   * @param a the attribute
   * @return true iff the attribute is a LocalVariableTypeTable
   */
  @Pure
  protected final boolean is_local_variable_type_table(Attribute a) {
    return get_attribute_name(a).equals("LocalVariableTypeTable");
  }

  /**
   * Returns whether or not the specified attribute is a StackMapTable.
   *
   * @param a the attribute
   * @return true iff the attribute is a StackMapTable
   */
  @Pure
  protected final boolean is_stack_map_table(Attribute a) {
    return get_attribute_name(a).equals("StackMapTable");
  }

  /**
   * Find the StackMapTable attribute for a method. Return null if there isn't one.
   *
   * @param mgen the method
   * @return the StackMapTable attribute for the method (or null if not present)
   */
  @Pure
  protected final @Nullable Attribute get_stack_map_table_attribute(MethodGen mgen) {
    for (Attribute a : mgen.getCodeAttributes()) {
      if (is_stack_map_table(a)) {
        return a;
      }
    }
    return null;
  }

  /**
   * Find the LocalVariableTypeTable attribute for a method. Return null if there isn't one.
   *
   * @param mgen the method
   * @return the LocalVariableTypeTable attribute for the method (or null if not present)
   */
  @Pure
  protected final @Nullable Attribute get_local_variable_type_table_attribute(MethodGen mgen) {
    for (Attribute a : mgen.getCodeAttributes()) {
      if (is_local_variable_type_table(a)) {
        return a;
      }
    }
    return null;
  }

  /**
   * Remove the local variable type table attribute (LVTT) from mgen. Some instrumentation changes
   * require this to be updated, but without BCEL support that would be hard to do. It should be
   * safe to just delete it since it is optional and really only of use to a debugger.
   *
   * @param mgen the method to clear out
   */
  protected final void remove_local_variable_type_table(MethodGen mgen) {
    mgen.removeLocalVariableTypeTable();
  }

  /**
   * We have inserted additional byte(s) into the instruction list; update the StackMaps, if
   * required. Also sets running_offset.
   *
   * @param position the location of insertion
   * @param delta the size of the insertion
   */
  protected final void update_stack_map_offset(int position, int delta) {

    running_offset = -1; // no +1 on first entry
    for (int i = 0; i < stack_map_table.length; i++) {
      running_offset = stack_map_table[i].getByteCodeOffset() + running_offset + 1;

      if (running_offset > position) {
        stack_map_table[i].updateByteCodeOffset(delta);
        // Only update the first StackMap that occurs after the given
        // offset as map offsets are relative to previous map entry.
        return;
      }
    }
  }

  /**
   * Find the StackMap entry whose offset matches the input argument. Also sets running_offset.
   *
   * @param offset byte code offset
   * @return the corresponding StackMapEntry
   */
  protected final StackMapEntry find_stack_map_equal(int offset) {

    running_offset = -1; // no +1 on first entry
    for (int i = 0; i < stack_map_table.length; i++) {
      running_offset = stack_map_table[i].getByteCodeOffset() + running_offset + 1;

      if (running_offset > offset) {
        throw new RuntimeException("Invalid StackMap offset 1");
      }

      if (running_offset == offset) {
        return stack_map_table[i];
      }
      // try next map entry
    }

    // no offset matched
    throw new RuntimeException("Invalid StackMap offset 2");
  }

  /**
   * Find the index of the StackMap entry whose offset is the last one before the input argument.
   * Return -1 if there isn't one. Also sets running_offset and number_active_locals.
   *
   * @param offset byte code offset
   * @return the corresponding StackMapEntry index
   */
  protected final int find_stack_map_index_before(int offset) {

    number_active_locals = initial_locals_count;
    running_offset = -1; // no +1 on first entry
    for (int i = 0; i < stack_map_table.length; i++) {
      running_offset = running_offset + stack_map_table[i].getByteCodeOffset() + 1;
      if (running_offset >= offset) {
        if (i == 0) {
          // reset offset to previous
          running_offset = -1;
          return -1;
        } else {
          // back up offset to previous
          running_offset = running_offset - stack_map_table[i].getByteCodeOffset() - 1;
          // return previous
          return i - 1;
        }
      }

      // Update number of active locals based on this StackMap entry.
      int frame_type = stack_map_table[i].getFrameType();
      if (frame_type >= Const.APPEND_FRAME && frame_type <= Const.APPEND_FRAME_MAX) {
        number_active_locals += frame_type - 251;
      } else if (frame_type >= Const.CHOP_FRAME && frame_type <= Const.CHOP_FRAME_MAX) {
        number_active_locals -= 251 - frame_type;
      } else if (frame_type == Const.FULL_FRAME) {
        number_active_locals = stack_map_table[i].getNumberOfLocals();
      }
      // All other frame_types do not modify locals.
    }

    if (stack_map_table.length == 0) {
      return -1;
    } else {
      return stack_map_table.length - 1;
    }
  }

  /**
   * Find the index of the StackMap entry whose offset is the first one after the input argument.
   * Return -1 if there isn't one. Also sets running_offset.
   *
   * @param offset byte code offset
   * @return the corresponding StackMapEntry index
   */
  protected final @IndexOrLow("stack_map_table") int find_stack_map_index_after(int offset) {

    running_offset = -1; // no +1 on first entry
    for (int i = 0; i < stack_map_table.length; i++) {
      running_offset = running_offset + stack_map_table[i].getByteCodeOffset() + 1;
      if (running_offset > offset) {
        return i;
      }
      // try next map entry
    }

    // no such entry found
    return -1;
  }

  /**
   * Check to see if (due to some instruction modifications) there have been any changes in a switch
   * statement's padding bytes. If so, then update the corresponding StackMap to reflect this
   * change.
   *
   * @param ih where to start looking for a switch instruction
   * @param il instruction list to search
   */
  protected final void modify_stack_maps_for_switches(InstructionHandle ih, InstructionList il) {
    Instruction inst;
    short opcode;

    if (!needStackMap) {
      return;
    }

    // Make sure all instruction offsets are uptodate.
    il.setPositions();

    // Loop through each instruction looking for a switch
    while (ih != null) {
      inst = ih.getInstruction();
      opcode = inst.getOpcode();

      if (opcode == Const.TABLESWITCH || opcode == Const.LOOKUPSWITCH) {
        int current_offset = ih.getPosition();
        int index = find_stack_map_index_after(current_offset);
        if (index == -1) {
          throw new RuntimeException("Invalid StackMap offset 3");
        }
        StackMapEntry stack_map = stack_map_table[index];
        int delta = (current_offset + inst.getLength()) - running_offset;
        if (delta != 0) {
          stack_map.updateByteCodeOffset(delta);
        }
        // Since StackMap offsets are relative to the previous one
        // we only have to do the first one after a switch.
        // But we do need to look at all the switch instructions.
      }

      // Go on to the next instruction in the list
      ih = ih.getNext();
    }
  }

  /**
   * Find the live range of the compiler temp(s) at the given offset and create a LocalVariableGen
   * for each. Note the compiler might generate temps of different sizes at the same offset (must
   * have disjoint lifetimes).
   *
   * @param mgen the method
   * @param offset compiler assigned local offset of hidden temp
   * @return offset incremented by size of smallest temp found at offset
   */
  protected final int gen_temp_locals(MethodGen mgen, int offset) {
    int live_start = 0;
    Type live_type = null;
    InstructionList il = mgen.getInstructionList();
    il.setPositions();

    // Set up inital state of StackMap info on entry to method.
    int locals_offset_height = 0;
    int byte_code_offset = -1;
    LocalVariableGen new_lvg;
    int min_size = 3; // only sizes are 1 or 2; start with something larger.

    number_active_locals = initial_locals_count;
    StackMapType[] types_of_active_locals = new StackMapType[number_active_locals];
    for (int ii = 0; ii < number_active_locals; ii++) {
      types_of_active_locals[ii] = initial_type_list[ii];
      locals_offset_height += getSize(initial_type_list[ii]);
    }

    // update state for each StackMap entry
    for (StackMapEntry smte : stack_map_table) {
      int frame_type = smte.getFrameType();
      byte_code_offset += smte.getByteCodeOffset() + 1;

      if (frame_type >= Const.APPEND_FRAME && frame_type <= Const.APPEND_FRAME_MAX) {
        // number to append is frame_type - 251
        types_of_active_locals =
            Arrays.copyOf(types_of_active_locals, number_active_locals + frame_type - 251);
        for (StackMapType smt : smte.getTypesOfLocals()) {
          types_of_active_locals[number_active_locals++] = smt;
          locals_offset_height += getSize(smt);
        }
      } else if (frame_type >= Const.CHOP_FRAME && frame_type <= Const.CHOP_FRAME_MAX) {
        int number_to_chop = 251 - frame_type;
        while (number_to_chop > 0) {
          locals_offset_height -= getSize(types_of_active_locals[--number_active_locals]);
          number_to_chop--;
        }
        types_of_active_locals = Arrays.copyOf(types_of_active_locals, number_active_locals);
      } else if (frame_type == Const.FULL_FRAME) {
        locals_offset_height = 0;
        number_active_locals = 0;
        types_of_active_locals = new StackMapType[smte.getNumberOfLocals()];
        for (StackMapType smt : smte.getTypesOfLocals()) {
          types_of_active_locals[number_active_locals++] = smt;
          locals_offset_height += getSize(smt);
        }
      }
      // all other frame_types do not modify locals.

      // System.out.printf("byte_code_offset: %d, temp offset: %d, locals_offset_height: %d,
      // number_active_locals: %d, local types: %s%n",
      //       byte_code_offset, offset, locals_offset_height, number_active_locals,
      // Arrays.toString(types_of_active_locals));

      if (live_start == 0) {
        // did the latest StackMap entry define the temp in question?
        if (offset < locals_offset_height) {
          live_start = byte_code_offset;
          int running_offset = 0;
          for (StackMapType smt : types_of_active_locals) {
            if (running_offset == offset) {
              live_type = generate_Type_from_StackMapType(smt);
              break;
            }
            running_offset += getSize(smt);
          }
          if (live_type == null) {
            // No matching offset in stack maps so this offset must be
            // second half of long or double. Just skip to next stack map.
            live_start = 0;
          }
        }
      } else {
        // did the latest StackMap entry undefine the temp in question?
        if (offset >= locals_offset_height) {
          // create the temp variable
          new_lvg =
              mgen.addLocalVariable(
                  "DaIkOnTeMp" + offset,
                  live_type,
                  offset,
                  il.findHandle(live_start),
                  il.findHandle(byte_code_offset));
          debug_instrument.log(
              "Added local  %s, %d, %d : %s, %s%n",
              new_lvg.getIndex(),
              new_lvg.getStart().getPosition(),
              new_lvg.getEnd().getPosition(),
              new_lvg.getName(),
              new_lvg.getType());
          min_size = Math.min(min_size, live_type.getSize());
          // reset to look for more temps at same offset
          live_start = 0;
          live_type = null;
        }
      }
      // go on to next StackMap entry
    }
    // we are done with stack maps; need to see if any temps still active
    if (live_start != 0) {
      // live range is to end of method; create the temp variable
      new_lvg =
          mgen.addLocalVariable(
              "DaIkOnTeMp" + offset, live_type, offset, il.findHandle(live_start), null);
      debug_instrument.log(
          "Added local  %s, %d, %d : %s, %s%n",
          new_lvg.getIndex(),
          new_lvg.getStart().getPosition(),
          il.getEnd().getPosition(),
          new_lvg.getName(),
          new_lvg.getType());
      min_size = Math.min(min_size, live_type.getSize());
    } else {
      if (min_size == 3) {
        // did not find a temp in any of the stack maps; that must mean the
        // temp live range is in between two stack maps or after last stack map
        // need to scan all byte codes to get live range and type
        // HACK: for now, just make a guess and put it after last stack map
        // Note we may be defining a bogus temp for second half of long or double.
        if (byte_code_offset == -1) {
          // no stack maps at all, set start to 0
          byte_code_offset = 0;
        }
        new_lvg =
            mgen.addLocalVariable(
                "DaIkOnTeMp" + offset, Type.OBJECT, offset, il.findHandle(byte_code_offset), null);
        debug_instrument.log(
            "Added local  %s, %d, %d : %s, %s%n",
            new_lvg.getIndex(),
            new_lvg.getStart().getPosition(),
            il.getEnd().getPosition(),
            new_lvg.getName(),
            new_lvg.getType());
        min_size = Math.min(min_size, Type.OBJECT.getSize());
      }
    }
    return offset + min_size;
  }

  // TODO: From the documentation, I am not sure what this method does or when it should be called.
  /**
   * We need to locate and remember any NEW instructions that create uninitialized objects. Their
   * offset may be contained in a StackMap entry and will probably need to be updated as we add
   * instrumentation code. Note that these instructions are fairly rare.
   *
   * @param il instruction list to search
   */
  protected final void build_unitialized_NEW_map(InstructionList il) {

    uninitialized_NEW_map.clear();
    il.setPositions();

    for (StackMapEntry smte : stack_map_table) {
      int frame_type = smte.getFrameType();

      if ((frame_type >= Const.SAME_LOCALS_1_STACK_ITEM_FRAME
              && frame_type <= Const.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED)
          || (frame_type >= Const.APPEND_FRAME && frame_type <= Const.APPEND_FRAME_MAX)
          || (frame_type == Const.FULL_FRAME)) {

        if (smte.getNumberOfLocals() > 0) {
          for (StackMapType smt : smte.getTypesOfLocals()) {
            if (smt.getType() == Const.ITEM_NewObject) {
              int i = smt.getIndex();
              uninitialized_NEW_map.put(il.findHandle(i), i);
            }
          }
        }
        if (smte.getNumberOfStackItems() > 0) {
          for (StackMapType smt : smte.getTypesOfStackItems()) {
            if (smt.getType() == Const.ITEM_NewObject) {
              int i = smt.getIndex();
              uninitialized_NEW_map.put(il.findHandle(i), i);
            }
          }
        }
      }
    }
  }

  /**
   * One of uninitialized NEW instructions has moved. Update its offset in StackMap entries. Note
   * that more than one entry could refer to the same instruction. This is a helper routine used by
   * update_uninitialized_NEW_offsets.
   *
   * @param old_offset original location of NEW instruction
   * @param new_offset new location of NEW instruction
   */
  private final void update_NEW_object_stack_map_entries(int old_offset, int new_offset) {

    for (StackMapEntry smte : stack_map_table) {
      int frame_type = smte.getFrameType();

      if ((frame_type >= Const.SAME_LOCALS_1_STACK_ITEM_FRAME
              && frame_type <= Const.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED)
          || (frame_type >= Const.APPEND_FRAME && frame_type <= Const.APPEND_FRAME_MAX)
          || (frame_type == Const.FULL_FRAME)) {

        if (smte.getNumberOfLocals() > 0) {
          for (StackMapType smt : smte.getTypesOfLocals()) {
            if (smt.getType() == Const.ITEM_NewObject) {
              if (old_offset == smt.getIndex()) {
                smt.setIndex(new_offset);
              }
            }
          }
        }
        if (smte.getNumberOfStackItems() > 0) {
          for (StackMapType smt : smte.getTypesOfStackItems()) {
            if (smt.getType() == Const.ITEM_NewObject) {
              if (old_offset == smt.getIndex()) {
                smt.setIndex(new_offset);
              }
            }
          }
        }
      }
    }
  }

  /**
   * Check to see if any of the uninitialized NEW instructions have moved. Again, these are rare, so
   * a linear pass is fine.
   *
   * @param il instruction list to search
   */
  protected final void update_uninitialized_NEW_offsets(InstructionList il) {

    il.setPositions();

    for (Map.Entry<InstructionHandle, Integer> e : uninitialized_NEW_map.entrySet()) {
      InstructionHandle ih = e.getKey();
      int old_offset = e.getValue().intValue();
      int new_offset = ih.getPosition();
      if (old_offset != new_offset) {
        update_NEW_object_stack_map_entries(old_offset, new_offset);
        e.setValue(new_offset);
      }
    }
  }

  /**
   * Process the instruction list, adding size (1 or 2) to the index of each Instruction that
   * references a local that is equal or higher in the local map than index_first_moved_local. Size
   * should be the size of the new local that was just inserted at index_first_moved_local.
   *
   * @param mgen MethodGen to be modified
   * @param index_first_moved_local original index of first local moved "up"
   * @param size size of new local added (1 or 2)
   */
  protected final void adjust_code_for_locals_change(
      MethodGen mgen, int index_first_moved_local, int size) {

    InstructionList il = mgen.getInstructionList();
    for (InstructionHandle ih = il.getStart(); ih != null; ih = ih.getNext()) {
      Instruction inst = ih.getInstruction();
      int orig_length = inst.getLength();
      int operand;

      if ((inst instanceof RET) || (inst instanceof IINC)) {
        IndexedInstruction index_inst = (IndexedInstruction) inst;
        if (index_inst.getIndex() >= index_first_moved_local) {
          index_inst.setIndex(index_inst.getIndex() + size);
        }
      } else if (inst instanceof LocalVariableInstruction) {
        // BCEL handles all the details of which opcode and if index
        // is implicit or explicit; also, and if needs to be WIDE.
        operand = ((LocalVariableInstruction) inst).getIndex();
        if (operand >= index_first_moved_local) {
          ((LocalVariableInstruction) inst).setIndex(operand + size);
        }
      }
      // Unfortunately, BCEL doesn't take care of incrementing the
      // offset within StackMapEntrys.
      int delta = inst.getLength() - orig_length;
      if (delta > 0) {
        il.setPositions();
        update_stack_map_offset(ih.getPosition(), delta);
        modify_stack_maps_for_switches(ih, il);
      }
    }
  }

  /**
   * Get existing StackMapTable from the MethodGen argument. If there is none, create a new empty
   * one. Sets both smta and stack_map_table. Must be called prior to any other methods that
   * manipulate the stack_map_table!
   *
   * @param mgen MethodGen to search
   * @param java_class_version Java version for the classfile; stack_map_table is optional before
   *     Java 1.7 (= classfile version 51)
   * @deprecated use {@link #set_current_stack_map_table}
   */
  @Deprecated // use set_current_stack_map_table() */
  protected final void fetch_current_stack_map_table(MethodGen mgen, int java_class_version) {
    set_current_stack_map_table(mgen, java_class_version);
  }

  /**
   * Get existing StackMapTable from the MethodGen argument. If there is none, create a new empty
   * one. Sets both smta and stack_map_table. Must be called prior to any other methods that
   * manipulate the stack_map_table!
   *
   * @param mgen MethodGen to search
   * @param java_class_version Java version for the classfile; stack_map_table is optional before
   *     Java 1.7 (= classfile version 51)
   */
  @EnsuresNonNull({"stack_map_table"})
  protected final void set_current_stack_map_table(MethodGen mgen, int java_class_version) {

    smta = (StackMap) get_stack_map_table_attribute(mgen);
    if (smta != null) {
      // get a deep copy of the original StackMapTable.
      stack_map_table = ((StackMap) (smta.copy(smta.getConstantPool()))).getStackMap();
      needStackMap = true;

      debug_instrument.log(
          "Attribute tag: %s length: %d nameIndex: %d%n",
          smta.getTag(), smta.getLength(), smta.getNameIndex());
      // Delete existing stack map - we'll add a new one later.
      mgen.removeCodeAttribute(smta);
    } else {
      stack_map_table = empty_stack_map_table;
      if (java_class_version > Const.MAJOR_1_6) {
        needStackMap = true;
      }
    }
    print_stack_map_table("Original");
  }

  /**
   * Print the contents of the StackMapTable to the debug_instrument.log.
   *
   * @param prefix label to display with table
   */
  protected final void print_stack_map_table(String prefix) {

    debug_instrument.log("%nStackMap(%s) %s items:%n", prefix, stack_map_table.length);
    running_offset = -1; // no +1 on first entry
    for (int i = 0; i < stack_map_table.length; i++) {
      running_offset = stack_map_table[i].getByteCodeOffset() + running_offset + 1;
      debug_instrument.log("@%03d %s %n", running_offset, stack_map_table[i]);
    }
  }

  /**
   * Create a new StackMap code attribute from stack_map_table.
   *
   * @param mgen MethodGen to add attribute to
   * @throws IOException if cannot create the attribute
   */
  protected final void create_new_stack_map_attribute(MethodGen mgen) throws IOException {

    if (!needStackMap) {
      return;
    }
    if (stack_map_table == empty_stack_map_table) {
      return;
    }
    print_stack_map_table("Final");

    // Build new StackMapTable attribute
    StackMap map_table =
        new StackMap(pool.addUtf8("StackMapTable"), 0, null, pool.getConstantPool());
    map_table.setStackMap(stack_map_table);
    mgen.addCodeAttribute(map_table);
  }

  /**
   * Convert a Type name to a Class name.
   *
   * @param t type whose name is to be converted
   * @return a String containing the class name
   */
  @SuppressWarnings("signature") // conversion routine
  protected static @ClassGetName String typeToClassGetName(Type t) {

    if (t instanceof ObjectType) {
      return ((ObjectType) t).getClassName();
    } else if (t instanceof BasicType) {
      // Use reserved keyword for basic type rather than signature to
      // avoid conflicts with user defined types.
      return t.toString();
    } else {
      // Array type: just convert '/' to '.'
      return t.getSignature().replace('/', '.');
    }
  }

  /**
   * Convert a Type to a StackMapType.
   *
   * @param t Type to be converted
   * @return result StackMapType
   */
  protected final StackMapType generate_StackMapType_from_Type(Type t) {

    switch (t.getType()) {
      case Const.T_BOOLEAN:
      case Const.T_CHAR:
      case Const.T_BYTE:
      case Const.T_SHORT:
      case Const.T_INT:
        return new StackMapType(Const.ITEM_Integer, -1, pool.getConstantPool());
      case Const.T_FLOAT:
        return new StackMapType(Const.ITEM_Float, -1, pool.getConstantPool());
      case Const.T_DOUBLE:
        return new StackMapType(Const.ITEM_Double, -1, pool.getConstantPool());
      case Const.T_LONG:
        return new StackMapType(Const.ITEM_Long, -1, pool.getConstantPool());
      case Const.T_ARRAY:
      case Const.T_OBJECT:
        return new StackMapType(
            Const.ITEM_Object, pool.addClass(typeToClassGetName(t)), pool.getConstantPool());
        // UNKNOWN seems to be used for Uninitialized objects.
        // The second argument to the constructor should be the code offset
        // of the corresponding 'new' instruction.  Just using 0 for now.
      case Const.T_UNKNOWN:
        return new StackMapType(Const.ITEM_NewObject, 0, pool.getConstantPool());
      default:
        throw new RuntimeException("Invalid type: " + t + t.getType());
    }
  }

  /**
   * Convert a StackMapType to a Type.
   *
   * @param smt StackMapType to be converted
   * @return result Type
   */
  protected final Type generate_Type_from_StackMapType(StackMapType smt) {

    switch (smt.getType()) {
      case Const.ITEM_Integer:
      case Const.ITEM_Bogus: // not sure about this; reresents 'top'?
        return Type.INT;
      case Const.ITEM_Float:
        return Type.FLOAT;
      case Const.ITEM_Double:
        return Type.DOUBLE;
      case Const.ITEM_Long:
        return Type.LONG;
      case Const.ITEM_Object:
        return Type.OBJECT;
      default:
        Thread.dumpStack();
        assert false : "Invalid StackMapType: " + smt + smt.getType();
        throw new RuntimeException("Invalid StackMapType: " + smt + smt.getType());
    }
  }

  /**
   * @param smt a StackMapType object
   * @return operand size of this type (2 for long and double, 1 otherwise)
   */
  protected final int getSize(StackMapType smt) {
    switch (smt.getType()) {
      case Const.ITEM_Double:
      case Const.ITEM_Long:
        return 2;
      default:
        return 1;
    }
  }

  /**
   * Update any FULL_FRAME StackMap entries to include a new local var. The locals array is a copy
   * of the local variables PRIOR to the addition of the new local in question.
   *
   * @param offset offset into stack of the new variable we are adding
   * @param type_new_var type of new variable we are adding
   * @param locals a copy of the local variable table prior to this modification
   */
  protected final void update_full_frame_stack_map_entries(
      int offset, Type type_new_var, LocalVariableGen[] locals) {
    @NonNegative int index; // locals index

    for (int i = 0; i < stack_map_table.length; i++) {
      if (stack_map_table[i].getFrameType() == Const.FULL_FRAME) {

        int num_locals = stack_map_table[i].getNumberOfLocals();
        StackMapType[] new_local_types = new StackMapType[num_locals + 1];
        StackMapType[] old_local_types = stack_map_table[i].getTypesOfLocals();

        // System.out.printf ("update_full_frame %s %s %s %n", offset, num_locals, locals.length);

        for (index = 0; index < num_locals; index++) {
          if (index >= locals.length) {
            // there are hidden compiler temps in map
            break;
          }
          if (locals[index].getIndex() >= offset) {
            // we've reached the point of insertion
            break;
          }
          new_local_types[index] = old_local_types[index];
        }
        new_local_types[index++] = generate_StackMapType_from_Type(type_new_var);
        while (index <= num_locals) {
          new_local_types[index] = old_local_types[index - 1];
          index++;
        }

        stack_map_table[i].setTypesOfLocals(new_local_types);
      }
    }
  }

  /**
   * Add a new parameter to the method. This will be added after last current parameter and before
   * the first local variable. This might have the side effect of causing us to rewrite the method
   * byte codes to adjust the offsets for the local variables - see below for details.
   *
   *
Must call fix_local_variable_table (just once per method) before calling this routine.
   *
   * @param mgen MethodGen to be modified
   * @param arg_name name of new parameter
   * @param arg_type type of new parameter
   * @return a LocalVariableGen for the new parameter
   * @deprecated use {@link #add_new_parameter}
   */
  @Deprecated // use add_new_parameter()
  protected final LocalVariableGen add_new_argument(
      MethodGen mgen, String arg_name, Type arg_type) {
    return add_new_parameter(mgen, arg_name, arg_type);
  }

  /**
   * Add a new parameter to the method. This will be added after last current parameter and before
   * the first local variable. This might have the side effect of causing us to rewrite the method
   * byte codes to adjust the offsets for the local variables - see below for details.
   *
   *
Must call fix_local_variable_table (just once per method) before calling this routine.
   *
   * @param mgen MethodGen to be modified
   * @param arg_name name of new parameter
   * @param arg_type type of new parameter
   * @return a LocalVariableGen for the new parameter
   */
  protected final LocalVariableGen add_new_parameter(
      MethodGen mgen, String arg_name, Type arg_type) {
    // We add a new parameter, after any current ones, and then
    // we need to make a pass over the byte codes to update the local
    // offset values of all the locals we just shifted up.  This may have
    // a 'knock on' effect if we are forced to change an instruction that
    // references implict local #3 to an instruction with an explict
    // reference to local #4 as this would require the insertion of an
    // offset into the byte codes. This means we would need to make an
    // additional pass to update branch targets (no - BCEL does this for
    // us) and the StackMapTable (yes - BCEL should do this, but it doesn't).
    //

    LocalVariableGen arg_new = null;
    // get a copy of the locals before modification
    LocalVariableGen[] locals = mgen.getLocalVariables();
    Type[] arg_types = mgen.getArgumentTypes();
    int new_index = 0;
    int new_offset = 0;

    boolean has_code = (mgen.getInstructionList() != null);

    if (has_code) {
      if (!mgen.isStatic()) {
        // Skip the 'this' pointer.
        new_index++;
        new_offset++; // size of 'this' is 1
      }

      if (arg_types.length > 0) {
        LocalVariableGen last_arg;
        new_index = new_index + arg_types.length;
        // new_index is now positive, because arg_types.length is
        last_arg = locals[new_index - 1];
        new_offset = last_arg.getIndex() + (last_arg.getType()).getSize();
      }

      // Insert our new local variable into existing table at 'new_offset'.
      arg_new = mgen.addLocalVariable(arg_name, arg_type, new_offset, null, null);

      // Update the index of the first 'true' local in the local variable table.
      first_local_index++;
    }
    initial_locals_count++;

    // Update the method's parameter information.
    arg_types = BcelUtil.postpendToArray(arg_types, arg_type);
    String[] arg_names = add_string(mgen.getArgumentNames(), arg_name);
    mgen.setArgumentTypes(arg_types);
    mgen.setArgumentNames(arg_names);

    if (has_code) {
      // we need to adjust the offset of any locals after our insertion
      for (int i = new_index; i < locals.length; i++) {
        LocalVariableGen lv = locals[i];
        lv.setIndex(lv.getIndex() + arg_type.getSize());
      }
      mgen.setMaxLocals(mgen.getMaxLocals() + arg_type.getSize());

      debug_instrument.log(
          "Added arg    %s%n",
          arg_new.getIndex() + ": " + arg_new.getName() + ", " + arg_new.getType());

      // Now process the instruction list, adding one to the offset
      // within each LocalVariableInstruction that references a
      // local that is 'higher' in the local map than new local
      // we just inserted.
      adjust_code_for_locals_change(mgen, new_offset, arg_type.getSize());

      // Finally, we need to update any FULL_FRAME StackMap entries to
      // add in the new local variable type.
      update_full_frame_stack_map_entries(new_offset, arg_type, locals);

      debug_instrument.log("New LocalVariableTable:%n%s%n", mgen.getLocalVariableTable(pool));
    }
    return arg_new;
  }

  /**
   * Create a new local with a scope of the full method. This means we need to search the existing
   * locals to find the proper index for our new local. This might have the side effect of causing
   * us to rewrite the method byte codes to adjust the offsets for the existing local variables -
   * see below for details.
   *
   *
Must call fix_local_variable_table (just once per method) before calling this routine.
   *
   * @param mgen MethodGen to be modified
   * @param local_name name of new local
   * @param local_type type of new local
   * @return a LocalVariableGen for the new local
   */
  protected final LocalVariableGen create_method_scope_local(
      MethodGen mgen, String local_name, Type local_type) {
    // BCEL sorts local vars and presents them in offset order.  Search
    // locals for first var with start != 0. If none, just add the new
    // var at the end of the table and exit. Otherwise, insert the new
    // var just prior to the local we just found.
    // Now we need to make a pass over the byte codes to update the local
    // offset values of all the locals we just shifted up.  This may have
    // a 'knock on' effect if we are forced to change an instruction that
    // references implict local #3 to an instruction with an explict
    // reference to local #4 as this would require the insertion of an
    // offset into the byte codes. This means we would need to make an
    // additional pass to update branch targets (no - BCEL does this for
    // us) and the StackMapTable (yes - BCEL should do this, but it doesn't).
    //
    // We never want to insert our local prior to any parameters.  This would
    // happen naturally, but some old class files have non zero addresses
    // for 'this' and/or the parameters so we need to add an explicit
    // check to make sure we skip these variables.

    LocalVariableGen lv_new;
    int max_offset = 0;
    int new_offset = -1;
    // get a copy of the local before modification
    LocalVariableGen[] locals = mgen.getLocalVariables();
    @IndexOrLow("locals") int compiler_temp_i = -1;
    int new_index = -1;
    int i;

    for (i = 0; i < locals.length; i++) {
      LocalVariableGen lv = locals[i];
      if (i >= first_local_index) {
        if (lv.getStart().getPosition() != 0) {
          if (new_offset == -1) {
            if (compiler_temp_i != -1) {
              new_offset = locals[compiler_temp_i].getIndex();
              new_index = compiler_temp_i;
            } else {
              new_offset = lv.getIndex();
              new_index = i;
            }
          }
        }
      }

      // calculate max local size seen so far (+1)
      max_offset = lv.getIndex() + lv.getType().getSize();

      if (lv.getName().startsWith("DaIkOnTeMp")) {
        // Remember the index of a compiler temp.  We may wish
        // to insert our new local prior to a temp to simplfy
        // the generation of StackMaps.
        if (compiler_temp_i == -1) {
          compiler_temp_i = i;
        }
      } else {
        // If there were compiler temps prior to this local, we don't
        // need to worry about them as the compiler will have already
        // included them in the StackMaps. Reset the indicators.
        compiler_temp_i = -1;
      }
    }

    // We have looked at all the local variables; if we have not found
    // a place for our new local, check to see if last local was a
    // compiler temp and insert prior.
    if ((new_offset == -1) && (compiler_temp_i != -1)) {
      new_offset = locals[compiler_temp_i].getIndex();
      new_index = compiler_temp_i;
    }

    // If new_offset is still unset, we can just add our local at the
    // end.  There may be unnamed compiler temps here, so we need to
    // check for this (via max_offset) and move them up.
    if (new_offset == -1) {
      new_offset = max_offset;
      if (new_offset < mgen.getMaxLocals()) {
        mgen.setMaxLocals(mgen.getMaxLocals() + local_type.getSize());
      }
      lv_new = mgen.addLocalVariable(local_name, local_type, new_offset, null, null);
    } else {
      // insert our new local variable into existing table at 'new_offset'
      lv_new = mgen.addLocalVariable(local_name, local_type, new_offset, null, null);
      // we need to adjust the offset of any locals after our insertion
      for (i = new_index; i < locals.length; i++) {
        LocalVariableGen lv = locals[i];
        lv.setIndex(lv.getIndex() + local_type.getSize());
      }
      mgen.setMaxLocals(mgen.getMaxLocals() + local_type.getSize());
    }

    debug_instrument.log(
        "Added local  %s%n", lv_new.getIndex() + ": " + lv_new.getName() + ", " + lv_new.getType());

    // Now process the instruction list, adding one to the offset
    // within each LocalVariableInstruction that references a
    // local that is 'higher' in the local map than new local
    // we just inserted.
    adjust_code_for_locals_change(mgen, new_offset, local_type.getSize());

    // Finally, we need to update any FULL_FRAME StackMap entries to
    // add in the new local variable type.
    update_full_frame_stack_map_entries(new_offset, local_type, locals);

    debug_instrument.log("New LocalVariableTable:%n%s%n", mgen.getLocalVariableTable(pool));
    return lv_new;
  }

  /**
   * Under some circumstances, there may be problems with the local variable table. These problems
   * occur when the Java compiler adds unnamed entries. There may be unnamed parameters and/or
   * unnamed local variables. These items appear as gaps in the LocalVariable table. This routine
   * creates LocalVariable entries for these missing items.
   *
   *

   *
The java Compiler allocates a hidden parameter for the constructor of an inner class.
   *       These items are given the name $hidden$ appended with their offset.
   *
The Java compiler allocates unnamed local temps for:
   *

   *
saving the exception in a finally clause
   *
the lock for a synchronized block
   *
interators
   *
(others?)
   *

   *       These items are given the name DaIkOnTeMp appended with their offset.
   *

   *
   * @param mgen MethodGen to be modified
   */
  protected final void fix_local_variable_table(MethodGen mgen) {
    InstructionList il = mgen.getInstructionList();
    if (il == null) {
      // no code so nothing to do
      first_local_index = 0;
      return;
    }

    // Get the current local variables (includes 'this' and parameters)
    LocalVariableGen[] locals = mgen.getLocalVariables();
    LocalVariableGen l;
    LocalVariableGen new_lvg;

    // We need a deep copy
    for (int ii = 0; ii < locals.length; ii++) {
      locals[ii] = (LocalVariableGen) (locals[ii].clone());
    }

    // The arg types are correct and include all parameters.
    Type[] arg_types = mgen.getArgumentTypes();

    // Initial offset into the stack frame
    int offset = 0;

    // Index into locals of the first parameter
    int loc_index = 0;

    // Rarely, the java compiler gets the max locals count wrong (too big).
    // This would cause problems for us later, so we need to recalculate
    // the highest local used based on looking at code offsets.
    mgen.setMaxLocals();
    int max_locals = mgen.getMaxLocals();
    // Remove the existing locals
    mgen.removeLocalVariables();
    // Reset MaxLocals to 0 and let code below rebuild it.
    mgen.setMaxLocals(0);

    // Determine the first 'true' local index into the local variables.
    // The object 'this' pointer and the parameters form the first n
    // entries in the list.
    first_local_index = arg_types.length;

    if (!mgen.isStatic()) {
      // Add the 'this' pointer back in.
      l = locals[0];
      new_lvg = mgen.addLocalVariable(l.getName(), l.getType(), l.getIndex(), null, null);
      debug_instrument.log(
          "Added <this> %s%n",
          new_lvg.getIndex() + ": " + new_lvg.getName() + ", " + new_lvg.getType());
      loc_index = 1;
      offset = 1;
      first_local_index++;
    }

    // Loop through each parameter
    for (int ii = 0; ii < arg_types.length; ii++) {

      // If this parameter doesn't have a matching local
      if ((loc_index >= locals.length) || (offset != locals[loc_index].getIndex())) {

        // Create a local variable to describe the missing parameter
        new_lvg = mgen.addLocalVariable("$hidden$" + offset, arg_types[ii], offset, null, null);
      } else {
        l = locals[loc_index];
        new_lvg = mgen.addLocalVariable(l.getName(), l.getType(), l.getIndex(), null, null);
        loc_index++;
      }
      debug_instrument.log(
          "Added param  %s%n",
          new_lvg.getIndex() + ": " + new_lvg.getName() + ", " + new_lvg.getType());
      offset += arg_types[ii].getSize();
    }

    // At this point the LocalVaraibles contain:
    //   the 'this' pointer (if present)
    //   the parameters to the method
    // This will be used to construct the initial state of the
    // StackMapTypes.
    LocalVariableGen[] initial_locals = mgen.getLocalVariables();
    initial_locals_count = initial_locals.length;
    initial_type_list = new StackMapType[initial_locals_count];
    for (int ii = 0; ii < initial_locals_count; ii++) {
      initial_type_list[ii] = generate_StackMapType_from_Type(initial_locals[ii].getType());
    }

    // Add back the true locals
    //
    // NOTE that the Java compiler uses unnamed local temps for:
    //   saving the exception in a finally clause
    //   the lock for a synchronized block
    //   iterators
    //   (others?)
    // We will create a 'fake' local for these cases.

    for (int ii = first_local_index; ii < locals.length; ii++) {
      l = locals[ii];
      if (l.getIndex() > offset) {
        // A gap in index values indicates a compiler allocated temp.
        // (if offset is 0, probably a lock object)
        // there is at least one hidden compiler temp before the next local
        offset = gen_temp_locals(mgen, offset);
        ii--; // need to revisit same local
      } else {
        new_lvg =
            mgen.addLocalVariable(l.getName(), l.getType(), l.getIndex(), l.getStart(), l.getEnd());
        debug_instrument.log(
            "Added local  %s%n",
            new_lvg.getIndex() + ": " + new_lvg.getName() + ", " + new_lvg.getType());
        offset = new_lvg.getIndex() + (new_lvg.getType()).getSize();
      }
    }

    // check for hidden temps after last declared local.
    while (offset < max_locals) {
      offset = gen_temp_locals(mgen, offset);
    }

    // Recalculate the highest local used based on looking at code offsets.
    mgen.setMaxLocals();
  }

  /**
   * Calculates the types on the stack for each instruction using the BCEL stack verification
   * routines.
   *
   * @param mg MethodGen for the method to be analyzed
   * @return a StackTypes object for the method
   */
  protected final StackTypes bcel_calc_stack_types(MethodGen mg) {

    StackVer stackver = new StackVer();
    VerificationResult vr;
    try {
      vr = stackver.do_stack_ver(mg);
    } catch (Throwable t) {
      System.out.printf("Warning: StackVer exception for %s.%s%n", mg.getClassName(), mg.getName());
      System.out.printf("Exception: %s%n", t);
      System.out.printf("Method is NOT instrumented%n");
      return null;
    }
    if (vr != VerificationResult.VR_OK) {
      System.out.printf(
          "Warning: StackVer failed for %s.%s: %s%n", mg.getClassName(), mg.getName(), vr);
      System.out.printf("Method is NOT instrumented%n");
      return null;
    }
    assert vr == VerificationResult.VR_OK : " vr failed " + vr;
    return stackver.get_stack_types();
  }
}


package org.plumelib.bcelutil;

import java.io.File;
import java.io.PrintStream;
import java.util.Iterator;
import org.apache.bcel.Const;
import org.apache.bcel.classfile.Attribute;
import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.Constant;
import org.apache.bcel.classfile.ConstantClass;
import org.apache.bcel.classfile.ConstantPool;
import org.apache.bcel.classfile.ConstantUtf8;
import org.apache.bcel.classfile.Field;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ClassGen;
import org.apache.bcel.generic.CodeExceptionGen;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.InstructionTargeter;
import org.apache.bcel.generic.LineNumberGen;
import org.apache.bcel.generic.LocalVariableGen;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.RETURN;
import org.apache.bcel.generic.Type;
import org.checkerframework.checker.index.qual.SameLen;
import org.checkerframework.checker.signature.qual.BinaryName;
import org.checkerframework.checker.signature.qual.ClassGetName;
import org.checkerframework.checker.signature.qual.InternalForm;
import org.plumelib.reflection.ReflectionPlume;
import org.plumelib.reflection.Signatures;

/** Static utility methods for working with BCEL. */
public final class BcelUtil {
  /** This class is a collection of methods; it does not represent anything. */
  private BcelUtil() {
    throw new Error("do not instantiate");
  }

  /** Controls whether the checks in {@link #checkMgen} are performed. */
  public static boolean skipChecks = false;

  /** The type that represents String[]. */
  private static final Type stringArray = Type.getType("[Ljava.lang.String;");

  // 'ToString' methods

  /**
   * Returns a string describing a method declaration. It contains the access flags (public,
   * private, static, etc), the return type, the method name, and the types of each of its
   * parameters.
   *
   *
For example, if the orignal Java source declarationwas: private final String
   * constantToString (int index) Then the output of methodDeclarationToString would be: private
   * final java.lang.String constantToString (int)
   *
   * @param m the method
   * @return a string describing the method declaration
   */
  public static String methodDeclarationToString(Method m) {

    StringBuilder sb = new StringBuilder();
    String flags = accessFlagsToString(m);
    boolean argsExist = false;
    if (flags != null && !flags.isEmpty()) {
      sb.append(String.format("%s ", flags));
    }
    sb.append(String.format("%s %s(", m.getReturnType(), m.getName()));
    for (Type at : m.getArgumentTypes()) {
      sb.append(String.format("%s, ", at));
      argsExist = true;
    }
    if (argsExist) {
      sb.setLength(sb.length() - 2); // remove trailing ", "
    }
    sb.append(")");
    return sb.toString();
  }

  /**
   * Return a string representation of the access flags of method m. In the string, the flags are
   * space-separated and in a canonical order.
   *
   * @param m the method whose access flags to retrieve
   * @return a string representation of the access flags of method m
   */
  static String accessFlagsToString(Method m) {

    int flags = m.getAccessFlags();

    StringBuilder buf = new StringBuilder();
    // Note that pow is a binary mask for the flag (= 2^i).
    for (int i = 0, pow = 1; i <= Const.MAX_ACC_FLAG; i++) {
      if ((flags & pow) != 0) {
        if (buf.length() > 0) {
          buf.append(" ");
        }
        if (i < Const.ACCESS_NAMES_LENGTH) {
          buf.append(Const.getAccessName(i));
        } else {
          buf.append(String.format("ACC_BIT(%x)", pow));
        }
      }
      pow <<= 1;
    }

    return buf.toString();
  }

  /**
   * Return a printed description of the given instructions.
   *
   * @param il the instructions to describe
   * @param pool the constant pool the instructions refer to
   * @return a printed representation of the instructions in {@code il}
   */
  public static String instructionListToString(InstructionList il, ConstantPoolGen pool) {

    StringBuilder out = new StringBuilder();
    for (Iterator<InstructionHandle> i = il.iterator(); i.hasNext(); ) {
      InstructionHandle handle = i.next();
      out.append(handle.getInstruction().toString(pool.getConstantPool()) + "\n");
    }
    return out.toString();
  }

  /**
   * Return a description of the local variables (one per line).
   *
   * @param mg the method whose local variables to describe
   * @return a description of the local variables (one per line)
   */
  public static String localVariablesToString(MethodGen mg) {

    StringBuilder out = new StringBuilder();
    out.append(String.format("Locals for %s [cnt %d]%n", mg, mg.getMaxLocals()));
    LocalVariableGen[] lvgs = mg.getLocalVariables();
    if ((lvgs != null) && (lvgs.length > 0)) {
      for (LocalVariableGen lvg : lvgs) {
        out.append(String.format("  %s [index %d]%n", lvg, lvg.getIndex()));
      }
    }
    return out.toString();
  }

  /**
   * Return the attribute name for the specified attribute, looked up in the original class file
   * ConstantPool.
   *
   * @param a the attribute
   * @return the attribute name for the specified attribute
   */
  public static String attributeNameToString(Attribute a) {

    ConstantPool pool = a.getConstantPool();
    int conIndex = a.getNameIndex();
    Constant c = pool.getConstant(conIndex);
    String attName = ((ConstantUtf8) c).getBytes();
    return attName;
  }

  /**
   * Return the attribute name for the specified attribute, looked up in the given ConstantPoolGen.
   *
   * @param a the attribute
   * @param pool the constant pool
   * @return the attribute name for the specified attribute
   */
  public static String attributeNameToString(Attribute a, ConstantPoolGen pool) {

    int conIndex = a.getNameIndex();
    Constant c = pool.getConstant(conIndex);
    String attName = ((ConstantUtf8) c).getBytes();
    return attName;
  }

  // 'is' (boolean test) methods

  /**
   * Returns whether or not the method is a constructor.
   *
   * @param mg the MethodGen to test
   * @return true iff the method is a constructor
   */
  public static boolean isConstructor(MethodGen mg) {
    if (mg.getName().equals("")) {
      throw new Error("method name cannot be empty");
    }
    return mg.getName().equals("<init>");
  }

  /**
   * Returns whether or not the method is a constructor.
   *
   * @param m the Method to test
   * @return true iff the method is a constructor
   */
  public static boolean isConstructor(Method m) {
    if (m.getName().equals("")) {
      throw new Error("method name cannot be empty");
    }
    return m.getName().equals("<init>");
  }

  /**
   * Returns whether or not the method is a class initializer.
   *
   * @param mg the method to test
   * @return true iff the method is a class initializer
   */
  public static boolean isClinit(MethodGen mg) {
    return mg.getName().equals("<clinit>");
  }

  /**
   * Returns whether or not the method is a class initializer.
   *
   * @param m the method to test
   * @return true iff the method is a class initializer
   */
  public static boolean isClinit(Method m) {
    return m.getName().equals("<clinit>");
  }

  /**
   * Returns whether or not the class is part of the JDK (rt.jar).
   *
   * @param gen the class to test
   * @return true iff the class is in a package that is in the JDK (rt.jar)
   */
  public static boolean inJdk(ClassGen gen) {
    return inJdk(gen.getClassName());
  }

  /**
   * Returns whether or not the class is part of the JDK (rt.jar).
   *
   * @param classname the class to test, in the format of Class.getName(); the class should not be
   *     an array
   * @return true iff the class is in a package that is in the JDK (rt.jar)
   */
  public static boolean inJdk(@ClassGetName String classname) {
    return classname.startsWith("java.")
        || classname.startsWith("com.oracle.")
        || classname.startsWith("com.sun.")
        || classname.startsWith("javax.")
        || classname.startsWith("jdk.")
        || classname.startsWith("org.ietf.")
        || classname.startsWith("org.jcp.")
        || classname.startsWith("org.omg.")
        || classname.startsWith("org.w3c.")
        || classname.startsWith("org.xml.")
        || classname.startsWith("sun.")
        || classname.startsWith("sunw.");
  }

  /**
   * Returns whether or not the class is part of the JDK (rt.jar).
   *
   * @param classname the class to test, in internal form
   * @return true iff the class is part of the JDK (rt.jar)
   */
  public static boolean inJdkInternalform(@InternalForm String classname) {
    return classname.startsWith("java/")
        || classname.startsWith("com/oracle/")
        || classname.startsWith("com/sun/")
        || classname.startsWith("javax/")
        || classname.startsWith("jdk/")
        || classname.startsWith("org/ietj/")
        || classname.startsWith("org/jcp/")
        || classname.startsWith("org/omg/")
        || classname.startsWith("org/w3c/")
        || classname.startsWith("org/xml/")
        || classname.startsWith("sun/")
        || classname.startsWith("sunw/");
  }

  /**
   * Returns whether or not the specified attribute is a local variable type table.
   *
   * @param a the attribute
   * @param pool the constant pool
   * @return true iff the attribute is a local variable type table
   */
  public static boolean isLocalVariableTypeTable(Attribute a, ConstantPoolGen pool) {
    return attributeNameToString(a, pool).equals("LocalVariableTypeTable");
  }

  /**
   * Returns whether or not this is a standard main method (static, void, name is 'main', and one
   * formal parameter: a string array).
   *
   * @param mg the method to check
   * @return true iff the method is a main method
   */
  public static boolean isMain(MethodGen mg) {
    Type[] argTypes = mg.getArgumentTypes();
    return mg.isStatic()
        && (mg.getReturnType() == Type.VOID)
        && mg.getName().equals("main")
        && (argTypes.length == 1)
        && argTypes[0].equals(stringArray);
  }

  // consistency check methods

  /**
   * Checks the specified method for consistency.
   *
   *
Does nothing if {@link #skipChecks} is false.
   *
   * @param mgen the class to check
   */
  public static void checkMgen(MethodGen mgen) {

    if (skipChecks) {
      return;
    }

    try {
      mgen.toString(); // ensure it can be formatted without exceptions
      mgen.getLineNumberTable(mgen.getConstantPool());

      InstructionList ilist = mgen.getInstructionList();
      if (ilist == null || ilist.getStart() == null) {
        return;
      }
      CodeExceptionGen[] exceptionHandlers = mgen.getExceptionHandlers();
      for (CodeExceptionGen gen : exceptionHandlers) {
        assert ilist.contains(gen.getStartPC())
            : "exception handler "
                + gen
                + " has been forgotten in "
                + mgen.getClassName()
                + "."
                + mgen.getName();
      }
      MethodGen nmg = new MethodGen(mgen.getMethod(), mgen.getClassName(), mgen.getConstantPool());
      nmg.getLineNumberTable(mgen.getConstantPool());
    } catch (Throwable t) {
      Error e =
          new Error(
              String.format(
                  "failure while checking method %s.%s%n", mgen.getClassName(), mgen.getName()),
              t);
      e.printStackTrace();
      throw e;
    }
  }

  /**
   * Checks all of the methods in gen for consistency.
   *
   * @param gen the class to check
   */
  public static void checkMgens(final ClassGen gen) {

    if (skipChecks) {
      return;
    }

    Method[] methods = gen.getMethods();
    for (int i = 0; i < methods.length; i++) {
      Method method = methods[i];
      // System.out.println ("Checking method " + method + " in class "
      // + gen.getClassName());
      checkMgen(new MethodGen(method, gen.getClassName(), gen.getConstantPool()));
    }

    // Diagnostic output
    if (false) {
      dumpStackTrace();
      dumpMethods(gen);
    }
  }

  // 'dump' methods

  /** Print the current java call stack. */
  public static void dumpStackTrace() {

    StackTraceElement[] ste = Thread.currentThread().getStackTrace();
    // [0] is getStackTrace
    // [1] is dumpStackTrace
    if (ste.length < 3) {
      System.out.println("No stack trace information available");
    } else {
      StackTraceElement caller = ste[2];
      System.out.printf(
          "%s.%s (%s line %d)",
          caller.getClassName(),
          caller.getMethodName(),
          caller.getFileName(),
          caller.getLineNumber());
      for (int ii = 3; ii < ste.length; ii++) {
        System.out.printf(" [%s line %d]", ste[ii].getFileName(), ste[ii].getLineNumber());
      }
      System.out.printf("%n");
    }
  }

  /**
   * Print the methods in the class, to standard output.
   *
   * @param gen the class whose methods to print
   */
  static void dumpMethods(ClassGen gen) {

    System.out.printf("Class %s methods:%n", gen.getClassName());
    for (Method m : gen.getMethods()) {
      System.out.printf("  %s%n", m);
    }
  }

  /**
   * Dumps the contents of the specified class to the specified directory. The file is named
   * dumpDir/[class].bcel. It contains a synopsis of the fields and methods followed by the JVM code
   * for each method.
   *
   * @param jc JavaClass to dump
   * @param dumpDir directory in which to write the file
   * @see #dump(JavaClass, File)
   */
  public static void dump(JavaClass jc, String dumpDir) {

    dump(jc, new File(dumpDir));
  }

  /**
   * Dumps the contents of the specified class to the specified directory. The file is named
   * dumpDir/[class].bcel. It contains a synopsis of the fields and methods followed by the JVM code
   * for each method.
   *
   * @param jc JavaClass to dump
   * @param dumpDir directory in which to write the file
   */
  public static void dump(JavaClass jc, File dumpDir) {

    try {
      dumpDir.mkdir();
      File path = new File(dumpDir, jc.getClassName() + ".bcel");
      PrintStream p = new PrintStream(path);

      // Print the class, superclass, and interfaces
      p.printf("class %s extends %s%n", jc.getClassName(), jc.getSuperclassName());
      String[] inames = jc.getInterfaceNames();
      boolean first = true;
      if ((inames != null) && (inames.length > 0)) {
        p.printf("   implements ");
        for (String iname : inames) {
          if (!first) p.printf(", ");
          p.printf("%s", iname);
          first = false;
        }
        p.printf("%n");
      }

      // Print each field
      p.printf("%nFields%n");
      for (Field f : jc.getFields()) {
        p.printf("  %s%n", f);
      }

      // Print the signature of each method
      p.printf("%nMethods%n");
      for (Method m : jc.getMethods()) {
        p.printf("  %s%n", m);
      }

      for (Method m : jc.getMethods()) {
        Code code = m.getCode();
        if (code != null) {
          p.printf("%nMethod %s%n", m);
          p.printf("  %s%n", code.toString().replace("\n", "\n  "));
        }
      }

      // Print the details of the constant pool.
      p.printf("Constant Pool:%n");
      ConstantPool cp = jc.getConstantPool();
      Constant[] constants = cp.getConstantPool();
      for (int ii = 0; ii < constants.length; ii++) {
        p.printf("  %d %s%n", ii, constants[ii]);
      }

      p.close();

    } catch (Exception e) {
      throw new Error(
          "Unexpected error dumping JavaClass: " + jc.getClassName() + " to " + dumpDir.getName(),
          e);
    }
  }

  // miscellaneous methods

  /**
   * Adds instructions to the start of a method.
   *
   * @param mg method to be augmented
   * @param newList instructions to prepend to the method
   */
  public static void addToStart(MethodGen mg, InstructionList newList) {

    // Add the code before the first instruction
    InstructionList il = mg.getInstructionList();
    InstructionHandle oldStart = il.getStart();
    InstructionHandle newStart = il.insert(newList);

    // Move any LineNumbers and local variables that currently point to
    // the first instruction to include the new instructions. Other
    // targeters (branches, exceptions) should not include the new code.
    if (oldStart.hasTargeters()) {
      // getTargeters() returns non-null because hasTargeters => true
      for (InstructionTargeter it : oldStart.getTargeters()) {
        if ((it instanceof LineNumberGen) || (it instanceof LocalVariableGen)) {
          it.updateTarget(oldStart, newStart);
        }
      }
    }
    mg.setMaxStack();
    mg.setMaxLocals();
  }

  /**
   * Returns the constant string at the specified offset.
   *
   * @param pool the constant pool
   * @param index the index in the constant pool
   * @return the constant string at the specified offset in the constant pool
   */
  public static String getConstantString(ConstantPool pool, int index) {

    Constant c = pool.getConstant(index);
    assert c != null : "Bad index " + index + " into pool";
    if (c instanceof ConstantUtf8) {
      return ((ConstantUtf8) c).getBytes();
    } else if (c instanceof ConstantClass) {
      ConstantClass cc = (ConstantClass) c;
      return cc.getBytes(pool) + " [" + cc.getNameIndex() + "]";
    } else {
      throw new Error("unexpected constant " + c + " of class " + c.getClass());
    }
  }

  /**
   * Sets the locals to be the formal parameters. Any other locals are removed. An instruction list
   * with at least one instruction must exist.
   *
   * @param mg the method whose locals to set
   */
  public static void resetLocalsToFormals(MethodGen mg) {

    // Get the parameter types and names.
    @SuppressWarnings("nullness" // The annotation arguments might not be initialized yet.
    // Since the arguments are not executed at run time, there is no null pointer exception.
    )
    Type @SameLen({"argTypes", "mg.getArgumentTypes()"}) [] argTypes = mg.getArgumentTypes();
    @SuppressWarnings("nullness" // The annotation arguments might not be initialized yet.
    // Since the arguments are not executed at run time, there is no null pointer exception.
    )
    String @SameLen({"argTypes", "argNames", "mg.getArgumentTypes()", "mg.getArgumentNames()"}) []
        argNames = mg.getArgumentNames();

    // Remove any existing locals
    mg.setMaxLocals(0);
    mg.removeLocalVariables();

    // Add a local for the instance variable (this)
    if (!mg.isStatic()) {
      mg.addLocalVariable("this", new ObjectType(mg.getClassName()), null, null);
    }

    // Add a local for each parameter
    for (int ii = 0; ii < argNames.length; ii++) {
      mg.addLocalVariable(argNames[ii], argTypes[ii], null, null);
    }

    // Reset the current number of locals so that when other locals
    // are added they get added at the correct offset.
    mg.setMaxLocals();

    return;
  }

  /**
   * Empties the method of all code (except for a return). This includes line numbers, exceptions,
   * local variables, etc.
   *
   * @param mg the method to clear out
   */
  public static void makeMethodBodyEmpty(MethodGen mg) {

    mg.setInstructionList(new InstructionList(new RETURN()));
    mg.removeExceptionHandlers();
    mg.removeLineNumbers();
    mg.removeLocalVariables();
    mg.setMaxLocals();
  }

  /**
   * Remove the local variable type table attribute (LVTT) from mg. Evidently some changes require
   * this to be updated, but without BCEL support that would be hard to do. It should be safe to
   * just delete it since it is optional and really only of use to a debugger.
   *
   * @param mg the method to clear out
   */
  public static void removeLocalVariableTypeTables(MethodGen mg) {

    for (Attribute a : mg.getCodeAttributes()) {
      if (isLocalVariableTypeTable(a, mg.getConstantPool())) {
        mg.removeCodeAttribute(a);
      }
    }
  }

  /**
   * Returns the Java class name, in the format of {@link Class#getName()}, that corresponds to
   * type.
   *
   * @param type the type
   * @return the Java classname that corresponds to type
   */
  public static @ClassGetName String typeToClassgetname(Type type) {
    String signature = type.getSignature();
    return Signatures.fieldDescriptorToClassGetName(signature);
  }

  /**
   * Returns the class that corresponds to type.
   *
   * @param type the type
   * @return the Java class that corresponds to type
   */
  public static Class<?> typeToClass(Type type) {

    String classname = typeToClassgetname(type);
    try {
      return ReflectionPlume.classForName(classname);
    } catch (Exception e) {
      throw new RuntimeException("can't find class for " + classname, e);
    }
  }

  /**
   * Returns a copy of the given type array, with newType added to the end.
   *
   * @param types the array to extend
   * @param newType the element to add to the end of the array
   * @return a new array, with newType at the end
   */
  public static Type[] postpendToArray(Type[] types, Type newType) {
    if (types.length == Integer.MAX_VALUE) {
      throw new Error("array " + types + " is too large to extend");
    }
    Type[] newTypes = new Type[types.length + 1];
    System.arraycopy(types, 0, newTypes, 0, types.length);
    newTypes[types.length] = newType;
    return newTypes;
  }

  /**
   * Returns a copy of the given type array, with newType inserted at the beginning.
   *
   * @param types the array to extend
   * @param newType the element to add to the beginning of the array
   * @return a new array, with newType at the beginning
   */
  public static Type[] prependToArray(Type newType, Type[] types) {
    if (types.length == Integer.MAX_VALUE) {
      throw new Error("array " + types + " is too large to extend");
    }
    Type[] newTypes = new Type[types.length + 1];
    System.arraycopy(types, 0, newTypes, 1, types.length);
    newTypes[0] = newType;
    return newTypes;
  }

  /**
   * Return the type corresponding to a given class name.
   *
   * @param classname the binary name of a class (= fully-qualified name, except for inner classes)
   * @return the type corresponding to the given class name
   */
  public static Type classnameToType(@BinaryName String classname) {

    classname = classname.intern();

    if (classname == "int") { // interned
      return Type.INT;
    } else if (classname == "boolean") { // interned
      return Type.BOOLEAN;
    } else if (classname == "byte") { // interned
      return Type.BYTE;
    } else if (classname == "char") { // interned
      return Type.CHAR;
    } else if (classname == "double") { // interned
      return Type.DOUBLE;
    } else if (classname == "float") { // interned
      return Type.FLOAT;
    } else if (classname == "long") { // interned
      return Type.LONG;
    } else if (classname == "short") { // interned
      return Type.SHORT;
    } else { // must be a non-primitive
      return new ObjectType(classname);
    }
  }
}


package org.plumelib.bcelutil;

import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.Type;
import org.apache.bcel.verifier.structurals.Frame;
import org.apache.bcel.verifier.structurals.LocalVariables;
import org.apache.bcel.verifier.structurals.OperandStack;
import org.apache.bcel.verifier.structurals.UninitializedObjectType;
import org.checkerframework.checker.index.qual.IndexFor;
import org.checkerframework.checker.index.qual.SameLen;
import org.checkerframework.checker.lock.qual.GuardSatisfied;
import org.checkerframework.dataflow.qual.SideEffectFree;

/**
 * Stores the types on the stack at each instruction (identified by byte code offset) in a method.
 */
public final class StackTypes {

  /**
   * The state of the operand stack at each instruction location. The instruction's byte code offset
   * is used as the index.
   */
  OperandStack @SameLen("loc_arr") [] os_arr;

  /**
   * The state of the live local variables at each instruction location. The instruction's byte code
   * offset is used as the index.
   */
  LocalVariables @SameLen("os_arr") [] loc_arr;

  /**
   * Create a record of the types on the stack at each instruction in a method. The created object
   * starts out empty, with no type information.
   *
   * @param mg the method
   */
  public StackTypes(MethodGen mg) {
    InstructionList il = mg.getInstructionList();
    int size = (il == null) ? 0 : il.getEnd().getPosition();
    os_arr = new OperandStack[size + 1];
    loc_arr = new LocalVariables[size + 1];
  }

  /**
   * Sets the stack for the instruction at the specified offset to a copy of the information in the
   * given frame.
   *
   * @param offset the offset at which the instruction appears
   * @param f the stack frame to use for the instruction
   */
  public void set(@IndexFor({"loc_arr", "os_arr"}) int offset, Frame f) {

    OperandStack os = f.getStack();
    // logger.info ("stack[" + offset + "] = " + toString(os));

    loc_arr[offset] = (LocalVariables) (f.getLocals().clone());
    os_arr[offset] = (OperandStack) (os.clone());
  }

  /**
   * Returns the stack contents at the specified offset.
   *
   * @param offset the offset to which to get the stack contents
   * @return the stack at the (instruction at the) given offset
   */
  public OperandStack get(@IndexFor({"loc_arr", "os_arr"}) int offset) {
    return os_arr[offset];
  }

  @SuppressWarnings({"purity", "lock"}) // local StringBuilder
  @SideEffectFree
  @Override
  public String toString(@GuardSatisfied StackTypes this) {

    StringBuilder sb = new StringBuilder();

    for (int i = 0; i < os_arr.length; i++) {
      if (os_arr[i] != null) {
        sb.append(String.format("Instruction %d:\n", i));
        sb.append(String.format("  stack:  %s\n", toString(os_arr[i])));
        sb.append(String.format("  locals: %s\n", toString(loc_arr[i])));
      }
    }

    return sb.toString();
  }

  /**
   * Return a printed representation of the given OperandStack.
   *
   * @param os the OperandStack to print
   * @return a printed representation of {@code os}
   */
  @SideEffectFree
  public String toString(@GuardSatisfied StackTypes this, OperandStack os) {

    String buff = "";

    for (int i = 0; i < os.size(); i++) {
      if (buff.length() > 0) buff += ", ";
      Type t = os.peek(i);
      if (t instanceof UninitializedObjectType) {
        buff += "uninitialized-object";
      } else {
        buff += t;
      }
    }

    return "{" + buff + "}";
  }

  /**
   * Return a printed representation of the given LocalVariables.
   *
   * @param lv the LocalVariablesStack to print
   * @return a printed representation of {@code lv}
   */
  @SideEffectFree
  public String toString(@GuardSatisfied StackTypes this, LocalVariables lv) {

    String buff = "";

    for (int i = 0; i < lv.maxLocals(); i++) {
      if (buff.length() > 0) buff += ", ";
      Type t = lv.get(i);
      if (t instanceof UninitializedObjectType) {
        buff += "uninitialized-object";
      } else {
        buff += t;
      }
    }
    return "{" + buff + "}";
  }
}


package org.plumelib.bcelutil;

import java.util.HashMap;
import java.util.StringTokenizer;
import org.checkerframework.checker.index.qual.Positive;
import org.checkerframework.checker.signature.qual.BinaryName;
import org.checkerframework.checker.signature.qual.ClassGetName;
import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers;
import org.checkerframework.checker.signature.qual.FieldDescriptor;

/**
 * Utility functions for working with the JVM.
 *
 *
Currently contains conversion utilities between Java and JVM string formats, for types and
 * signatures.
 *
 * @deprecated Use org.plumelib.reflection.Signatures
 */
@Deprecated
public final class JvmUtil {

  /** A map from Java primitive data types to the corresponding field descriptor letter. */
  private static HashMap<@DotSeparatedIdentifiers String, @FieldDescriptor String>
      primitiveClassesJvm = new HashMap<>(8);

  static {
    primitiveClassesJvm.put("boolean", "Z");
    primitiveClassesJvm.put("byte", "B");
    primitiveClassesJvm.put("char", "C");
    primitiveClassesJvm.put("double", "D");
    primitiveClassesJvm.put("float", "F");
    primitiveClassesJvm.put("int", "I");
    primitiveClassesJvm.put("long", "J");
    primitiveClassesJvm.put("short", "S");
  }

  /**
   * Convert a binary name to a field descriptor. For example, convert "java.lang.Object[]" to
   * "[Ljava/lang/Object;" or "int" to "I".
   *
   * @param classname name of the class, in binary class name format
   * @return name of the class, in field descriptor format
   */
  @SuppressWarnings("signature") // conversion routine
  public static @FieldDescriptor String binaryNameToFieldDescriptor(@BinaryName String classname) {
    int dims = 0;
    String sansArray = classname;
    while (sansArray.endsWith("[]")) {
      dims++;
      sansArray = sansArray.substring(0, sansArray.length() - 2);
    }
    String result = primitiveClassesJvm.get(sansArray);
    if (result == null) {
      result = "L" + sansArray + ";";
    }
    for (int i = 0; i < dims; i++) {
      result = "[" + result;
    }
    return result.replace('.', '/');
  }

  /**
   * Convert a primitive Java type name (e.g., "int", "double", etc.) to a field descriptor (e.g.,
   * "I", "D", etc.).
   *
   * @param primitiveName name of the type, in Java format
   * @return name of the type, in field descriptor format
   * @throws IllegalArgumentException if primitiveName is not a valid primitive type name
   */
  public static @FieldDescriptor String primitiveTypeNameToFieldDescriptor(String primitiveName) {
    String result = primitiveClassesJvm.get(primitiveName);
    if (result == null) {
      throw new IllegalArgumentException("Not the name of a primitive type: " + primitiveName);
    }
    return result;
  }

  /**
   * Convert from a BinaryName to the format of {@link Class#getName()}.
   *
   * @param bn the binary name to convert
   * @return the class name, in Class.getName format
   */
  @SuppressWarnings("signature") // conversion routine
  public static @ClassGetName String binaryNameToClassGetName(/*BinaryName*/ String bn) {
    if (bn.endsWith("[]")) {
      return binaryNameToFieldDescriptor(bn).replace('/', '.');
    } else {
      return bn;
    }
  }

  /**
   * Convert from a FieldDescriptor to the format of {@link Class#getName()}.
   *
   * @param fd the class, in field descriptor format
   * @return the class name, in Class.getName format
   */
  @SuppressWarnings("signature") // conversion routine
  public static @ClassGetName String fieldDescriptorToClassGetName(/*FieldDescriptor*/ String fd) {
    if (fd.startsWith("[")) {
      return fd.replace('/', '.');
    } else {
      return fieldDescriptorToBinaryName(fd);
    }
  }

  /**
   * Convert a fully-qualified argument list from Java format to JVML format. For example, convert
   * "(java.lang.Integer[], int, java.lang.Integer[][])" to
   * "([Ljava/lang/Integer;I[[Ljava/lang/Integer;)".
   *
   * @param arglist an argument list, in Java format
   * @return argument list, in JVML format
   */
  public static String arglistToJvm(String arglist) {
    if (!(arglist.startsWith("(") && arglist.endsWith(")"))) {
      throw new Error("Malformed arglist: " + arglist);
    }
    String result = "(";
    String commaSepArgs = arglist.substring(1, arglist.length() - 1);
    StringTokenizer argsTokenizer = new StringTokenizer(commaSepArgs, ",", false);
    while (argsTokenizer.hasMoreTokens()) {
      @SuppressWarnings("signature") // substring
      @BinaryName String arg = argsTokenizer.nextToken().trim();
      result += binaryNameToFieldDescriptor(arg);
    }
    result += ")";
    // System.out.println("arglistToJvm: " + arglist + " => " + result);
    return result;
  }

  /** A map from field descriptor letters to the corresponding Java primitive data type. */
  private static HashMap<String, String> primitiveClassesFromJvm = new HashMap<>(8);

  static {
    primitiveClassesFromJvm.put("Z", "boolean");
    primitiveClassesFromJvm.put("B", "byte");
    primitiveClassesFromJvm.put("C", "char");
    primitiveClassesFromJvm.put("D", "double");
    primitiveClassesFromJvm.put("F", "float");
    primitiveClassesFromJvm.put("I", "int");
    primitiveClassesFromJvm.put("J", "long");
    primitiveClassesFromJvm.put("S", "short");
  }

  // does not convert "V" to "void".  Should it?
  /**
   * Convert a field descriptor to a binary name. For example, convert "[Ljava/lang/Object;" to
   * "java.lang.Object[]" or "I" to "int".
   *
   * @param classname name of the type, in JVML format
   * @return name of the type, in Java format
   */
  @SuppressWarnings("signature") // conversion routine
  public static @BinaryName String fieldDescriptorToBinaryName(String classname) {
    if (classname.equals("")) {
      throw new Error("Empty string passed to fieldDescriptorToBinaryName");
    }
    int dims = 0;
    while (classname.startsWith("[")) {
      dims++;
      classname = classname.substring(1);
    }
    String result;
    if (classname.startsWith("L") && classname.endsWith(";")) {
      result = classname.substring(1, classname.length() - 1);
    } else {
      result = primitiveClassesFromJvm.get(classname);
      if (result == null) {
        throw new Error("Malformed base class: " + classname);
      }
    }
    for (int i = 0; i < dims; i++) {
      result += "[]";
    }
    return result.replace('/', '.');
  }

  /**
   * Convert an argument list from JVML format to Java format. For example, convert
   * "([Ljava/lang/Integer;I[[Ljava/lang/Integer;)" to "(java.lang.Integer[], int,
   * java.lang.Integer[][])".
   *
   * @param arglist an argument list, in JVML format
   * @return argument list, in Java format
   */
  public static String arglistFromJvm(String arglist) {
    if (!(arglist.startsWith("(") && arglist.endsWith(")"))) {
      throw new Error("Malformed arglist: " + arglist);
    }
    String result = "(";
    @Positive int pos = 1;
    while (pos < arglist.length() - 1) {
      if (pos > 1) {
        result += ", ";
      }
      int nonarrayPos = pos;
      while (arglist.charAt(nonarrayPos) == '[') {
        nonarrayPos++;
        if (nonarrayPos >= arglist.length()) {
          throw new Error("Malformed arglist: " + arglist);
        }
      }
      char c = arglist.charAt(nonarrayPos);
      if (c == 'L') {
        int semicolonPos = arglist.indexOf(';', nonarrayPos);
        if (semicolonPos == -1) {
          throw new Error("Malformed arglist: " + arglist);
        }
        String fieldDescriptor = arglist.substring(pos, semicolonPos + 1);
        result += fieldDescriptorToBinaryName(fieldDescriptor);
        pos = semicolonPos + 1;
      } else {
        String maybe = fieldDescriptorToBinaryName(arglist.substring(pos, nonarrayPos + 1));
        if (maybe == null) {
          // return null;
          throw new Error("Malformed arglist: " + arglist);
        }
        result += maybe;
        pos = nonarrayPos + 1;
      }
    }
    return result + ")";
  }
}