java.lang.ObjectThis 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.com.sun.tools.javac.tree.JCTree.Visitor
com.sun.tools.javac.tree.TreeScanner
com.sun.tools.javac.comp.Flow
The JLS has a number of problems in the specification of these flow analysis problems. This implementation attempts to address those issues.
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.
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
V is unassigned everywhere in a block if it is unassigned before the block and there is no reachable assignment to V within the block.
In 16.2.15, the third bullet (and all of its sub-bullets) for all try blocks is changed to
V is definitely unassigned before a catch block iff V is definitely unassigned everywhere in the try block.
The last bullet (and all of its sub-bullets) for try blocks that have a finally block is changed to
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.
In addition,
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.
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.
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".
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.
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.
Intervening finally blocks similarly affect exception analysis. An intervening finally that cannot complete normally allows us to ignore an otherwise uncaught exception.
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 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).
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.
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.
Nested Class Summary: static class Flow.PendingExit 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. Field Summary protected static final Key<Flow> flowKey Bits inits The set of definitely assigned variables. Bits uninits The set of definitely unassigned variables. HashMap<Type> preciseRethrowTypes Bits uninitsTry 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 initsWhenTrue When analyzing a condition, inits and uninits are null.
Instead we have: Bits initsWhenFalse Bits uninitsWhenTrue Bits uninitsWhenFalse VarSymbol[] vars A mapping from addresses to variable symbols. JCClassDecl classDef The current class being defined. int firstadr The first variable sequence number in this class definition. int nextadr The next available variable sequence number. List<Type> thrown The list of possibly thrown declarable exceptions. List<Type> caught The list of exceptions that are either caught or declared to be
thrown. Scope unrefdResources The list of unreferenced automatic resources. boolean loopPassTwo Set when processing a loop body the second time for DU analysis. ListBuffer<PendingExit> pendingExits The currently pending exits that go from current inner blocks
to an enclosing block, in source order. Constructor: 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();
}
Method from com.sun.tools.javac.comp.Flow Summary:
analyzeTree, checkCaughtType, checkInit, errorUncaught, instance, letInit, letInit, markDead, markThrown, merge, newVar, recordExit, referenced, resolveBreaks, resolveContinues, scanCond, scanDef, scanExpr, scanExprs, scanStat, scanStats, split, trackable, visitApply, visitAssert, visitAssign, visitAssignop, visitBinary, visitBlock, visitBreak, visitClassDef, visitConditional, visitContinue, visitDoLoop, visitForLoop, visitForeachLoop, visitIdent, visitIf, visitLabelled, visitMethodDef, visitNewArray, visitNewClass, visitReturn, visitSwitch, visitThrow, visitTopLevel, visitTry, visitTypeCast, visitUnary, visitVarDef, visitWhileLoop Methods from java.lang.Object:
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait Method from com.sun.tools.javac.comp.Flow Detail: 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;
}
}
Perform definite assignment/unassignment analysis on a tree.
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);
}
}
}
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);
}
}
Check that trackable variable is initialized.
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);
}
}
}
Complain that pending exceptions are not caught.
public static Flow instance(Context context)
{
Flow instance = context.get(flowKey);
if (instance == null)
instance = new Flow(context);
return instance;
}
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);
}
}
}
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(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);
}
}
Record an initialization of a trackable variable.
void markDead()
{
inits.inclRange(firstadr, nextadr);
uninits.inclRange(firstadr, nextadr);
alive = false;
}
Record that statement is unreachable.
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);
}
}
Record that exception is potentially thrown and check that it
is caught.
void merge()
{
inits = initsWhenFalse.andSet(initsWhenTrue);
uninits = uninitsWhenFalse.andSet(uninitsWhenTrue);
}
Merge (intersect) inits/uninits from WhenTrue/WhenFalse sets.
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++;
}
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 recordExit(JCTree tree)
{
pendingExits.append(new PendingExit(tree, inits, uninits));
markDead();
}
Record an outward transfer of control.
void referenced(Symbol sym)
{
unrefdResources.remove(sym);
}
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 breaks 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;
}
Resolve all continues of this statement.
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;
}
Analyze a condition. Make sure to set (un)initsWhenTrue(WhenFalse)
rather than (un)inits on exit.
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 definition.
void scanExpr(JCTree tree)
{
if (tree != null) {
scan(tree);
if (inits == null) merge();
}
}
Analyze an expression. Make sure to set (un)inits rather than
(un)initsWhenTrue(WhenFalse) on exit.
void scanExprs(List<JCExpression> trees)
{
if (trees != null)
for (List< ? extends JCExpression > l = trees; l.nonEmpty(); l = l.tail)
scanExpr(l.head);
}
Analyze a list of expressions.
void scanStat(JCTree tree)
{
if (!alive && tree != null) {
log.error(tree.pos(), "unreachable.stmt");
if (tree.getTag() != JCTree.SKIP) alive = true;
}
scan(tree);
}
Analyze a statement. Check that statement is reachable.
void scanStats(List<JCStatement> trees)
{
if (trees != null)
for (List< ? extends JCStatement > l = trees; l.nonEmpty(); l = l.tail)
scanStat(l.head);
}
Analyze list of statements.
void split(boolean setToNull)
{
initsWhenFalse = inits.dup();
uninitsWhenFalse = uninits.dup();
initsWhenTrue = inits;
uninitsWhenTrue = uninits;
if (setToNull)
inits = uninits = null;
}
Split (duplicate) inits/uninits into WhenTrue/WhenFalse sets
boolean trackable(VarSymbol sym)
{
return
(sym.owner.kind == MTH ||
((sym.flags() & (FINAL | HASINIT | PARAMETER)) == FINAL &&
classDef.sym.isEnclosedBy((ClassSymbol)sym.owner)));
}
Do we need to track init/uninit state of this symbol?
I.e. is symbol either a local or a blank final variable?
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 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 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 visitBlock(JCBlock tree)
{
int nextadrPrev = nextadr;
scanStats(tree.stats);
nextadr = nextadrPrev;
}
public void visitBreak(JCBreak tree)
{
recordExit(tree);
}
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 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 visitContinue(JCContinue tree)
{
recordExit(tree);
}
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 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 visitIdent(JCIdent tree)
{
if (tree.sym.kind == VAR) {
checkInit(tree.pos(), (VarSymbol)tree.sym);
referenced(tree.sym);
}
}
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 visitLabelled(JCLabeledStatement tree)
{
ListBuffer< PendingExit > prevPendingExits = pendingExits;
pendingExits = new ListBuffer< PendingExit >();
scanStat(tree.body);
alive |= resolveBreaks(tree, prevPendingExits);
}
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 visitNewArray(JCNewArray tree)
{
scanExprs(tree.dims);
scanExprs(tree.elems);
}
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 visitReturn(JCReturn tree)
{
scanExpr(tree.expr);
// if not initial constructor, should markDead instead of recordExit
recordExit(tree);
}
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;
}
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 visitTopLevel(JCCompilationUnit tree)
{
// Do nothing for TopLevel since each class is visited individually
}
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());
if (closeMethod.kind == MTH) {
for (Type t : ((MethodSymbol)closeMethod).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);
}
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);
}
}
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 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 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();
}