content: /*
   * Copyright (C) 2007 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.taobao.android.dx.dex.file;
  
  import com.taobao.android.dex.util.ExceptionWithContext;
  import com.taobao.android.dx.dex.code.LocalList;
  import com.taobao.android.dx.dex.code.PositionList;
  import static com.taobao.android.dx.dex.file.DebugInfoConstants.DBG_ADVANCE_LINE;
  import static com.taobao.android.dx.dex.file.DebugInfoConstants.DBG_ADVANCE_PC;
  import static com.taobao.android.dx.dex.file.DebugInfoConstants.DBG_END_LOCAL;
  import static com.taobao.android.dx.dex.file.DebugInfoConstants.DBG_END_SEQUENCE;
  import static com.taobao.android.dx.dex.file.DebugInfoConstants.DBG_FIRST_SPECIAL;
  import static com.taobao.android.dx.dex.file.DebugInfoConstants.DBG_LINE_BASE;
  import static com.taobao.android.dx.dex.file.DebugInfoConstants.DBG_LINE_RANGE;
  import static com.taobao.android.dx.dex.file.DebugInfoConstants.DBG_RESTART_LOCAL;
  import static com.taobao.android.dx.dex.file.DebugInfoConstants.DBG_SET_PROLOGUE_END;
  import static com.taobao.android.dx.dex.file.DebugInfoConstants.DBG_START_LOCAL;
  import static com.taobao.android.dx.dex.file.DebugInfoConstants.DBG_START_LOCAL_EXTENDED;
  import com.taobao.android.dx.rop.code.RegisterSpec;
  import com.taobao.android.dx.rop.code.SourcePosition;
  import com.taobao.android.dx.rop.cst.CstMethodRef;
  import com.taobao.android.dx.rop.cst.CstString;
  import com.taobao.android.dx.rop.cst.CstType;
  import com.taobao.android.dx.rop.type.Prototype;
  import com.taobao.android.dx.rop.type.StdTypeList;
  import com.taobao.android.dx.rop.type.Type;
  import com.taobao.android.dx.util.AnnotatedOutput;
  import com.taobao.android.dx.util.ByteArrayAnnotatedOutput;
  import java.io.IOException;
  import java.io.PrintWriter;
  import java.util.ArrayList;
  import java.util.BitSet;
  import java.util.Collections;
  import java.util.Comparator;
  
  /**
   * An encoder for the dex debug info state machine format. The format
   * for each method enrty is as follows:
   * <ol>
   * <li> signed LEB128: initial value for line register.
   * <li> n instances of signed LEB128: string indicies (offset by 1)
   * for each method argument in left-to-right order
   * with {@code this} excluded. A value of '0' indicates "no name"
   * <li> A sequence of special or normal opcodes as defined in
   * {@code DebugInfoConstants}.
   * <li> A single terminating {@code OP_END_SEQUENCE}
   * </ol>
   */
  public final class DebugInfoEncoder {
      private static final boolean DEBUG = false;
  
      /** {@code null-ok;} positions (line numbers) to encode */
      private final PositionList positions;
  
      /** {@code null-ok;} local variables to encode */
      private final LocalList locals;
  
      private final ByteArrayAnnotatedOutput output;
      private final DexFile file;
      private final int codeSize;
      private final int regSize;
  
      private final Prototype desc;
      private final boolean isStatic;
  
      /** current encoding state: bytecode address */
      private int address = 0;
  
      /** current encoding state: line number */
      private int line = 1;
  
      /**
       * if non-null: the output to write annotations to. No normal
       * output is written to this.
       */
      private AnnotatedOutput annotateTo;
  
      /** if non-null: another possible output for annotations */
      private PrintWriter debugPrint;
  
      /** if non-null: the prefix for each annotation or debugPrint line */
      private String prefix;
  
      /** true if output should be consumed during annotation */
      private boolean shouldConsume;
  
      /** indexed by register; last local alive in register */
      private final LocalList.Entry[] lastEntryForReg;
  
      /**
       * Creates an instance.
       *
       * @param positions {@code null-ok;} positions (line numbers) to encode
       * @param locals {@code null-ok;} local variables to encode
       * @param file {@code null-ok;} may only be {@code null} if simply using
       * this class to do a debug print
       * @param codeSize
       * @param regSize
       * @param isStatic
       * @param ref
       */
      public DebugInfoEncoder(PositionList positions, LocalList locals,
              DexFile file, int codeSize, int regSize,
              boolean isStatic, CstMethodRef ref) {
          this.positions = positions;
          this.locals = locals;
          this.file = file;
          this.desc = ref.getPrototype();
          this.isStatic = isStatic;
          this.codeSize = codeSize;
          this.regSize = regSize;
  
          output = new ByteArrayAnnotatedOutput();
          lastEntryForReg = new LocalList.Entry[regSize];
      }
  
      /**
       * Annotates or writes a message to the {@code debugPrint} writer
       * if applicable.
       *
       * @param length the number of bytes associated with this message
       * @param message the message itself
       */
      private void annotate(int length, String message) {
          if (prefix != null) {
              message = prefix + message;
          }
  
          if (annotateTo != null) {
              annotateTo.annotate(shouldConsume ? length : 0, message);
          }
  
          if (debugPrint != null) {
              debugPrint.println(message);
          }
      }
  
      /**
       * Converts this (PositionList, LocalList) pair into a state machine
       * sequence.
       *
       * @return {@code non-null;} encoded byte sequence without padding and
       * terminated with a {@code 0x00} byte
       */
      public byte[] convert() {
          try {
              byte[] ret;
              ret = convert0();
  
              if (DEBUG) {
                  for (int i = 0 ; i < ret.length; i++) {
                      System.err.printf("byte %02x\n", (0xff & ret[i]));
                  }
              }
  
              return ret;
          } catch (IOException ex) {
              throw ExceptionWithContext
                      .withContext(ex, "...while encoding debug info");
          }
      }
  
      /**
       * Converts and produces annotations on a stream. Does not write
       * actual bits to the {@code AnnotatedOutput}.
       *
       * @param prefix {@code null-ok;} prefix to attach to each line of output
       * @param debugPrint {@code null-ok;} if specified, an alternate output for
       * annotations
       * @param out {@code null-ok;} if specified, where annotations should go
       * @param consume whether to claim to have consumed output for
       * {@code out}
       * @return {@code non-null;} encoded output
       */
      public byte[] convertAndAnnotate(String prefix, PrintWriter debugPrint,
              AnnotatedOutput out, boolean consume) {
          this.prefix = prefix;
          this.debugPrint = debugPrint;
          annotateTo = out;
          shouldConsume = consume;
  
          byte[] result = convert();
  
          return result;
      }
  
      private byte[] convert0() throws IOException {
          ArrayList<PositionList.Entry> sortedPositions = buildSortedPositions();
          ArrayList<LocalList.Entry> methodArgs = extractMethodArguments();
  
          emitHeader(sortedPositions, methodArgs);
  
          // TODO: Make this mark be the actual prologue end.
          output.writeByte(DBG_SET_PROLOGUE_END);
  
          if (annotateTo != null || debugPrint != null) {
              annotate(1, String.format("%04x: prologue end",address));
          }
  
          int positionsSz = sortedPositions.size();
          int localsSz = locals.size();
  
          // Current index in sortedPositions
          int curPositionIdx = 0;
          // Current index in locals
          int curLocalIdx = 0;
  
          for (;;) {
              /*
               * Emit any information for the current address.
               */
  
              curLocalIdx = emitLocalsAtAddress(curLocalIdx);
              curPositionIdx =
                  emitPositionsAtAddress(curPositionIdx, sortedPositions);
  
              /*
               * Figure out what the next important address is.
               */
  
              int nextAddrL = Integer.MAX_VALUE; // local variable
              int nextAddrP = Integer.MAX_VALUE; // position (line number)
  
              if (curLocalIdx < localsSz) {
                  nextAddrL = locals.get(curLocalIdx).getAddress();
              }
  
              if (curPositionIdx < positionsSz) {
                  nextAddrP = sortedPositions.get(curPositionIdx).getAddress();
              }
  
              int next = Math.min(nextAddrP, nextAddrL);
  
              // No next important address == done.
              if (next == Integer.MAX_VALUE) {
                  break;
              }
  
              /*
               * If the only work remaining are local ends at the end of the
               * block, stop here. Those are implied anyway.
               */
              if (next == codeSize
                      && nextAddrL == Integer.MAX_VALUE
                      && nextAddrP == Integer.MAX_VALUE) {
                  break;
              }
  
              if (next == nextAddrP) {
                  // Combined advance PC + position entry
                  emitPosition(sortedPositions.get(curPositionIdx++));
              } else {
                  emitAdvancePc(next - address);
              }
          }
  
          emitEndSequence();
  
          return output.toByteArray();
      }
  
      /**
       * Emits all local variable activity that occurs at the current
       * {@link #address} starting at the given index into {@code
       * locals} and including all subsequent activity at the same
       * address.
       *
       * @param curLocalIdx Current index in locals
       * @return new value for {@code curLocalIdx}
       * @throws IOException
       */
      private int emitLocalsAtAddress(int curLocalIdx)
              throws IOException {
          int sz = locals.size();
  
          // TODO: Don't emit ends implied by starts.
  
          while ((curLocalIdx < sz)
                  && (locals.get(curLocalIdx).getAddress() == address)) {
              LocalList.Entry entry = locals.get(curLocalIdx++);
              int reg = entry.getRegister();
              LocalList.Entry prevEntry = lastEntryForReg[reg];
  
              if (entry == prevEntry) {
                  /*
                   * Here we ignore locals entries for parameters,
                   * which have already been represented and placed in the
                   * lastEntryForReg array.
                   */
                  continue;
              }
  
              // At this point we have a new entry one way or another.
              lastEntryForReg[reg] = entry;
  
              if (entry.isStart()) {
                  if ((prevEntry != null) && entry.matches(prevEntry)) {
                      /*
                       * The previous local in this register has the same
                       * name and type as the one being introduced now, so
                       * use the more efficient "restart" form.
                       */
                      if (prevEntry.isStart()) {
                          /*
                           * We should never be handed a start when a
                           * a matching local is already active.
                           */
                          throw new RuntimeException("shouldn't happen");
                      }
                      emitLocalRestart(entry);
                  } else {
                      emitLocalStart(entry);
                  }
              } else {
                  /*
                   * Only emit a local end if it is *not* due to a direct
                   * replacement. Direct replacements imply an end of the
                   * previous local in the same register.
                   *
                   * TODO: Make sure the runtime can deal with implied
                   * local ends from category-2 interactions, and when so,
                   * also stop emitting local ends for those cases.
                   */
                  if (entry.getDisposition()
                          != LocalList.Disposition.END_REPLACED) {
                      emitLocalEnd(entry);
                  }
              }
          }
  
          return curLocalIdx;
      }
  
      /**
       * Emits all positions that occur at the current {@code address}
       *
       * @param curPositionIdx Current index in sortedPositions
       * @param sortedPositions positions, sorted by ascending address
       * @return new value for {@code curPositionIdx}
       * @throws IOException
       */
      private int emitPositionsAtAddress(int curPositionIdx,
              ArrayList<PositionList.Entry> sortedPositions)
              throws IOException {
          int positionsSz = sortedPositions.size();
          while ((curPositionIdx < positionsSz)
                  && (sortedPositions.get(curPositionIdx).getAddress()
                          == address)) {
              emitPosition(sortedPositions.get(curPositionIdx++));
          }
          return curPositionIdx;
      }
  
      /**
       * Emits the header sequence, which consists of LEB128-encoded initial
       * line number and string indicies for names of all non-"this" arguments.
       *
       * @param sortedPositions positions, sorted by ascending address
       * @param methodArgs local list entries for method argumens arguments,
       * in left-to-right order omitting "this"
       * @throws IOException
       */
      private void emitHeader(ArrayList<PositionList.Entry> sortedPositions,
              ArrayList<LocalList.Entry> methodArgs) throws IOException {
          boolean annotate = (annotateTo != null) || (debugPrint != null);
          int mark = output.getCursor();
  
          // Start by initializing the line number register.
          if (sortedPositions.size() > 0) {
              PositionList.Entry entry = sortedPositions.get(0);
              line = entry.getPosition().getLine();
          }
          output.writeUleb128(line);
  
          if (annotate) {
              annotate(output.getCursor() - mark, "line_start: " + line);
          }
  
          int curParam = getParamBase();
          // paramTypes will not include 'this'
          StdTypeList paramTypes = desc.getParameterTypes();
          int szParamTypes = paramTypes.size();
  
          /*
           * Initialize lastEntryForReg to have an initial
           * entry for the 'this' pointer.
           */
          if (!isStatic) {
              for (LocalList.Entry arg : methodArgs) {
                  if (curParam == arg.getRegister()) {
                      lastEntryForReg[curParam] = arg;
                      break;
                  }
              }
              curParam++;
          }
  
          // Write out the number of parameter entries that will follow.
          mark = output.getCursor();
          output.writeUleb128(szParamTypes);
  
          if (annotate) {
              annotate(output.getCursor() - mark,
                      String.format("parameters_size: %04x", szParamTypes));
          }
  
          /*
           * Then emit the string indicies of all the method parameters.
           * Note that 'this', if applicable, is excluded.
           */
          for (int i = 0; i < szParamTypes; i++) {
              Type pt = paramTypes.get(i);
              LocalList.Entry found = null;
  
              mark = output.getCursor();
  
              for (LocalList.Entry arg : methodArgs) {
                  if (curParam == arg.getRegister()) {
                      found = arg;
  
                      if (arg.getSignature() != null) {
                          /*
                           * Parameters with signatures will be re-emitted
                           * in complete as LOCAL_START_EXTENDED's below.
                           */
                          emitStringIndex(null);
                      } else {
                          emitStringIndex(arg.getName());
                      }
                      lastEntryForReg[curParam] = arg;
  
                      break;
                  }
              }
  
              if (found == null) {
                  /*
                   * Emit a null symbol for "unnamed." This is common
                   * for, e.g., synthesized methods and inner-class
                   * this$0 arguments.
                   */
                  emitStringIndex(null);
              }
  
              if (annotate) {
                  String parameterName
                          = (found == null || found.getSignature() != null)
                                  ? "<unnamed>" : found.getName().toHuman();
                  annotate(output.getCursor() - mark,
                          "parameter " + parameterName + " "
                                  + RegisterSpec.PREFIX + curParam);
              }
  
              curParam += pt.getCategory();
          }
  
          /*
           * If anything emitted above has a type signature, emit it again as
           * a LOCAL_RESTART_EXTENDED
           */
  
          for (LocalList.Entry arg : lastEntryForReg) {
              if (arg == null) {
                  continue;
              }
  
              CstString signature = arg.getSignature();
  
              if (signature != null) {
                  emitLocalStartExtended(arg);
              }
          }
      }
  
      /**
       * Builds a list of position entries, sorted by ascending address.
       *
       * @return A sorted positions list
       */
      private ArrayList<PositionList.Entry> buildSortedPositions() {
          int sz = (positions == null) ? 0 : positions.size();
          ArrayList<PositionList.Entry> result = new ArrayList(sz);
  
          for (int i = 0; i < sz; i++) {
              result.add(positions.get(i));
          }
  
          // Sort ascending by address.
          Collections.sort (result, new Comparator<PositionList.Entry>() {
              public int compare (PositionList.Entry a, PositionList.Entry b) {
                  return a.getAddress() - b.getAddress();
              }
  
              public boolean equals (Object obj) {
                 return obj == this;
              }
          });
          return result;
      }
  
      /**
       * Gets the register that begins the method's parameter range (including
       * the 'this' parameter for non-static methods). The range continues until
       * {@code regSize}
       *
       * @return register as noted above
       */
      private int getParamBase() {
          return regSize
                  - desc.getParameterTypes().getWordCount() - (isStatic? 0 : 1);
      }
  
      /**
       * Extracts method arguments from a locals list. These will be collected
       * from the input list and sorted by ascending register in the
       * returned list.
       *
       * @return list of non-{@code this} method argument locals,
       * sorted by ascending register
       */
      private ArrayList<LocalList.Entry> extractMethodArguments() {
          ArrayList<LocalList.Entry> result
                  = new ArrayList(desc.getParameterTypes().size());
          int argBase = getParamBase();
          BitSet seen = new BitSet(regSize - argBase);
          int sz = locals.size();
  
          for (int i = 0; i < sz; i++) {
              LocalList.Entry e = locals.get(i);
              int reg = e.getRegister();
  
              if (reg < argBase) {
                  continue;
              }
  
              // only the lowest-start-address entry is included.
              if (seen.get(reg - argBase)) {
                  continue;
              }
  
              seen.set(reg - argBase);
              result.add(e);
          }
  
          // Sort by ascending register.
          Collections.sort(result, new Comparator<LocalList.Entry>() {
              public int compare(LocalList.Entry a, LocalList.Entry b) {
                  return a.getRegister() - b.getRegister();
              }
  
              public boolean equals(Object obj) {
                 return obj == this;
              }
          });
  
          return result;
      }
  
      /**
       * Returns a string representation of this LocalList entry that is
       * appropriate for emitting as an annotation.
       *
       * @param e {@code non-null;} entry
       * @return {@code non-null;} annotation string
       */
      private String entryAnnotationString(LocalList.Entry e) {
          StringBuilder sb = new StringBuilder();
  
          sb.append(RegisterSpec.PREFIX);
          sb.append(e.getRegister());
          sb.append(' ');
  
          CstString name = e.getName();
          if (name == null) {
              sb.append("null");
          } else {
              sb.append(name.toHuman());
          }
          sb.append(' ');
  
          CstType type = e.getType();
          if (type == null) {
              sb.append("null");
          } else {
              sb.append(type.toHuman());
          }
  
          CstString signature = e.getSignature();
  
          if (signature != null) {
              sb.append(' ');
              sb.append(signature.toHuman());
          }
  
          return sb.toString();
      }
  
      /**
       * Emits a {@link DebugInfoConstants#DBG_RESTART_LOCAL DBG_RESTART_LOCAL}
       * sequence.
       *
       * @param entry entry associated with this restart
       * @throws IOException
       */
      private void emitLocalRestart(LocalList.Entry entry)
              throws IOException {
  
          int mark = output.getCursor();
  
          output.writeByte(DBG_RESTART_LOCAL);
          emitUnsignedLeb128(entry.getRegister());
  
          if (annotateTo != null || debugPrint != null) {
              annotate(output.getCursor() - mark,
                      String.format("%04x: +local restart %s",
                              address, entryAnnotationString(entry)));
          }
  
          if (DEBUG) {
              System.err.println("emit local restart");
          }
      }
  
      /**
       * Emits a string index as an unsigned LEB128. The actual value written
       * is shifted by 1, so that the '0' value is reserved for "null". The
       * null symbol is used in some cases by the parameter name list
       * at the beginning of the sequence.
       *
       * @param string {@code null-ok;} string to emit
       * @throws IOException
       */
      private void emitStringIndex(CstString string) throws IOException {
          if ((string == null) || (file == null)) {
              output.writeUleb128(0);
          } else {
              output.writeUleb128(
                      1 + file.getStringIds().indexOf(string));
          }
  
          if (DEBUG) {
              System.err.printf("Emit string %s\n",
                      string == null ? "<null>" : string.toQuoted());
          }
      }
  
      /**
       * Emits a type index as an unsigned LEB128. The actual value written
       * is shifted by 1, so that the '0' value is reserved for "null".
       *
       * @param type {@code null-ok;} type to emit
       * @throws IOException
       */
      private void emitTypeIndex(CstType type) throws IOException {
          if ((type == null) || (file == null)) {
              output.writeUleb128(0);
          } else {
              output.writeUleb128(
                      1 + file.getTypeIds().indexOf(type));
          }
  
          if (DEBUG) {
              System.err.printf("Emit type %s\n",
                      type == null ? "<null>" : type.toHuman());
          }
      }
  
      /**
       * Emits a {@link DebugInfoConstants#DBG_START_LOCAL DBG_START_LOCAL} or
       * {@link DebugInfoConstants#DBG_START_LOCAL_EXTENDED
       * DBG_START_LOCAL_EXTENDED} sequence.
       *
       * @param entry entry to emit
       * @throws IOException
       */
      private void emitLocalStart(LocalList.Entry entry)
          throws IOException {
  
          if (entry.getSignature() != null) {
              emitLocalStartExtended(entry);
              return;
          }
  
          int mark = output.getCursor();
  
          output.writeByte(DBG_START_LOCAL);
  
          emitUnsignedLeb128(entry.getRegister());
          emitStringIndex(entry.getName());
          emitTypeIndex(entry.getType());
  
          if (annotateTo != null || debugPrint != null) {
              annotate(output.getCursor() - mark,
                      String.format("%04x: +local %s", address,
                              entryAnnotationString(entry)));
          }
  
          if (DEBUG) {
              System.err.println("emit local start");
          }
      }
  
      /**
       * Emits a {@link DebugInfoConstants#DBG_START_LOCAL_EXTENDED
       * DBG_START_LOCAL_EXTENDED} sequence.
       *
       * @param entry entry to emit
       * @throws IOException
       */
      private void emitLocalStartExtended(LocalList.Entry entry)
          throws IOException {
  
          int mark = output.getCursor();
  
          output.writeByte(DBG_START_LOCAL_EXTENDED);
  
          emitUnsignedLeb128(entry.getRegister());
          emitStringIndex(entry.getName());
          emitTypeIndex(entry.getType());
          emitStringIndex(entry.getSignature());
  
          if (annotateTo != null || debugPrint != null) {
              annotate(output.getCursor() - mark,
                      String.format("%04x: +localx %s", address,
                              entryAnnotationString(entry)));
          }
  
          if (DEBUG) {
              System.err.println("emit local start");
          }
      }
  
      /**
       * Emits a {@link DebugInfoConstants#DBG_END_LOCAL DBG_END_LOCAL} sequence.
       *
       * @param entry {@code entry non-null;} entry associated with end.
       * @throws IOException
       */
      private void emitLocalEnd(LocalList.Entry entry)
              throws IOException {
  
          int mark = output.getCursor();
  
          output.writeByte(DBG_END_LOCAL);
          output.writeUleb128(entry.getRegister());
  
          if (annotateTo != null || debugPrint != null) {
              annotate(output.getCursor() - mark,
                      String.format("%04x: -local %s", address,
                              entryAnnotationString(entry)));
          }
  
          if (DEBUG) {
              System.err.println("emit local end");
          }
      }
  
      /**
       * Emits the necessary byte sequences to emit the given position table
       * entry. This will typically be a single special opcode, although
       * it may also require DBG_ADVANCE_PC or DBG_ADVANCE_LINE.
       *
       * @param entry position entry to emit.
       * @throws IOException
       */
      private void emitPosition(PositionList.Entry entry)
              throws IOException {
  
          SourcePosition pos = entry.getPosition();
          int newLine = pos.getLine();
          int newAddress = entry.getAddress();
  
          int opcode;
  
          int deltaLines = newLine - line;
          int deltaAddress = newAddress - address;
  
          if (deltaAddress < 0) {
              throw new RuntimeException(
                      "Position entries must be in ascending address order");
          }
  
          if ((deltaLines < DBG_LINE_BASE)
                  || (deltaLines > (DBG_LINE_BASE + DBG_LINE_RANGE -1))) {
              emitAdvanceLine(deltaLines);
              deltaLines = 0;
          }
  
          opcode = computeOpcode (deltaLines, deltaAddress);
  
          if ((opcode & ~0xff) > 0) {
              emitAdvancePc(deltaAddress);
              deltaAddress = 0;
              opcode = computeOpcode (deltaLines, deltaAddress);
  
              if ((opcode & ~0xff) > 0) {
                  emitAdvanceLine(deltaLines);
                  deltaLines = 0;
                  opcode = computeOpcode (deltaLines, deltaAddress);
              }
          }
  
          output.writeByte(opcode);
  
          line += deltaLines;
          address += deltaAddress;
  
          if (annotateTo != null || debugPrint != null) {
              annotate(1,
                      String.format("%04x: line %d", address, line));
          }
      }
  
      /**
       * Computes a special opcode that will encode the given position change.
       * If the return value is > 0xff, then the request cannot be fulfilled.
       * Essentially the same as described in "DWARF Debugging Format Version 3"
       * section 6.2.5.1.
       *
       * @param deltaLines {@code >= DBG_LINE_BASE, <= DBG_LINE_BASE +
       * DBG_LINE_RANGE;} the line change to encode
       * @param deltaAddress {@code >= 0;} the address change to encode
       * @return {@code <= 0xff} if in range, otherwise parameters are out
       * of range
       */
      private static int computeOpcode(int deltaLines, int deltaAddress) {
          if (deltaLines < DBG_LINE_BASE
                  || deltaLines > (DBG_LINE_BASE + DBG_LINE_RANGE -1)) {
  
              throw new RuntimeException("Parameter out of range");
          }
  
          return (deltaLines - DBG_LINE_BASE)
              + (DBG_LINE_RANGE * deltaAddress) + DBG_FIRST_SPECIAL;
      }
  
      /**
       * Emits an {@link DebugInfoConstants#DBG_ADVANCE_LINE DBG_ADVANCE_LINE}
       * sequence.
       *
       * @param deltaLines amount to change line number register by
       * @throws IOException
       */
      private void emitAdvanceLine(int deltaLines) throws IOException {
          int mark = output.getCursor();
  
          output.writeByte(DBG_ADVANCE_LINE);
          output.writeSleb128(deltaLines);
          line += deltaLines;
  
          if (annotateTo != null || debugPrint != null) {
              annotate(output.getCursor() - mark,
                      String.format("line = %d", line));
          }
  
          if (DEBUG) {
              System.err.printf("Emitting advance_line for %d\n", deltaLines);
          }
      }
  
      /**
       * Emits an  {@link DebugInfoConstants#DBG_ADVANCE_PC DBG_ADVANCE_PC}
       * sequence.
       *
       * @param deltaAddress {@code >= 0;} amount to change program counter by
       * @throws IOException
       */
      private void emitAdvancePc(int deltaAddress) throws IOException {
          int mark = output.getCursor();
  
          output.writeByte(DBG_ADVANCE_PC);
          output.writeUleb128(deltaAddress);
          address += deltaAddress;
  
          if (annotateTo != null || debugPrint != null) {
              annotate(output.getCursor() - mark,
                      String.format("%04x: advance pc", address));
          }
  
          if (DEBUG) {
              System.err.printf("Emitting advance_pc for %d\n", deltaAddress);
          }
      }
  
      /**
       * Emits an unsigned LEB128 value.
       *
       * @param n {@code >= 0;} value to emit. Note that, although this can
       * represent integers larger than Integer.MAX_VALUE, we currently don't
       * allow that.
       * @throws IOException
       */
      private void emitUnsignedLeb128(int n) throws IOException {
          // We'll never need the top end of the unsigned range anyway.
          if (n < 0) {
              throw new RuntimeException(
                      "Signed value where unsigned required: " + n);
          }
  
          output.writeUleb128(n);
      }
  
      /**
       * Emits the {@link DebugInfoConstants#DBG_END_SEQUENCE DBG_END_SEQUENCE}
       * bytecode.
       */
      private void emitEndSequence() {
          output.writeByte(DBG_END_SEQUENCE);
  
          if (annotateTo != null || debugPrint != null) {
              annotate(1, "end sequence");
          }
      }
  }
  