/*
 * Copyright (c) 1994, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.tools.asm;

import sun.tools.java.*;
import java.util.Enumeration;
import java.io.IOException;
import java.io.DataOutputStream;
import java.io.PrintStream;
import java.util.Vector;
// JCOV
import sun.tools.javac.*;
import java.io.File;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.lang.String;
// end JCOV

/**
 * This class is used to assemble the bytecode instructions for a method.
 *
 * WARNING: The contents of this source file are not part of any
 * supported API.  Code that depends on them does so at its own risk:
 * they are subject to change or removal without notice.
 *
 * @author Arthur van Hoff
 */
public final
class Assembler implements Constants {
    static final int NOTREACHED         = 0;
    static final int REACHED            = 1;
    static final int NEEDED             = 2;

    Label first = new Label();
    Instruction last = first;
    int maxdepth;
    int maxvar;
    int maxpc;

    /**
     * Add an instruction
     */
    public void add(Instruction inst) {
        if (inst != null) {
            last.next = inst;
            last = inst;
        }
    }
    public void add(long where, int opc) {
        add(new Instruction(where, opc, null));
    }
    public void add(long where, int opc, Object obj) {
        add(new Instruction(where, opc, obj));
    }
// JCOV
    public void add(long where, int opc, Object obj, boolean flagCondInverted) {
        add(new Instruction(where, opc, obj, flagCondInverted));
    }

    public void add(boolean flagNoCovered, long where, int opc, Object obj) {
        add(new Instruction(flagNoCovered, where, opc, obj));
    }

    public void add(long where, int opc, boolean flagNoCovered) {
        add(new Instruction(where, opc, flagNoCovered));
    }

    static Vector<String> SourceClassList = new Vector<>();

    static Vector<String> TmpCovTable = new Vector<>();

    static int[]  JcovClassCountArray = new int[CT_LAST_KIND + 1];

    static String JcovMagicLine     = "JCOV-DATA-FILE-VERSION: 2.0";
    static String JcovClassLine     = "CLASS: ";
    static String JcovSrcfileLine   = "SRCFILE: ";
    static String JcovTimestampLine = "TIMESTAMP: ";
    static String JcovDataLine      = "DATA: ";
    static String JcovHeadingLine   = "#kind\tcount";

    static int[]  arrayModifiers    =
                {M_PUBLIC, M_PRIVATE, M_PROTECTED, M_ABSTRACT, M_FINAL, M_INTERFACE};
    static int[]  arrayModifiersOpc =
                {PUBLIC, PRIVATE, PROTECTED, ABSTRACT, FINAL, INTERFACE};
//end JCOV

    /**
     * Optimize instructions and mark those that can be reached
     */
    void optimize(Environment env, Label lbl) {
        lbl.pc = REACHED;

        for (Instruction inst = lbl.next ; inst != null ; inst = inst.next)  {
            switch (inst.pc) {
              case NOTREACHED:
                inst.optimize(env);
                inst.pc = REACHED;
                break;
              case REACHED:
                return;
              case NEEDED:
                break;
            }

            switch (inst.opc) {
              case opc_label:
              case opc_dead:
                if (inst.pc == REACHED) {
                    inst.pc = NOTREACHED;
                }
                break;

              case opc_ifeq:
              case opc_ifne:
              case opc_ifgt:
              case opc_ifge:
              case opc_iflt:
              case opc_ifle:
              case opc_if_icmpeq:
              case opc_if_icmpne:
              case opc_if_icmpgt:
              case opc_if_icmpge:
              case opc_if_icmplt:
              case opc_if_icmple:
              case opc_if_acmpeq:
              case opc_if_acmpne:
              case opc_ifnull:
              case opc_ifnonnull:
                optimize(env, (Label)inst.value);
                break;

              case opc_goto:
                optimize(env, (Label)inst.value);
                return;

              case opc_jsr:
                optimize(env, (Label)inst.value);
                break;

              case opc_ret:
              case opc_return:
              case opc_ireturn:
              case opc_lreturn:
              case opc_freturn:
              case opc_dreturn:
              case opc_areturn:
              case opc_athrow:
                return;

              case opc_tableswitch:
              case opc_lookupswitch: {
                SwitchData sw = (SwitchData)inst.value;
                optimize(env, sw.defaultLabel);
                for (Enumeration<Label> e = sw.tab.elements() ; e.hasMoreElements();) {
                    optimize(env, e.nextElement());
                }
                return;
              }

              case opc_try: {
                TryData td = (TryData)inst.value;
                td.getEndLabel().pc = NEEDED;
                for (Enumeration<CatchData> e = td.catches.elements() ; e.hasMoreElements();) {
                    CatchData cd = e.nextElement();
                    optimize(env, cd.getLabel());
                }
                break;
              }
            }
        }
    }

    /**
     * Eliminate instructions that are not reached
     */
    boolean eliminate() {
        boolean change = false;
        Instruction prev = first;

        for (Instruction inst = first.next ; inst != null ; inst = inst.next) {
            if (inst.pc != NOTREACHED) {
                prev.next = inst;
                prev = inst;
                inst.pc = NOTREACHED;
            } else {
                change = true;
            }
        }
        first.pc = NOTREACHED;
        prev.next = null;
        return change;
    }

    /**
     * Optimize the byte codes
     */
    public void optimize(Environment env) {
        //listing(System.out);
        do {
            // Figure out which instructions are reached
            optimize(env, first);

            // Eliminate instructions that are not reached
        } while (eliminate() && env.opt());
    }

    /**
     * Collect all constants into the constant table
     */
    public void collect(Environment env, MemberDefinition field, ConstantPool tab) {
        // Collect constants for arguments only
        // if a local variable table is generated
        if ((field != null) && env.debug_vars()) {
            @SuppressWarnings("unchecked")
            Vector<MemberDefinition> v = (Vector<MemberDefinition>)field.getArguments();
            if (v != null) {
                for (Enumeration<MemberDefinition> e = v.elements() ; e.hasMoreElements() ;) {
                    MemberDefinition f = e.nextElement();
                    tab.put(f.getName().toString());
                    tab.put(f.getType().getTypeSignature());
                }
            }
        }

        // Collect constants from the instructions
        for (Instruction inst = first ; inst != null ; inst = inst.next) {
            inst.collect(tab);
        }
    }

    /**
     * Determine stack size, count local variables
     */
    void balance(Label lbl, int depth) {
        for (Instruction inst = lbl ; inst != null ; inst = inst.next)  {
            //Environment.debugOutput(inst.toString() + ": " + depth + " => " +
            //                                 (depth + inst.balance()));
            depth += inst.balance();
            if (depth < 0) {
               throw new CompilerError("stack under flow: " + inst.toString() + " = " + depth);
            }
            if (depth > maxdepth) {
                maxdepth = depth;
            }
            switch (inst.opc) {
              case opc_label:
                lbl = (Label)inst;
                if (inst.pc == REACHED) {
                    if (lbl.depth != depth) {
                        throw new CompilerError("stack depth error " +
                                                depth + "/" + lbl.depth +
                                                ": " + inst.toString());
                    }
                    return;
                }
                lbl.pc = REACHED;
                lbl.depth = depth;
                break;

              case opc_ifeq:
              case opc_ifne:
              case opc_ifgt:
              case opc_ifge:
              case opc_iflt:
              case opc_ifle:
              case opc_if_icmpeq:
              case opc_if_icmpne:
              case opc_if_icmpgt:
              case opc_if_icmpge:
              case opc_if_icmplt:
              case opc_if_icmple:
              case opc_if_acmpeq:
              case opc_if_acmpne:
              case opc_ifnull:
              case opc_ifnonnull:
                balance((Label)inst.value, depth);
                break;

              case opc_goto:
                balance((Label)inst.value, depth);
                return;

              case opc_jsr:
                balance((Label)inst.value, depth + 1);
                break;

              case opc_ret:
              case opc_return:
              case opc_ireturn:
              case opc_lreturn:
              case opc_freturn:
              case opc_dreturn:
              case opc_areturn:
              case opc_athrow:
                return;

              case opc_iload:
              case opc_fload:
              case opc_aload:
              case opc_istore:
              case opc_fstore:
              case opc_astore: {
                int v = ((inst.value instanceof Number)
                            ? ((Number)inst.value).intValue()
                            : ((LocalVariable)inst.value).slot) + 1;
                if (v > maxvar)
                    maxvar = v;
                break;
              }

              case opc_lload:
              case opc_dload:
              case opc_lstore:
              case opc_dstore: {
                int v = ((inst.value instanceof Number)
                            ? ((Number)inst.value).intValue()
                            : ((LocalVariable)inst.value).slot) + 2;
                if (v  > maxvar)
                    maxvar = v;
                break;
              }

              case opc_iinc: {
                  int v = ((int[])inst.value)[0] + 1;
                  if (v  > maxvar)
                      maxvar = v + 1;
                  break;
              }

              case opc_tableswitch:
              case opc_lookupswitch: {
                SwitchData sw = (SwitchData)inst.value;
                balance(sw.defaultLabel, depth);
                for (Enumeration<Label> e = sw.tab.elements() ; e.hasMoreElements();) {
                    balance(e.nextElement(), depth);
                }
                return;
              }

              case opc_try: {
                TryData td = (TryData)inst.value;
                for (Enumeration<CatchData> e = td.catches.elements() ; e.hasMoreElements();) {
                    CatchData cd = e.nextElement();
                    balance(cd.getLabel(), depth + 1);
                }
                break;
              }
            }
        }
    }

    /**
     * Generate code
     */
    public void write(Environment env, DataOutputStream out,
                      MemberDefinition field, ConstantPool tab)
                 throws IOException {
        //listing(System.out);

        if ((field != null) && field.getArguments() != null) {
              int sum = 0;
              @SuppressWarnings("unchecked")
              Vector<MemberDefinition> v = (Vector<MemberDefinition>)field.getArguments();
              for (Enumeration<MemberDefinition> e = v.elements(); e.hasMoreElements(); ) {
                  MemberDefinition f = e.nextElement();
                  sum += f.getType().stackSize();
              }
              maxvar = sum;
        }

        // Make sure the stack balances.  Also calculate maxvar and maxstack
        try {
            balance(first, 0);
        } catch (CompilerError e) {
            System.out.println("ERROR: " + e);
            listing(System.out);
            throw e;
        }

        // Assign PCs
        int pc = 0, nexceptions = 0;
        for (Instruction inst = first ; inst != null ; inst = inst.next) {
            inst.pc = pc;
            int sz = inst.size(tab);
            if (pc<65536 && (pc+sz)>=65536) {
               env.error(inst.where, "warn.method.too.long");
            }
            pc += sz;

            if (inst.opc == opc_try) {
                nexceptions += ((TryData)inst.value).catches.size();
            }
        }

        // Write header
        out.writeShort(maxdepth);
        out.writeShort(maxvar);
        out.writeInt(maxpc = pc);

        // Generate code
        for (Instruction inst = first.next ; inst != null ; inst = inst.next) {
            inst.write(out, tab);
        }

        // write exceptions
        out.writeShort(nexceptions);
        if (nexceptions > 0) {
            //listing(System.out);
            writeExceptions(env, out, tab, first, last);
        }
    }

    /**
     * Write the exceptions table
     */
    void writeExceptions(Environment env, DataOutputStream out, ConstantPool tab, Instruction first, Instruction last) throws IOException {
        for (Instruction inst = first ; inst != last.next ; inst = inst.next) {
            if (inst.opc == opc_try) {
                TryData td = (TryData)inst.value;
                writeExceptions(env, out, tab, inst.next, td.getEndLabel());
                for (Enumeration<CatchData> e = td.catches.elements() ; e.hasMoreElements();) {
                    CatchData cd = e.nextElement();
                    //System.out.println("EXCEPTION: " + env.getSource() + ", pc=" + inst.pc + ", end=" + td.getEndLabel().pc + ", hdl=" + cd.getLabel().pc + ", tp=" + cd.getType());
                    out.writeShort(inst.pc);
                    out.writeShort(td.getEndLabel().pc);
                    out.writeShort(cd.getLabel().pc);
                    if (cd.getType() != null) {
                        out.writeShort(tab.index(cd.getType()));
                    } else {
                        out.writeShort(0);
                    }
                }
                inst = td.getEndLabel();
            }
        }
    }

//JCOV
    /**
     * Write the coverage table
     */
    public void writeCoverageTable(Environment env, ClassDefinition c, DataOutputStream out, ConstantPool tab, long whereField) throws IOException {
        Vector<Cover> TableLot = new Vector<>();         /* Coverage table */
        boolean begseg = false;
        boolean begmeth = false;
        @SuppressWarnings("deprecation")
        long whereClass = ((SourceClass)c).getWhere();
        Vector<Long> whereTry = new Vector<>();
        int numberTry = 0;
        int count = 0;

        for (Instruction inst = first ; inst != null ; inst = inst.next) {
            long n = (inst.where >> WHEREOFFSETBITS);
            if (n > 0 && inst.opc != opc_label) {
                if (!begmeth) {
                  if ( whereClass == inst.where)
                        TableLot.addElement(new Cover(CT_FIKT_METHOD, whereField, inst.pc));
                  else
                        TableLot.addElement(new Cover(CT_METHOD, whereField, inst.pc));
                  count++;
                  begmeth = true;
                }
                if (!begseg && !inst.flagNoCovered ) {
                  boolean findTry = false;
                  for (Enumeration<Long> e = whereTry.elements(); e.hasMoreElements();) {
                       if (e.nextElement().longValue() == inst.where) {
                              findTry = true;
                              break;
                       }
                  }
                  if (!findTry) {
                      TableLot.addElement(new Cover(CT_BLOCK, inst.where, inst.pc));
                      count++;
                      begseg = true;
                  }
                }
            }
            switch (inst.opc) {
              case opc_label:
                begseg = false;
                break;
              case opc_ifeq:
              case opc_ifne:
              case opc_ifnull:
              case opc_ifnonnull:
              case opc_ifgt:
              case opc_ifge:
              case opc_iflt:
              case opc_ifle:
              case opc_if_icmpeq:
              case opc_if_icmpne:
              case opc_if_icmpgt:
              case opc_if_icmpge:
              case opc_if_icmplt:
              case opc_if_icmple:
              case opc_if_acmpeq:
              case opc_if_acmpne: {
                if ( inst.flagCondInverted ) {
                   TableLot.addElement(new Cover(CT_BRANCH_TRUE, inst.where, inst.pc));
                   TableLot.addElement(new Cover(CT_BRANCH_FALSE, inst.where, inst.pc));
                } else {
                   TableLot.addElement(new Cover(CT_BRANCH_FALSE, inst.where, inst.pc));
                   TableLot.addElement(new Cover(CT_BRANCH_TRUE, inst.where, inst.pc));
                }
                count += 2;
                begseg = false;
                break;
              }

              case opc_goto: {
                begseg = false;
                break;
              }

              case opc_ret:
              case opc_return:
              case opc_ireturn:
              case opc_lreturn:
              case opc_freturn:
              case opc_dreturn:
              case opc_areturn:
              case opc_athrow: {
                break;
              }

              case opc_try: {
                whereTry.addElement(Long.valueOf(inst.where));
                begseg = false;
                break;
              }

              case opc_tableswitch: {
                SwitchData sw = (SwitchData)inst.value;
                for (int i = sw.minValue; i <= sw.maxValue; i++) {
                     TableLot.addElement(new Cover(CT_CASE, sw.whereCase(new Integer(i)), inst.pc));
                     count++;
                }
                if (!sw.getDefault()) {
                     TableLot.addElement(new Cover(CT_SWITH_WO_DEF, inst.where, inst.pc));
                     count++;
                } else {
                     TableLot.addElement(new Cover(CT_CASE, sw.whereCase("default"), inst.pc));
                     count++;
                }
                begseg = false;
                break;
              }
              case opc_lookupswitch: {
                SwitchData sw = (SwitchData)inst.value;
                for (Enumeration<Integer> e = sw.sortedKeys(); e.hasMoreElements() ; ) {
                     Integer v = e.nextElement();
                     TableLot.addElement(new Cover(CT_CASE, sw.whereCase(v), inst.pc));
                     count++;
                }
                if (!sw.getDefault()) {
                     TableLot.addElement(new Cover(CT_SWITH_WO_DEF, inst.where, inst.pc));
                     count++;
                } else {
                     TableLot.addElement(new Cover(CT_CASE, sw.whereCase("default"), inst.pc));
                     count++;
                }
                begseg = false;
                break;
              }
            }
        }
        Cover Lot;
        long ln, pos;

        out.writeShort(count);
        for (int i = 0; i < count; i++) {
           Lot = TableLot.elementAt(i);
           ln = (Lot.Addr >> WHEREOFFSETBITS);
           pos = (Lot.Addr << (64 - WHEREOFFSETBITS)) >> (64 - WHEREOFFSETBITS);
           out.writeShort(Lot.NumCommand);
           out.writeShort(Lot.Type);
           out.writeInt((int)ln);
           out.writeInt((int)pos);

           if ( !(Lot.Type == CT_CASE && Lot.Addr == 0) ) {
                JcovClassCountArray[Lot.Type]++;
           }
        }

    }

/*
 *  Increase count of methods for native methods
 */

public void addNativeToJcovTab(Environment env, ClassDefinition c) {
        JcovClassCountArray[CT_METHOD]++;
}

/*
 *  Create class jcov element
 */

private String createClassJcovElement(Environment env, ClassDefinition c) {
        String SourceClass = (Type.mangleInnerType((c.getClassDeclaration()).getName())).toString();
        String ConvSourceClass;
        String classJcovLine;

        SourceClassList.addElement(SourceClass);
        ConvSourceClass = SourceClass.replace('.', '/');
        classJcovLine = JcovClassLine + ConvSourceClass;

        classJcovLine = classJcovLine + " [";
        String blank = "";

        for (int i = 0; i < arrayModifiers.length; i++ ) {
            if ((c.getModifiers() & arrayModifiers[i]) != 0) {
                classJcovLine = classJcovLine + blank + opNames[arrayModifiersOpc[i]];
                blank = " ";
            }
        }
        classJcovLine = classJcovLine + "]";

        return classJcovLine;
}

/*
 *  generate coverage data
 */

public void GenVecJCov(Environment env, ClassDefinition c, long Time) {
        @SuppressWarnings("deprecation")
        String SourceFile = ((SourceClass)c).getAbsoluteName();

        TmpCovTable.addElement(createClassJcovElement(env, c));
        TmpCovTable.addElement(JcovSrcfileLine + SourceFile);
        TmpCovTable.addElement(JcovTimestampLine + Time);
        TmpCovTable.addElement(JcovDataLine + "A");             // data format
        TmpCovTable.addElement(JcovHeadingLine);

        for (int i = CT_FIRST_KIND; i <= CT_LAST_KIND; i++) {
            if (JcovClassCountArray[i] != 0) {
                TmpCovTable.addElement(new String(i + "\t" + JcovClassCountArray[i]));
                JcovClassCountArray[i] = 0;
            }
        }
}


/*
 * generate file of coverage data
 */

@SuppressWarnings("deprecation") // for JCovd.readLine() calls
public void GenJCov(Environment env) {

     try {
        File outFile = env.getcovFile();
        if( outFile.exists()) {
           DataInputStream JCovd = new DataInputStream(
                                                       new BufferedInputStream(
                                                                               new FileInputStream(outFile)));
           String CurrLine = null;
           boolean first = true;
           String Class;

           CurrLine = JCovd.readLine();
           if ((CurrLine != null) && CurrLine.startsWith(JcovMagicLine)) {
                // this is a good Jcov file

                   while((CurrLine = JCovd.readLine()) != null ) {
                      if ( CurrLine.startsWith(JcovClassLine) ) {
                             first = true;
                             for(Enumeration<String> e = SourceClassList.elements(); e.hasMoreElements();) {
                                 String clsName = CurrLine.substring(JcovClassLine.length());
                                 int idx = clsName.indexOf(' ');

                                 if (idx != -1) {
                                     clsName = clsName.substring(0, idx);
                                 }
                                 Class = e.nextElement();
                                 if ( Class.compareTo(clsName) == 0) {
                                     first = false;
                                     break;
                                 }
                             }
                      }
                      if (first)        // re-write old class
                          TmpCovTable.addElement(CurrLine);
                   }
           }
           JCovd.close();
        }
        PrintStream CovFile = new PrintStream(new DataOutputStream(new FileOutputStream(outFile)));
        CovFile.println(JcovMagicLine);
        for(Enumeration<String> e = TmpCovTable.elements(); e.hasMoreElements();) {
              CovFile.println(e.nextElement());
        }
        CovFile.close();
    }
    catch (FileNotFoundException e) {
       System.out.println("ERROR: " + e);
    }
    catch (IOException e) {
       System.out.println("ERROR: " + e);
    }
}
// end JCOV


    /**
     * Write the linenumber table
     */
    public void writeLineNumberTable(Environment env, DataOutputStream out, ConstantPool tab) throws IOException {
        long ln = -1;
        int count = 0;

        for (Instruction inst = first ; inst != null ; inst = inst.next) {
            long n = (inst.where >> WHEREOFFSETBITS);
            if ((n > 0) && (ln != n)) {
                ln = n;
                count++;
            }
        }

        ln = -1;
        out.writeShort(count);
        for (Instruction inst = first ; inst != null ; inst = inst.next) {
            long n = (inst.where >> WHEREOFFSETBITS);
            if ((n > 0) && (ln != n)) {
                ln = n;
                out.writeShort(inst.pc);
                out.writeShort((int)ln);
                //System.out.println("pc = " + inst.pc + ", ln = " + ln);
            }
        }
    }

    /**
     * Figure out when registers contain a legal value. This is done
     * using a simple data flow algorithm. This information is later used
     * to generate the local variable table.
     */
    void flowFields(Environment env, Label lbl, MemberDefinition locals[]) {
        if (lbl.locals != null) {
            // Been here before. Erase any conflicts.
            MemberDefinition f[] = lbl.locals;
            for (int i = 0 ; i < maxvar ; i++) {
                if (f[i] != locals[i]) {
                    f[i] = null;
                }
            }
            return;
        }

        // Remember the set of active registers at this point
        lbl.locals = new MemberDefinition[maxvar];
        System.arraycopy(locals, 0, lbl.locals, 0, maxvar);

        MemberDefinition newlocals[] = new MemberDefinition[maxvar];
        System.arraycopy(locals, 0, newlocals, 0, maxvar);
        locals = newlocals;

        for (Instruction inst = lbl.next ; inst != null ; inst = inst.next)  {
            switch (inst.opc) {
              case opc_istore:   case opc_istore_0: case opc_istore_1:
              case opc_istore_2: case opc_istore_3:
              case opc_fstore:   case opc_fstore_0: case opc_fstore_1:
              case opc_fstore_2: case opc_fstore_3:
              case opc_astore:   case opc_astore_0: case opc_astore_1:
              case opc_astore_2: case opc_astore_3:
              case opc_lstore:   case opc_lstore_0: case opc_lstore_1:
              case opc_lstore_2: case opc_lstore_3:
              case opc_dstore:   case opc_dstore_0: case opc_dstore_1:
              case opc_dstore_2: case opc_dstore_3:
                if (inst.value instanceof LocalVariable) {
                    LocalVariable v = (LocalVariable)inst.value;
                    locals[v.slot] = v.field;
                }
                break;

              case opc_label:
                flowFields(env, (Label)inst, locals);
                return;

              case opc_ifeq: case opc_ifne: case opc_ifgt:
              case opc_ifge: case opc_iflt: case opc_ifle:
              case opc_if_icmpeq: case opc_if_icmpne: case opc_if_icmpgt:
              case opc_if_icmpge: case opc_if_icmplt: case opc_if_icmple:
              case opc_if_acmpeq: case opc_if_acmpne:
              case opc_ifnull: case opc_ifnonnull:
              case opc_jsr:
                flowFields(env, (Label)inst.value, locals);
                break;

              case opc_goto:
                flowFields(env, (Label)inst.value, locals);
                return;

              case opc_return:   case opc_ireturn:  case opc_lreturn:
              case opc_freturn:  case opc_dreturn:  case opc_areturn:
              case opc_athrow:   case opc_ret:
                return;

              case opc_tableswitch:
              case opc_lookupswitch: {
                SwitchData sw = (SwitchData)inst.value;
                flowFields(env, sw.defaultLabel, locals);
                for (Enumeration<Label> e = sw.tab.elements() ; e.hasMoreElements();) {
                    flowFields(env, e.nextElement(), locals);
                }
                return;
              }

              case opc_try: {
                Vector<CatchData> catches = ((TryData)inst.value).catches;
                for (Enumeration<CatchData> e = catches.elements(); e.hasMoreElements();) {
                    CatchData cd = e.nextElement();
                    flowFields(env, cd.getLabel(), locals);
                }
                break;
              }
            }
        }
    }

    /**
     * Write the local variable table. The necessary constants have already been
     * added to the constant table by the collect() method. The flowFields method
     * is used to determine which variables are alive at each pc.
     */
    public void writeLocalVariableTable(Environment env, MemberDefinition field, DataOutputStream out, ConstantPool tab) throws IOException {
        MemberDefinition locals[] = new MemberDefinition[maxvar];
        int i = 0;

        // Initialize arguments
        if ((field != null) && (field.getArguments() != null)) {
            int reg = 0;
            @SuppressWarnings("unchecked")
            Vector<MemberDefinition> v = (Vector<MemberDefinition>)field.getArguments();
            for (Enumeration<MemberDefinition> e = v.elements(); e.hasMoreElements(); ) {
                MemberDefinition f = e.nextElement();
                locals[reg] = f;
                reg += f.getType().stackSize();
            }
        }

        flowFields(env, first, locals);
        LocalVariableTable lvtab = new LocalVariableTable();

        // Initialize arguments again
        for (i = 0; i < maxvar; i++)
            locals[i] = null;
        if ((field != null) && (field.getArguments() != null)) {
            int reg = 0;
            @SuppressWarnings("unchecked")
            Vector<MemberDefinition> v = (Vector<MemberDefinition>)field.getArguments();
            for (Enumeration<MemberDefinition> e = v.elements(); e.hasMoreElements(); ) {
                MemberDefinition f = e.nextElement();
                locals[reg] = f;
                lvtab.define(f, reg, 0, maxpc);
                reg += f.getType().stackSize();
            }
        }

        int pcs[] = new int[maxvar];

        for (Instruction inst = first ; inst != null ; inst = inst.next)  {
            switch (inst.opc) {
              case opc_istore:   case opc_istore_0: case opc_istore_1:
              case opc_istore_2: case opc_istore_3: case opc_fstore:
              case opc_fstore_0: case opc_fstore_1: case opc_fstore_2:
              case opc_fstore_3:
              case opc_astore:   case opc_astore_0: case opc_astore_1:
              case opc_astore_2: case opc_astore_3:
              case opc_lstore:   case opc_lstore_0: case opc_lstore_1:
              case opc_lstore_2: case opc_lstore_3:
              case opc_dstore:   case opc_dstore_0: case opc_dstore_1:
              case opc_dstore_2: case opc_dstore_3:
                if (inst.value instanceof LocalVariable) {
                    LocalVariable v = (LocalVariable)inst.value;
                    int pc = (inst.next != null) ? inst.next.pc : inst.pc;
                    if (locals[v.slot] != null) {
                        lvtab.define(locals[v.slot], v.slot, pcs[v.slot], pc);
                    }
                    pcs[v.slot] = pc;
                    locals[v.slot] = v.field;
                }
                break;

              case opc_label: {
                // flush  previous labels
                for (i = 0 ; i < maxvar ; i++) {
                    if (locals[i] != null) {
                        lvtab.define(locals[i], i, pcs[i], inst.pc);
                    }
                }
                // init new labels
                int pc = inst.pc;
                MemberDefinition[] labelLocals = ((Label)inst).locals;
                if (labelLocals == null) { // unreachable code??
                    for (i = 0; i < maxvar; i++)
                        locals[i] = null;
                } else {
                    System.arraycopy(labelLocals, 0, locals, 0, maxvar);
                }
                for (i = 0 ; i < maxvar ; i++) {
                    pcs[i] = pc;
                }
                break;
              }
            }
        }

        // flush  remaining labels
        for (i = 0 ; i < maxvar ; i++) {
            if (locals[i] != null) {
                lvtab.define(locals[i], i, pcs[i], maxpc);
            }
        }

        // write the local variable table
        lvtab.write(env, out, tab);
    }

    /**
     * Return true if empty
     */
    public boolean empty() {
        return first == last;
    }

    /**
     * Print the byte codes
     */
    public void listing(PrintStream out) {
        out.println("-- listing --");
        for (Instruction inst = first ; inst != null ; inst = inst.next) {
            out.println(inst.toString());
        }
    }
}
