/*
 * Copyright (c) 1999, 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.
 */

//todo: one might eliminate uninits.andSets when monotonic

package com.sun.tools.javac.comp;

import java.util.HashMap;
import java.util.Map;
import java.util.LinkedHashMap;

import com.sun.tools.javac.code.*;
import com.sun.tools.javac.tree.*;
import com.sun.tools.javac.util.*;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;

import com.sun.tools.javac.code.Symbol.*;
import com.sun.tools.javac.tree.JCTree.*;

import static com.sun.tools.javac.code.Flags.*;
import static com.sun.tools.javac.code.Kinds.*;
import static com.sun.tools.javac.code.TypeTags.*;

/** This pass implements dataflow analysis for Java programs.
 *  Liveness analysis checks that every statement is reachable.
 *  Exception analysis ensures that every checked exception that is
 *  thrown is declared or caught.  Definite assignment analysis
 *  ensures that each variable is assigned when used.  Definite
 *  unassignment analysis ensures that no final variable is assigned
 *  more than once.
 *
 *  <p>The JLS has a number of problems in the
 *  specification of these flow analysis problems. This implementation
 *  attempts to address those issues.
 *
 *  <p>First, there is no accommodation for a finally clause that cannot
 *  complete normally. For liveness analysis, an intervening finally
 *  clause can cause a break, continue, or return not to reach its
 *  target.  For exception analysis, an intervening finally clause can
 *  cause any exception to be "caught".  For DA/DU analysis, the finally
 *  clause can prevent a transfer of control from propagating DA/DU
 *  state to the target.  In addition, code in the finally clause can
 *  affect the DA/DU status of variables.
 *
 *  <p>For try statements, we introduce the idea of a variable being
 *  definitely unassigned "everywhere" in a block.  A variable V is
 *  "unassigned everywhere" in a block iff it is unassigned at the
 *  beginning of the block and there is no reachable assignment to V
 *  in the block.  An assignment V=e is reachable iff V is not DA
 *  after e.  Then we can say that V is DU at the beginning of the
 *  catch block iff V is DU everywhere in the try block.  Similarly, V
 *  is DU at the beginning of the finally block iff V is DU everywhere
 *  in the try block and in every catch block.  Specifically, the
 *  following bullet is added to 16.2.2
 *  <pre>
 *      V is <em>unassigned everywhere</em> in a block if it is
 *      unassigned before the block and there is no reachable
 *      assignment to V within the block.
 *  </pre>
 *  <p>In 16.2.15, the third bullet (and all of its sub-bullets) for all
 *  try blocks is changed to
 *  <pre>
 *      V is definitely unassigned before a catch block iff V is
 *      definitely unassigned everywhere in the try block.
 *  </pre>
 *  <p>The last bullet (and all of its sub-bullets) for try blocks that
 *  have a finally block is changed to
 *  <pre>
 *      V is definitely unassigned before the finally block iff
 *      V is definitely unassigned everywhere in the try block
 *      and everywhere in each catch block of the try statement.
 *  </pre>
 *  <p>In addition,
 *  <pre>
 *      V is definitely assigned at the end of a constructor iff
 *      V is definitely assigned after the block that is the body
 *      of the constructor and V is definitely assigned at every
 *      return that can return from the constructor.
 *  </pre>
 *  <p>In addition, each continue statement with the loop as its target
 *  is treated as a jump to the end of the loop body, and "intervening"
 *  finally clauses are treated as follows: V is DA "due to the
 *  continue" iff V is DA before the continue statement or V is DA at
 *  the end of any intervening finally block.  V is DU "due to the
 *  continue" iff any intervening finally cannot complete normally or V
 *  is DU at the end of every intervening finally block.  This "due to
 *  the continue" concept is then used in the spec for the loops.
 *
 *  <p>Similarly, break statements must consider intervening finally
 *  blocks.  For liveness analysis, a break statement for which any
 *  intervening finally cannot complete normally is not considered to
 *  cause the target statement to be able to complete normally. Then
 *  we say V is DA "due to the break" iff V is DA before the break or
 *  V is DA at the end of any intervening finally block.  V is DU "due
 *  to the break" iff any intervening finally cannot complete normally
 *  or V is DU at the break and at the end of every intervening
 *  finally block.  (I suspect this latter condition can be
 *  simplified.)  This "due to the break" is then used in the spec for
 *  all statements that can be "broken".
 *
 *  <p>The return statement is treated similarly.  V is DA "due to a
 *  return statement" iff V is DA before the return statement or V is
 *  DA at the end of any intervening finally block.  Note that we
 *  don't have to worry about the return expression because this
 *  concept is only used for construcrors.
 *
 *  <p>There is no spec in the JLS for when a variable is definitely
 *  assigned at the end of a constructor, which is needed for final
 *  fields (8.3.1.2).  We implement the rule that V is DA at the end
 *  of the constructor iff it is DA and the end of the body of the
 *  constructor and V is DA "due to" every return of the constructor.
 *
 *  <p>Intervening finally blocks similarly affect exception analysis.  An
 *  intervening finally that cannot complete normally allows us to ignore
 *  an otherwise uncaught exception.
 *
 *  <p>To implement the semantics of intervening finally clauses, all
 *  nonlocal transfers (break, continue, return, throw, method call that
 *  can throw a checked exception, and a constructor invocation that can
 *  thrown a checked exception) are recorded in a queue, and removed
 *  from the queue when we complete processing the target of the
 *  nonlocal transfer.  This allows us to modify the queue in accordance
 *  with the above rules when we encounter a finally clause.  The only
 *  exception to this [no pun intended] is that checked exceptions that
 *  are known to be caught or declared to be caught in the enclosing
 *  method are not recorded in the queue, but instead are recorded in a
 *  global variable "Set<Type> thrown" that records the type of all
 *  exceptions that can be thrown.
 *
 *  <p>Other minor issues the treatment of members of other classes
 *  (always considered DA except that within an anonymous class
 *  constructor, where DA status from the enclosing scope is
 *  preserved), treatment of the case expression (V is DA before the
 *  case expression iff V is DA after the switch expression),
 *  treatment of variables declared in a switch block (the implied
 *  DA/DU status after the switch expression is DU and not DA for
 *  variables defined in a switch block), the treatment of boolean ?:
 *  expressions (The JLS rules only handle b and c non-boolean; the
 *  new rule is that if b and c are boolean valued, then V is
 *  (un)assigned after a?b:c when true/false iff V is (un)assigned
 *  after b when true/false and V is (un)assigned after c when
 *  true/false).
 *
 *  <p>There is the remaining question of what syntactic forms constitute a
 *  reference to a variable.  It is conventional to allow this.x on the
 *  left-hand-side to initialize a final instance field named x, yet
 *  this.x isn't considered a "use" when appearing on a right-hand-side
 *  in most implementations.  Should parentheses affect what is
 *  considered a variable reference?  The simplest rule would be to
 *  allow unqualified forms only, parentheses optional, and phase out
 *  support for assigning to a final field via this.x.
 *
 *  <p><b>This is NOT part of any supported API.
 *  If you write code that depends on this, you do so at your own risk.
 *  This code and its internal interfaces are subject to change or
 *  deletion without notice.</b>
 */
public class Flow extends TreeScanner {
    protected static final Context.Key<Flow> flowKey =
        new Context.Key<Flow>();

    private final Names names;
    private final Log log;
    private final Symtab syms;
    private final Types types;
    private final Check chk;
    private       TreeMaker make;
    private final Resolve rs;
    private Env<AttrContext> attrEnv;
    private       Lint lint;
    private final boolean allowImprovedRethrowAnalysis;
    private final boolean allowImprovedCatchAnalysis;

    public static Flow instance(Context context) {
        Flow instance = context.get(flowKey);
        if (instance == null)
            instance = new Flow(context);
        return instance;
    }

    protected Flow(Context context) {
        context.put(flowKey, this);
        names = Names.instance(context);
        log = Log.instance(context);
        syms = Symtab.instance(context);
        types = Types.instance(context);
        chk = Check.instance(context);
        lint = Lint.instance(context);
        rs = Resolve.instance(context);
        Source source = Source.instance(context);
        allowImprovedRethrowAnalysis = source.allowImprovedRethrowAnalysis();
        allowImprovedCatchAnalysis = source.allowImprovedCatchAnalysis();
    }

    /** A flag that indicates whether the last statement could
     *  complete normally.
     */
    private boolean alive;

    /** The set of definitely assigned variables.
     */
    Bits inits;

    /** The set of definitely unassigned variables.
     */
    Bits uninits;

    HashMap<Symbol, List<Type>> preciseRethrowTypes;

    /** The set of variables that are definitely unassigned everywhere
     *  in current try block. This variable is maintained lazily; it is
     *  updated only when something gets removed from uninits,
     *  typically by being assigned in reachable code.  To obtain the
     *  correct set of variables which are definitely unassigned
     *  anywhere in current try block, intersect uninitsTry and
     *  uninits.
     */
    Bits uninitsTry;

    /** When analyzing a condition, inits and uninits are null.
     *  Instead we have:
     */
    Bits initsWhenTrue;
    Bits initsWhenFalse;
    Bits uninitsWhenTrue;
    Bits uninitsWhenFalse;

    /** A mapping from addresses to variable symbols.
     */
    VarSymbol[] vars;

    /** The current class being defined.
     */
    JCClassDecl classDef;

    /** The first variable sequence number in this class definition.
     */
    int firstadr;

    /** The next available variable sequence number.
     */
    int nextadr;

    /** The list of possibly thrown declarable exceptions.
     */
    List<Type> thrown;

    /** The list of exceptions that are either caught or declared to be
     *  thrown.
     */
    List<Type> caught;

    /** The list of unreferenced automatic resources.
     */
    Scope unrefdResources;

    /** Set when processing a loop body the second time for DU analysis. */
    boolean loopPassTwo = false;

    /*-------------------- Environments ----------------------*/

    /** A pending exit.  These are the statements return, break, and
     *  continue.  In addition, exception-throwing expressions or
     *  statements are put here when not known to be caught.  This
     *  will typically result in an error unless it is within a
     *  try-finally whose finally block cannot complete normally.
     */
    static class PendingExit {
        JCTree tree;
        Bits inits;
        Bits uninits;
        Type thrown;
        PendingExit(JCTree tree, Bits inits, Bits uninits) {
            this.tree = tree;
            this.inits = inits.dup();
            this.uninits = uninits.dup();
        }
        PendingExit(JCTree tree, Type thrown) {
            this.tree = tree;
            this.thrown = thrown;
        }
    }

    /** The currently pending exits that go from current inner blocks
     *  to an enclosing block, in source order.
     */
    ListBuffer<PendingExit> pendingExits;

    /*-------------------- Exceptions ----------------------*/

    /** Complain that pending exceptions are not caught.
     */
    void errorUncaught() {
        for (PendingExit exit = pendingExits.next();
             exit != null;
             exit = pendingExits.next()) {
            if (classDef != null &&
                classDef.pos == exit.tree.pos) {
                log.error(exit.tree.pos(),
                        "unreported.exception.default.constructor",
                        exit.thrown);
            } else if (exit.tree.getTag() == JCTree.VARDEF &&
                    ((JCVariableDecl)exit.tree).sym.isResourceVariable()) {
                log.error(exit.tree.pos(),
                        "unreported.exception.implicit.close",
                        exit.thrown,
                        ((JCVariableDecl)exit.tree).sym.name);
            } else {
                log.error(exit.tree.pos(),
                        "unreported.exception.need.to.catch.or.throw",
                        exit.thrown);
            }
        }
    }

    /** Record that exception is potentially thrown and check that it
     *  is caught.
     */
    void markThrown(JCTree tree, Type exc) {
        if (!chk.isUnchecked(tree.pos(), exc)) {
            if (!chk.isHandled(exc, caught)) {
                pendingExits.append(new PendingExit(tree, exc));
            }
            thrown = chk.incl(exc, thrown);
        }
    }

    /*-------------- Processing variables ----------------------*/

    /** Do we need to track init/uninit state of this symbol?
     *  I.e. is symbol either a local or a blank final variable?
     */
    boolean trackable(VarSymbol sym) {
        return
            (sym.owner.kind == MTH ||
             ((sym.flags() & (FINAL | HASINIT | PARAMETER)) == FINAL &&
              classDef.sym.isEnclosedBy((ClassSymbol)sym.owner)));
    }

    /** Initialize new trackable variable by setting its address field
     *  to the next available sequence number and entering it under that
     *  index into the vars array.
     */
    void newVar(VarSymbol sym) {
        if (nextadr == vars.length) {
            VarSymbol[] newvars = new VarSymbol[nextadr * 2];
            System.arraycopy(vars, 0, newvars, 0, nextadr);
            vars = newvars;
        }
        sym.adr = nextadr;
        vars[nextadr] = sym;
        inits.excl(nextadr);
        uninits.incl(nextadr);
        nextadr++;
    }

    /** Record an initialization of a trackable variable.
     */
    void letInit(DiagnosticPosition pos, VarSymbol sym) {
        if (sym.adr >= firstadr && trackable(sym)) {
            if ((sym.flags() & FINAL) != 0) {
                if ((sym.flags() & PARAMETER) != 0) {
                    if ((sym.flags() & UNION) != 0) { //multi-catch parameter
                        log.error(pos, "multicatch.parameter.may.not.be.assigned",
                                  sym);
                    }
                    else {
                        log.error(pos, "final.parameter.may.not.be.assigned",
                              sym);
                    }
                } else if (!uninits.isMember(sym.adr)) {
                    log.error(pos,
                              loopPassTwo
                              ? "var.might.be.assigned.in.loop"
                              : "var.might.already.be.assigned",
                              sym);
                } else if (!inits.isMember(sym.adr)) {
                    // reachable assignment
                    uninits.excl(sym.adr);
                    uninitsTry.excl(sym.adr);
                } else {
                    //log.rawWarning(pos, "unreachable assignment");//DEBUG
                    uninits.excl(sym.adr);
                }
            }
            inits.incl(sym.adr);
        } else if ((sym.flags() & FINAL) != 0) {
            log.error(pos, "var.might.already.be.assigned", sym);
        }
    }

    /** If tree is either a simple name or of the form this.name or
     *  C.this.name, and tree represents a trackable variable,
     *  record an initialization of the variable.
     */
    void letInit(JCTree tree) {
        tree = TreeInfo.skipParens(tree);
        if (tree.getTag() == JCTree.IDENT || tree.getTag() == JCTree.SELECT) {
            Symbol sym = TreeInfo.symbol(tree);
            if (sym.kind == VAR) {
                letInit(tree.pos(), (VarSymbol)sym);
            }
        }
    }

    /** Check that trackable variable is initialized.
     */
    void checkInit(DiagnosticPosition pos, VarSymbol sym) {
        if ((sym.adr >= firstadr || sym.owner.kind != TYP) &&
            trackable(sym) &&
            !inits.isMember(sym.adr)) {
            log.error(pos, "var.might.not.have.been.initialized",
                      sym);
            inits.incl(sym.adr);
        }
    }

    /*-------------------- Handling jumps ----------------------*/

    /** Record an outward transfer of control. */
    void recordExit(JCTree tree) {
        pendingExits.append(new PendingExit(tree, inits, uninits));
        markDead();
    }

    /** Resolve all breaks of this statement. */
    boolean resolveBreaks(JCTree tree,
                          ListBuffer<PendingExit> oldPendingExits) {
        boolean result = false;
        List<PendingExit> exits = pendingExits.toList();
        pendingExits = oldPendingExits;
        for (; exits.nonEmpty(); exits = exits.tail) {
            PendingExit exit = exits.head;
            if (exit.tree.getTag() == JCTree.BREAK &&
                ((JCBreak) exit.tree).target == tree) {
                inits.andSet(exit.inits);
                uninits.andSet(exit.uninits);
                result = true;
            } else {
                pendingExits.append(exit);
            }
        }
        return result;
    }

    /** Resolve all continues of this statement. */
    boolean resolveContinues(JCTree tree) {
        boolean result = false;
        List<PendingExit> exits = pendingExits.toList();
        pendingExits = new ListBuffer<PendingExit>();
        for (; exits.nonEmpty(); exits = exits.tail) {
            PendingExit exit = exits.head;
            if (exit.tree.getTag() == JCTree.CONTINUE &&
                ((JCContinue) exit.tree).target == tree) {
                inits.andSet(exit.inits);
                uninits.andSet(exit.uninits);
                result = true;
            } else {
                pendingExits.append(exit);
            }
        }
        return result;
    }

    /** Record that statement is unreachable.
     */
    void markDead() {
        inits.inclRange(firstadr, nextadr);
        uninits.inclRange(firstadr, nextadr);
        alive = false;
    }

    /** Split (duplicate) inits/uninits into WhenTrue/WhenFalse sets
     */
    void split(boolean setToNull) {
        initsWhenFalse = inits.dup();
        uninitsWhenFalse = uninits.dup();
        initsWhenTrue = inits;
        uninitsWhenTrue = uninits;
        if (setToNull)
            inits = uninits = null;
    }

    /** Merge (intersect) inits/uninits from WhenTrue/WhenFalse sets.
     */
    void merge() {
        inits = initsWhenFalse.andSet(initsWhenTrue);
        uninits = uninitsWhenFalse.andSet(uninitsWhenTrue);
    }

/* ************************************************************************
 * Visitor methods for statements and definitions
 *************************************************************************/

    /** Analyze a definition.
     */
    void scanDef(JCTree tree) {
        scanStat(tree);
        if (tree != null && tree.getTag() == JCTree.BLOCK && !alive) {
            log.error(tree.pos(),
                      "initializer.must.be.able.to.complete.normally");
        }
    }

    /** Analyze a statement. Check that statement is reachable.
     */
    void scanStat(JCTree tree) {
        if (!alive && tree != null) {
            log.error(tree.pos(), "unreachable.stmt");
            if (tree.getTag() != JCTree.SKIP) alive = true;
        }
        scan(tree);
    }

    /** Analyze list of statements.
     */
    void scanStats(List<? extends JCStatement> trees) {
        if (trees != null)
            for (List<? extends JCStatement> l = trees; l.nonEmpty(); l = l.tail)
                scanStat(l.head);
    }

    /** Analyze an expression. Make sure to set (un)inits rather than
     *  (un)initsWhenTrue(WhenFalse) on exit.
     */
    void scanExpr(JCTree tree) {
        if (tree != null) {
            scan(tree);
            if (inits == null) merge();
        }
    }

    /** Analyze a list of expressions.
     */
    void scanExprs(List<? extends JCExpression> trees) {
        if (trees != null)
            for (List<? extends JCExpression> l = trees; l.nonEmpty(); l = l.tail)
                scanExpr(l.head);
    }

    /** Analyze a condition. Make sure to set (un)initsWhenTrue(WhenFalse)
     *  rather than (un)inits on exit.
     */
    void scanCond(JCTree tree) {
        if (tree.type.isFalse()) {
            if (inits == null) merge();
            initsWhenTrue = inits.dup();
            initsWhenTrue.inclRange(firstadr, nextadr);
            uninitsWhenTrue = uninits.dup();
            uninitsWhenTrue.inclRange(firstadr, nextadr);
            initsWhenFalse = inits;
            uninitsWhenFalse = uninits;
        } else if (tree.type.isTrue()) {
            if (inits == null) merge();
            initsWhenFalse = inits.dup();
            initsWhenFalse.inclRange(firstadr, nextadr);
            uninitsWhenFalse = uninits.dup();
            uninitsWhenFalse.inclRange(firstadr, nextadr);
            initsWhenTrue = inits;
            uninitsWhenTrue = uninits;
        } else {
            scan(tree);
            if (inits != null)
                split(tree.type != syms.unknownType);
        }
        if (tree.type != syms.unknownType)
            inits = uninits = null;
    }

    /* ------------ Visitor methods for various sorts of trees -------------*/

    public void visitClassDef(JCClassDecl tree) {
        if (tree.sym == null) return;

        JCClassDecl classDefPrev = classDef;
        List<Type> thrownPrev = thrown;
        List<Type> caughtPrev = caught;
        boolean alivePrev = alive;
        int firstadrPrev = firstadr;
        int nextadrPrev = nextadr;
        ListBuffer<PendingExit> pendingExitsPrev = pendingExits;
        Lint lintPrev = lint;

        pendingExits = new ListBuffer<PendingExit>();
        if (tree.name != names.empty) {
            caught = List.nil();
            firstadr = nextadr;
        }
        classDef = tree;
        thrown = List.nil();
        lint = lint.augment(tree.sym.attributes_field);

        try {
            // define all the static fields
            for (List<JCTree> l = tree.defs; l.nonEmpty(); l = l.tail) {
                if (l.head.getTag() == JCTree.VARDEF) {
                    JCVariableDecl def = (JCVariableDecl)l.head;
                    if ((def.mods.flags & STATIC) != 0) {
                        VarSymbol sym = def.sym;
                        if (trackable(sym))
                            newVar(sym);
                    }
                }
            }

            // process all the static initializers
            for (List<JCTree> l = tree.defs; l.nonEmpty(); l = l.tail) {
                if (l.head.getTag() != JCTree.METHODDEF &&
                    (TreeInfo.flags(l.head) & STATIC) != 0) {
                    scanDef(l.head);
                    errorUncaught();
                }
            }

            // add intersection of all thrown clauses of initial constructors
            // to set of caught exceptions, unless class is anonymous.
            if (tree.name != names.empty) {
                boolean firstConstructor = true;
                for (List<JCTree> l = tree.defs; l.nonEmpty(); l = l.tail) {
                    if (TreeInfo.isInitialConstructor(l.head)) {
                        List<Type> mthrown =
                            ((JCMethodDecl) l.head).sym.type.getThrownTypes();
                        if (firstConstructor) {
                            caught = mthrown;
                            firstConstructor = false;
                        } else {
                            caught = chk.intersect(mthrown, caught);
                        }
                    }
                }
            }

            // define all the instance fields
            for (List<JCTree> l = tree.defs; l.nonEmpty(); l = l.tail) {
                if (l.head.getTag() == JCTree.VARDEF) {
                    JCVariableDecl def = (JCVariableDecl)l.head;
                    if ((def.mods.flags & STATIC) == 0) {
                        VarSymbol sym = def.sym;
                        if (trackable(sym))
                            newVar(sym);
                    }
                }
            }

            // process all the instance initializers
            for (List<JCTree> l = tree.defs; l.nonEmpty(); l = l.tail) {
                if (l.head.getTag() != JCTree.METHODDEF &&
                    (TreeInfo.flags(l.head) & STATIC) == 0) {
                    scanDef(l.head);
                    errorUncaught();
                }
            }

            // in an anonymous class, add the set of thrown exceptions to
            // the throws clause of the synthetic constructor and propagate
            // outwards.
            // Changing the throws clause on the fly is okay here because
            // the anonymous constructor can't be invoked anywhere else,
            // and its type hasn't been cached.
            if (tree.name == names.empty) {
                for (List<JCTree> l = tree.defs; l.nonEmpty(); l = l.tail) {
                    if (TreeInfo.isInitialConstructor(l.head)) {
                        JCMethodDecl mdef = (JCMethodDecl)l.head;
                        mdef.thrown = make.Types(thrown);
                        mdef.sym.type = types.createMethodTypeWithThrown(mdef.sym.type, thrown);
                    }
                }
                thrownPrev = chk.union(thrown, thrownPrev);
            }

            // process all the methods
            for (List<JCTree> l = tree.defs; l.nonEmpty(); l = l.tail) {
                if (l.head.getTag() == JCTree.METHODDEF) {
                    scan(l.head);
                    errorUncaught();
                }
            }

            thrown = thrownPrev;
        } finally {
            pendingExits = pendingExitsPrev;
            alive = alivePrev;
            nextadr = nextadrPrev;
            firstadr = firstadrPrev;
            caught = caughtPrev;
            classDef = classDefPrev;
            lint = lintPrev;
        }
    }

    public void visitMethodDef(JCMethodDecl tree) {
        if (tree.body == null) return;

        List<Type> caughtPrev = caught;
        List<Type> mthrown = tree.sym.type.getThrownTypes();
        Bits initsPrev = inits.dup();
        Bits uninitsPrev = uninits.dup();
        int nextadrPrev = nextadr;
        int firstadrPrev = firstadr;
        Lint lintPrev = lint;

        lint = lint.augment(tree.sym.attributes_field);

        Assert.check(pendingExits.isEmpty());

        try {
            boolean isInitialConstructor =
                TreeInfo.isInitialConstructor(tree);

            if (!isInitialConstructor)
                firstadr = nextadr;
            for (List<JCVariableDecl> l = tree.params; l.nonEmpty(); l = l.tail) {
                JCVariableDecl def = l.head;
                scan(def);
                inits.incl(def.sym.adr);
                uninits.excl(def.sym.adr);
            }
            if (isInitialConstructor)
                caught = chk.union(caught, mthrown);
            else if ((tree.sym.flags() & (BLOCK | STATIC)) != BLOCK)
                caught = mthrown;
            // else we are in an instance initializer block;
            // leave caught unchanged.

            alive = true;
            scanStat(tree.body);

            if (alive && tree.sym.type.getReturnType().tag != VOID)
                log.error(TreeInfo.diagEndPos(tree.body), "missing.ret.stmt");

            if (isInitialConstructor) {
                for (int i = firstadr; i < nextadr; i++)
                    if (vars[i].owner == classDef.sym)
                        checkInit(TreeInfo.diagEndPos(tree.body), vars[i]);
            }
            List<PendingExit> exits = pendingExits.toList();
            pendingExits = new ListBuffer<PendingExit>();
            while (exits.nonEmpty()) {
                PendingExit exit = exits.head;
                exits = exits.tail;
                if (exit.thrown == null) {
                    Assert.check(exit.tree.getTag() == JCTree.RETURN);
                    if (isInitialConstructor) {
                        inits = exit.inits;
                        for (int i = firstadr; i < nextadr; i++)
                            checkInit(exit.tree.pos(), vars[i]);
                    }
                } else {
                    // uncaught throws will be reported later
                    pendingExits.append(exit);
                }
            }
        } finally {
            inits = initsPrev;
            uninits = uninitsPrev;
            nextadr = nextadrPrev;
            firstadr = firstadrPrev;
            caught = caughtPrev;
            lint = lintPrev;
        }
    }

    public void visitVarDef(JCVariableDecl tree) {
        boolean track = trackable(tree.sym);
        if (track && tree.sym.owner.kind == MTH) newVar(tree.sym);
        if (tree.init != null) {
            Lint lintPrev = lint;
            lint = lint.augment(tree.sym.attributes_field);
            try{
                scanExpr(tree.init);
                if (track) letInit(tree.pos(), tree.sym);
            } finally {
                lint = lintPrev;
            }
        }
    }

    public void visitBlock(JCBlock tree) {
        int nextadrPrev = nextadr;
        scanStats(tree.stats);
        nextadr = nextadrPrev;
    }

    public void visitDoLoop(JCDoWhileLoop tree) {
        ListBuffer<PendingExit> prevPendingExits = pendingExits;
        boolean prevLoopPassTwo = loopPassTwo;
        pendingExits = new ListBuffer<PendingExit>();
        int prevErrors = log.nerrors;
        do {
            Bits uninitsEntry = uninits.dup();
            uninitsEntry.excludeFrom(nextadr);
            scanStat(tree.body);
            alive |= resolveContinues(tree);
            scanCond(tree.cond);
            if (log.nerrors !=  prevErrors ||
                loopPassTwo ||
                uninitsEntry.dup().diffSet(uninitsWhenTrue).nextBit(firstadr)==-1)
                break;
            inits = initsWhenTrue;
            uninits = uninitsEntry.andSet(uninitsWhenTrue);
            loopPassTwo = true;
            alive = true;
        } while (true);
        loopPassTwo = prevLoopPassTwo;
        inits = initsWhenFalse;
        uninits = uninitsWhenFalse;
        alive = alive && !tree.cond.type.isTrue();
        alive |= resolveBreaks(tree, prevPendingExits);
    }

    public void visitWhileLoop(JCWhileLoop tree) {
        ListBuffer<PendingExit> prevPendingExits = pendingExits;
        boolean prevLoopPassTwo = loopPassTwo;
        Bits initsCond;
        Bits uninitsCond;
        pendingExits = new ListBuffer<PendingExit>();
        int prevErrors = log.nerrors;
        do {
            Bits uninitsEntry = uninits.dup();
            uninitsEntry.excludeFrom(nextadr);
            scanCond(tree.cond);
            initsCond = initsWhenFalse;
            uninitsCond = uninitsWhenFalse;
            inits = initsWhenTrue;
            uninits = uninitsWhenTrue;
            alive = !tree.cond.type.isFalse();
            scanStat(tree.body);
            alive |= resolveContinues(tree);
            if (log.nerrors != prevErrors ||
                loopPassTwo ||
                uninitsEntry.dup().diffSet(uninits).nextBit(firstadr) == -1)
                break;
            uninits = uninitsEntry.andSet(uninits);
            loopPassTwo = true;
            alive = true;
        } while (true);
        loopPassTwo = prevLoopPassTwo;
        inits = initsCond;
        uninits = uninitsCond;
        alive = resolveBreaks(tree, prevPendingExits) ||
            !tree.cond.type.isTrue();
    }

    public void visitForLoop(JCForLoop tree) {
        ListBuffer<PendingExit> prevPendingExits = pendingExits;
        boolean prevLoopPassTwo = loopPassTwo;
        int nextadrPrev = nextadr;
        scanStats(tree.init);
        Bits initsCond;
        Bits uninitsCond;
        pendingExits = new ListBuffer<PendingExit>();
        int prevErrors = log.nerrors;
        do {
            Bits uninitsEntry = uninits.dup();
            uninitsEntry.excludeFrom(nextadr);
            if (tree.cond != null) {
                scanCond(tree.cond);
                initsCond = initsWhenFalse;
                uninitsCond = uninitsWhenFalse;
                inits = initsWhenTrue;
                uninits = uninitsWhenTrue;
                alive = !tree.cond.type.isFalse();
            } else {
                initsCond = inits.dup();
                initsCond.inclRange(firstadr, nextadr);
                uninitsCond = uninits.dup();
                uninitsCond.inclRange(firstadr, nextadr);
                alive = true;
            }
            scanStat(tree.body);
            alive |= resolveContinues(tree);
            scan(tree.step);
            if (log.nerrors != prevErrors ||
                loopPassTwo ||
                uninitsEntry.dup().diffSet(uninits).nextBit(firstadr) == -1)
                break;
            uninits = uninitsEntry.andSet(uninits);
            loopPassTwo = true;
            alive = true;
        } while (true);
        loopPassTwo = prevLoopPassTwo;
        inits = initsCond;
        uninits = uninitsCond;
        alive = resolveBreaks(tree, prevPendingExits) ||
            tree.cond != null && !tree.cond.type.isTrue();
        nextadr = nextadrPrev;
    }

    public void visitForeachLoop(JCEnhancedForLoop tree) {
        visitVarDef(tree.var);

        ListBuffer<PendingExit> prevPendingExits = pendingExits;
        boolean prevLoopPassTwo = loopPassTwo;
        int nextadrPrev = nextadr;
        scan(tree.expr);
        Bits initsStart = inits.dup();
        Bits uninitsStart = uninits.dup();

        letInit(tree.pos(), tree.var.sym);
        pendingExits = new ListBuffer<PendingExit>();
        int prevErrors = log.nerrors;
        do {
            Bits uninitsEntry = uninits.dup();
            uninitsEntry.excludeFrom(nextadr);
            scanStat(tree.body);
            alive |= resolveContinues(tree);
            if (log.nerrors != prevErrors ||
                loopPassTwo ||
                uninitsEntry.dup().diffSet(uninits).nextBit(firstadr) == -1)
                break;
            uninits = uninitsEntry.andSet(uninits);
            loopPassTwo = true;
            alive = true;
        } while (true);
        loopPassTwo = prevLoopPassTwo;
        inits = initsStart;
        uninits = uninitsStart.andSet(uninits);
        resolveBreaks(tree, prevPendingExits);
        alive = true;
        nextadr = nextadrPrev;
    }

    public void visitLabelled(JCLabeledStatement tree) {
        ListBuffer<PendingExit> prevPendingExits = pendingExits;
        pendingExits = new ListBuffer<PendingExit>();
        scanStat(tree.body);
        alive |= resolveBreaks(tree, prevPendingExits);
    }

    public void visitSwitch(JCSwitch tree) {
        ListBuffer<PendingExit> prevPendingExits = pendingExits;
        pendingExits = new ListBuffer<PendingExit>();
        int nextadrPrev = nextadr;
        scanExpr(tree.selector);
        Bits initsSwitch = inits;
        Bits uninitsSwitch = uninits.dup();
        boolean hasDefault = false;
        for (List<JCCase> l = tree.cases; l.nonEmpty(); l = l.tail) {
            alive = true;
            inits = initsSwitch.dup();
            uninits = uninits.andSet(uninitsSwitch);
            JCCase c = l.head;
            if (c.pat == null)
                hasDefault = true;
            else
                scanExpr(c.pat);
            scanStats(c.stats);
            addVars(c.stats, initsSwitch, uninitsSwitch);
            // Warn about fall-through if lint switch fallthrough enabled.
            if (!loopPassTwo &&
                alive &&
                lint.isEnabled(Lint.LintCategory.FALLTHROUGH) &&
                c.stats.nonEmpty() && l.tail.nonEmpty())
                log.warning(Lint.LintCategory.FALLTHROUGH,
                            l.tail.head.pos(),
                            "possible.fall-through.into.case");
        }
        if (!hasDefault) {
            inits.andSet(initsSwitch);
            alive = true;
        }
        alive |= resolveBreaks(tree, prevPendingExits);
        nextadr = nextadrPrev;
    }
    // where
        /** Add any variables defined in stats to inits and uninits. */
        private static void addVars(List<JCStatement> stats, Bits inits,
                                    Bits uninits) {
            for (;stats.nonEmpty(); stats = stats.tail) {
                JCTree stat = stats.head;
                if (stat.getTag() == JCTree.VARDEF) {
                    int adr = ((JCVariableDecl) stat).sym.adr;
                    inits.excl(adr);
                    uninits.incl(adr);
                }
            }
        }

    public void visitTry(JCTry tree) {
        List<Type> caughtPrev = caught;
        List<Type> thrownPrev = thrown;
        thrown = List.nil();
        for (List<JCCatch> l = tree.catchers; l.nonEmpty(); l = l.tail) {
            List<JCExpression> subClauses = TreeInfo.isMultiCatch(l.head) ?
                    ((JCTypeUnion)l.head.param.vartype).alternatives :
                    List.of(l.head.param.vartype);
            for (JCExpression ct : subClauses) {
                caught = chk.incl(ct.type, caught);
            }
        }
        ListBuffer<JCVariableDecl> resourceVarDecls = ListBuffer.lb();
        Bits uninitsTryPrev = uninitsTry;
        ListBuffer<PendingExit> prevPendingExits = pendingExits;
        pendingExits = new ListBuffer<PendingExit>();
        Bits initsTry = inits.dup();
        uninitsTry = uninits.dup();
        for (JCTree resource : tree.resources) {
            if (resource instanceof JCVariableDecl) {
                JCVariableDecl vdecl = (JCVariableDecl) resource;
                visitVarDef(vdecl);
                unrefdResources.enter(vdecl.sym);
                resourceVarDecls.append(vdecl);
            } else if (resource instanceof JCExpression) {
                scanExpr((JCExpression) resource);
            } else {
                throw new AssertionError(tree);  // parser error
            }
        }
        for (JCTree resource : tree.resources) {
            List<Type> closeableSupertypes = resource.type.isCompound() ?
                types.interfaces(resource.type).prepend(types.supertype(resource.type)) :
                List.of(resource.type);
            for (Type sup : closeableSupertypes) {
                if (types.asSuper(sup, syms.autoCloseableType.tsym) != null) {
                    Symbol closeMethod = rs.resolveQualifiedMethod(tree,
                            attrEnv,
                            sup,
                            names.close,
                            List.<Type>nil(),
                            List.<Type>nil());
                    Type mt = types.memberType(resource.type, closeMethod);
                    if (closeMethod.kind == MTH) {
                        for (Type t : mt.getThrownTypes()) {
                            markThrown(resource, t);
                        }
                    }
                }
            }
        }
        scanStat(tree.body);
        List<Type> thrownInTry = allowImprovedCatchAnalysis ?
            chk.union(thrown, List.of(syms.runtimeExceptionType, syms.errorType)) :
            thrown;
        thrown = thrownPrev;
        caught = caughtPrev;
        boolean aliveEnd = alive;
        uninitsTry.andSet(uninits);
        Bits initsEnd = inits;
        Bits uninitsEnd = uninits;
        int nextadrCatch = nextadr;

        if (!resourceVarDecls.isEmpty() &&
                lint.isEnabled(Lint.LintCategory.TRY)) {
            for (JCVariableDecl resVar : resourceVarDecls) {
                if (unrefdResources.includes(resVar.sym)) {
                    log.warning(Lint.LintCategory.TRY, resVar.pos(),
                                "try.resource.not.referenced", resVar.sym);
                    unrefdResources.remove(resVar.sym);
                }
            }
        }

        List<Type> caughtInTry = List.nil();
        for (List<JCCatch> l = tree.catchers; l.nonEmpty(); l = l.tail) {
            alive = true;
            JCVariableDecl param = l.head.param;
            List<JCExpression> subClauses = TreeInfo.isMultiCatch(l.head) ?
                    ((JCTypeUnion)l.head.param.vartype).alternatives :
                    List.of(l.head.param.vartype);
            List<Type> ctypes = List.nil();
            List<Type> rethrownTypes = chk.diff(thrownInTry, caughtInTry);
            for (JCExpression ct : subClauses) {
                Type exc = ct.type;
                if (exc != syms.unknownType) {
                    ctypes = ctypes.append(exc);
                    if (types.isSameType(exc, syms.objectType))
                        continue;
                    checkCaughtType(l.head.pos(), exc, thrownInTry, caughtInTry);
                    caughtInTry = chk.incl(exc, caughtInTry);
                }
            }
            inits = initsTry.dup();
            uninits = uninitsTry.dup();
            scan(param);
            inits.incl(param.sym.adr);
            uninits.excl(param.sym.adr);
            preciseRethrowTypes.put(param.sym, chk.intersect(ctypes, rethrownTypes));
            scanStat(l.head.body);
            initsEnd.andSet(inits);
            uninitsEnd.andSet(uninits);
            nextadr = nextadrCatch;
            preciseRethrowTypes.remove(param.sym);
            aliveEnd |= alive;
        }
        if (tree.finalizer != null) {
            List<Type> savedThrown = thrown;
            thrown = List.nil();
            inits = initsTry.dup();
            uninits = uninitsTry.dup();
            ListBuffer<PendingExit> exits = pendingExits;
            pendingExits = prevPendingExits;
            alive = true;
            scanStat(tree.finalizer);
            if (!alive) {
                // discard exits and exceptions from try and finally
                thrown = chk.union(thrown, thrownPrev);
                if (!loopPassTwo &&
                    lint.isEnabled(Lint.LintCategory.FINALLY)) {
                    log.warning(Lint.LintCategory.FINALLY,
                            TreeInfo.diagEndPos(tree.finalizer),
                            "finally.cannot.complete");
                }
            } else {
                thrown = chk.union(thrown, chk.diff(thrownInTry, caughtInTry));
                thrown = chk.union(thrown, savedThrown);
                uninits.andSet(uninitsEnd);
                // FIX: this doesn't preserve source order of exits in catch
                // versus finally!
                while (exits.nonEmpty()) {
                    PendingExit exit = exits.next();
                    if (exit.inits != null) {
                        exit.inits.orSet(inits);
                        exit.uninits.andSet(uninits);
                    }
                    pendingExits.append(exit);
                }
                inits.orSet(initsEnd);
                alive = aliveEnd;
            }
        } else {
            thrown = chk.union(thrown, chk.diff(thrownInTry, caughtInTry));
            inits = initsEnd;
            uninits = uninitsEnd;
            alive = aliveEnd;
            ListBuffer<PendingExit> exits = pendingExits;
            pendingExits = prevPendingExits;
            while (exits.nonEmpty()) pendingExits.append(exits.next());
        }
        uninitsTry.andSet(uninitsTryPrev).andSet(uninits);
    }

    void checkCaughtType(DiagnosticPosition pos, Type exc, List<Type> thrownInTry, List<Type> caughtInTry) {
        if (chk.subset(exc, caughtInTry)) {
            log.error(pos, "except.already.caught", exc);
        } else if (!chk.isUnchecked(pos, exc) &&
                !isExceptionOrThrowable(exc) &&
                !chk.intersects(exc, thrownInTry)) {
            log.error(pos, "except.never.thrown.in.try", exc);
        } else if (allowImprovedCatchAnalysis) {
            List<Type> catchableThrownTypes = chk.intersect(List.of(exc), thrownInTry);
            // 'catchableThrownTypes' cannnot possibly be empty - if 'exc' was an
            // unchecked exception, the result list would not be empty, as the augmented
            // thrown set includes { RuntimeException, Error }; if 'exc' was a checked
            // exception, that would have been covered in the branch above
            if (chk.diff(catchableThrownTypes, caughtInTry).isEmpty() &&
                    !isExceptionOrThrowable(exc)) {
                String key = catchableThrownTypes.length() == 1 ?
                        "unreachable.catch" :
                        "unreachable.catch.1";
                log.warning(pos, key, catchableThrownTypes);
            }
        }
    }
    //where
        private boolean isExceptionOrThrowable(Type exc) {
            return exc.tsym == syms.throwableType.tsym ||
                exc.tsym == syms.exceptionType.tsym;
        }


    public void visitConditional(JCConditional tree) {
        scanCond(tree.cond);
        Bits initsBeforeElse = initsWhenFalse;
        Bits uninitsBeforeElse = uninitsWhenFalse;
        inits = initsWhenTrue;
        uninits = uninitsWhenTrue;
        if (tree.truepart.type.tag == BOOLEAN &&
            tree.falsepart.type.tag == BOOLEAN) {
            // if b and c are boolean valued, then
            // v is (un)assigned after a?b:c when true iff
            //    v is (un)assigned after b when true and
            //    v is (un)assigned after c when true
            scanCond(tree.truepart);
            Bits initsAfterThenWhenTrue = initsWhenTrue.dup();
            Bits initsAfterThenWhenFalse = initsWhenFalse.dup();
            Bits uninitsAfterThenWhenTrue = uninitsWhenTrue.dup();
            Bits uninitsAfterThenWhenFalse = uninitsWhenFalse.dup();
            inits = initsBeforeElse;
            uninits = uninitsBeforeElse;
            scanCond(tree.falsepart);
            initsWhenTrue.andSet(initsAfterThenWhenTrue);
            initsWhenFalse.andSet(initsAfterThenWhenFalse);
            uninitsWhenTrue.andSet(uninitsAfterThenWhenTrue);
            uninitsWhenFalse.andSet(uninitsAfterThenWhenFalse);
        } else {
            scanExpr(tree.truepart);
            Bits initsAfterThen = inits.dup();
            Bits uninitsAfterThen = uninits.dup();
            inits = initsBeforeElse;
            uninits = uninitsBeforeElse;
            scanExpr(tree.falsepart);
            inits.andSet(initsAfterThen);
            uninits.andSet(uninitsAfterThen);
        }
    }

    public void visitIf(JCIf tree) {
        scanCond(tree.cond);
        Bits initsBeforeElse = initsWhenFalse;
        Bits uninitsBeforeElse = uninitsWhenFalse;
        inits = initsWhenTrue;
        uninits = uninitsWhenTrue;
        scanStat(tree.thenpart);
        if (tree.elsepart != null) {
            boolean aliveAfterThen = alive;
            alive = true;
            Bits initsAfterThen = inits.dup();
            Bits uninitsAfterThen = uninits.dup();
            inits = initsBeforeElse;
            uninits = uninitsBeforeElse;
            scanStat(tree.elsepart);
            inits.andSet(initsAfterThen);
            uninits.andSet(uninitsAfterThen);
            alive = alive | aliveAfterThen;
        } else {
            inits.andSet(initsBeforeElse);
            uninits.andSet(uninitsBeforeElse);
            alive = true;
        }
    }



    public void visitBreak(JCBreak tree) {
        recordExit(tree);
    }

    public void visitContinue(JCContinue tree) {
        recordExit(tree);
    }

    public void visitReturn(JCReturn tree) {
        scanExpr(tree.expr);
        // if not initial constructor, should markDead instead of recordExit
        recordExit(tree);
    }

    public void visitThrow(JCThrow tree) {
        scanExpr(tree.expr);
        Symbol sym = TreeInfo.symbol(tree.expr);
        if (sym != null &&
            sym.kind == VAR &&
            (sym.flags() & (FINAL | EFFECTIVELY_FINAL)) != 0 &&
            preciseRethrowTypes.get(sym) != null &&
            allowImprovedRethrowAnalysis) {
            for (Type t : preciseRethrowTypes.get(sym)) {
                markThrown(tree, t);
            }
        }
        else {
            markThrown(tree, tree.expr.type);
        }
        markDead();
    }

    public void visitApply(JCMethodInvocation tree) {
        scanExpr(tree.meth);
        scanExprs(tree.args);
        for (List<Type> l = tree.meth.type.getThrownTypes(); l.nonEmpty(); l = l.tail)
            markThrown(tree, l.head);
    }

    public void visitNewClass(JCNewClass tree) {
        scanExpr(tree.encl);
        scanExprs(tree.args);
       // scan(tree.def);
        for (List<Type> l = tree.constructorType.getThrownTypes();
             l.nonEmpty();
             l = l.tail) {
            markThrown(tree, l.head);
        }
        List<Type> caughtPrev = caught;
        try {
            // If the new class expression defines an anonymous class,
            // analysis of the anonymous constructor may encounter thrown
            // types which are unsubstituted type variables.
            // However, since the constructor's actual thrown types have
            // already been marked as thrown, it is safe to simply include
            // each of the constructor's formal thrown types in the set of
            // 'caught/declared to be thrown' types, for the duration of
            // the class def analysis.
            if (tree.def != null)
                for (List<Type> l = tree.constructor.type.getThrownTypes();
                     l.nonEmpty();
                     l = l.tail) {
                    caught = chk.incl(l.head, caught);
                }
            scan(tree.def);
        }
        finally {
            caught = caughtPrev;
        }
    }

    public void visitNewArray(JCNewArray tree) {
        scanExprs(tree.dims);
        scanExprs(tree.elems);
    }

    public void visitAssert(JCAssert tree) {
        Bits initsExit = inits.dup();
        Bits uninitsExit = uninits.dup();
        scanCond(tree.cond);
        uninitsExit.andSet(uninitsWhenTrue);
        if (tree.detail != null) {
            inits = initsWhenFalse;
            uninits = uninitsWhenFalse;
            scanExpr(tree.detail);
        }
        inits = initsExit;
        uninits = uninitsExit;
    }

    public void visitAssign(JCAssign tree) {
        JCTree lhs = TreeInfo.skipParens(tree.lhs);
        if (!(lhs instanceof JCIdent)) scanExpr(lhs);
        scanExpr(tree.rhs);
        letInit(lhs);
    }

    public void visitAssignop(JCAssignOp tree) {
        scanExpr(tree.lhs);
        scanExpr(tree.rhs);
        letInit(tree.lhs);
    }

    public void visitUnary(JCUnary tree) {
        switch (tree.getTag()) {
        case JCTree.NOT:
            scanCond(tree.arg);
            Bits t = initsWhenFalse;
            initsWhenFalse = initsWhenTrue;
            initsWhenTrue = t;
            t = uninitsWhenFalse;
            uninitsWhenFalse = uninitsWhenTrue;
            uninitsWhenTrue = t;
            break;
        case JCTree.PREINC: case JCTree.POSTINC:
        case JCTree.PREDEC: case JCTree.POSTDEC:
            scanExpr(tree.arg);
            letInit(tree.arg);
            break;
        default:
            scanExpr(tree.arg);
        }
    }

    public void visitBinary(JCBinary tree) {
        switch (tree.getTag()) {
        case JCTree.AND:
            scanCond(tree.lhs);
            Bits initsWhenFalseLeft = initsWhenFalse;
            Bits uninitsWhenFalseLeft = uninitsWhenFalse;
            inits = initsWhenTrue;
            uninits = uninitsWhenTrue;
            scanCond(tree.rhs);
            initsWhenFalse.andSet(initsWhenFalseLeft);
            uninitsWhenFalse.andSet(uninitsWhenFalseLeft);
            break;
        case JCTree.OR:
            scanCond(tree.lhs);
            Bits initsWhenTrueLeft = initsWhenTrue;
            Bits uninitsWhenTrueLeft = uninitsWhenTrue;
            inits = initsWhenFalse;
            uninits = uninitsWhenFalse;
            scanCond(tree.rhs);
            initsWhenTrue.andSet(initsWhenTrueLeft);
            uninitsWhenTrue.andSet(uninitsWhenTrueLeft);
            break;
        default:
            scanExpr(tree.lhs);
            scanExpr(tree.rhs);
        }
    }

    public void visitIdent(JCIdent tree) {
        if (tree.sym.kind == VAR) {
            checkInit(tree.pos(), (VarSymbol)tree.sym);
            referenced(tree.sym);
        }
    }

    void referenced(Symbol sym) {
        unrefdResources.remove(sym);
    }

    public void visitTypeCast(JCTypeCast tree) {
        super.visitTypeCast(tree);
        if (!tree.type.isErroneous()
            && lint.isEnabled(Lint.LintCategory.CAST)
            && types.isSameType(tree.expr.type, tree.clazz.type)
            && !is292targetTypeCast(tree)) {
            log.warning(Lint.LintCategory.CAST,
                    tree.pos(), "redundant.cast", tree.expr.type);
        }
    }
    //where
        private boolean is292targetTypeCast(JCTypeCast tree) {
            boolean is292targetTypeCast = false;
            JCExpression expr = TreeInfo.skipParens(tree.expr);
            if (expr.getTag() == JCTree.APPLY) {
                JCMethodInvocation apply = (JCMethodInvocation)expr;
                Symbol sym = TreeInfo.symbol(apply.meth);
                is292targetTypeCast = sym != null &&
                    sym.kind == MTH &&
                    (sym.flags() & POLYMORPHIC_SIGNATURE) != 0;
            }
            return is292targetTypeCast;
        }

    public void visitTopLevel(JCCompilationUnit tree) {
        // Do nothing for TopLevel since each class is visited individually
    }

/**************************************************************************
 * main method
 *************************************************************************/

    /** Perform definite assignment/unassignment analysis on a tree.
     */
    public void analyzeTree(Env<AttrContext> env, TreeMaker make) {
        try {
            attrEnv = env;
            JCTree tree = env.tree;
            this.make = make;
            inits = new Bits();
            uninits = new Bits();
            uninitsTry = new Bits();
            initsWhenTrue = initsWhenFalse =
                uninitsWhenTrue = uninitsWhenFalse = null;
            if (vars == null)
                vars = new VarSymbol[32];
            else
                for (int i=0; i<vars.length; i++)
                    vars[i] = null;
            firstadr = 0;
            nextadr = 0;
            pendingExits = new ListBuffer<PendingExit>();
            preciseRethrowTypes = new HashMap<Symbol, List<Type>>();
            alive = true;
            this.thrown = this.caught = null;
            this.classDef = null;
            unrefdResources = new Scope(env.enclClass.sym);
            scan(tree);
        } finally {
            // note that recursive invocations of this method fail hard
            inits = uninits = uninitsTry = null;
            initsWhenTrue = initsWhenFalse =
                uninitsWhenTrue = uninitsWhenFalse = null;
            if (vars != null) for (int i=0; i<vars.length; i++)
                vars[i] = null;
            firstadr = 0;
            nextadr = 0;
            pendingExits = null;
            this.make = null;
            this.thrown = this.caught = null;
            this.classDef = null;
            unrefdResources = null;
        }
    }
}
