Thu, 02 Aug 2012 18:23:21 +0100
7175538: Integrate efectively final check with DA/DU analysis
Summary: Allow generalized effectively-final analysis for all local variables
Reviewed-by: jjg, dlsmith
1.1 --- a/src/share/classes/com/sun/tools/javac/code/Source.java Thu Aug 02 18:22:41 2012 +0100 1.2 +++ b/src/share/classes/com/sun/tools/javac/code/Source.java Thu Aug 02 18:23:21 2012 +0100 1.3 @@ -200,6 +200,9 @@ 1.4 public boolean allowMethodReferences() { 1.5 return compareTo(JDK1_8) >= 0; 1.6 } 1.7 + public boolean allowEffectivelyFinalInInnerClasses() { 1.8 + return compareTo(JDK1_8) >= 0; 1.9 + } 1.10 public static SourceVersion toSourceVersion(Source source) { 1.11 switch(source) { 1.12 case JDK1_2:
2.1 --- a/src/share/classes/com/sun/tools/javac/comp/Attr.java Thu Aug 02 18:22:41 2012 +0100 2.2 +++ b/src/share/classes/com/sun/tools/javac/comp/Attr.java Thu Aug 02 18:23:21 2012 +0100 2.3 @@ -228,7 +228,7 @@ 2.4 * @param env The current environment. 2.5 */ 2.6 boolean isAssignableAsBlankFinal(VarSymbol v, Env<AttrContext> env) { 2.7 - Symbol owner = env.info.scope.owner; 2.8 + Symbol owner = owner(env); 2.9 // owner refers to the innermost variable, method or 2.10 // initializer block declaration at this point. 2.11 return 2.12 @@ -243,6 +243,41 @@ 2.13 ((v.flags() & STATIC) != 0) == Resolve.isStatic(env)); 2.14 } 2.15 2.16 + /** 2.17 + * Return the innermost enclosing owner symbol in a given attribution context 2.18 + */ 2.19 + Symbol owner(Env<AttrContext> env) { 2.20 + while (true) { 2.21 + switch (env.tree.getTag()) { 2.22 + case VARDEF: 2.23 + //a field can be owner 2.24 + VarSymbol vsym = ((JCVariableDecl)env.tree).sym; 2.25 + if (vsym.owner.kind == TYP) { 2.26 + return vsym; 2.27 + } 2.28 + break; 2.29 + case METHODDEF: 2.30 + //method def is always an owner 2.31 + return ((JCMethodDecl)env.tree).sym; 2.32 + case CLASSDEF: 2.33 + //class def is always an owner 2.34 + return ((JCClassDecl)env.tree).sym; 2.35 + case BLOCK: 2.36 + //static/instance init blocks are owner 2.37 + Symbol blockSym = env.info.scope.owner; 2.38 + if ((blockSym.flags() & BLOCK) != 0) { 2.39 + return blockSym; 2.40 + } 2.41 + break; 2.42 + case TOPLEVEL: 2.43 + //toplevel is always an owner (for pkge decls) 2.44 + return env.info.scope.owner; 2.45 + } 2.46 + Assert.checkNonNull(env.next); 2.47 + env = env.next; 2.48 + } 2.49 + } 2.50 + 2.51 /** Check that variable can be assigned to. 2.52 * @param pos The current source code position. 2.53 * @param v The assigned varaible 2.54 @@ -883,7 +918,6 @@ 2.55 memberEnter.memberEnter(tree, env); 2.56 annotate.flush(); 2.57 } 2.58 - tree.sym.flags_field |= EFFECTIVELY_FINAL; 2.59 } 2.60 2.61 VarSymbol v = tree.sym; 2.62 @@ -2187,16 +2221,6 @@ 2.63 // illegal forward reference. 2.64 checkInit(tree, env, v, false); 2.65 2.66 - // If symbol is a local variable accessed from an embedded 2.67 - // inner class check that it is final. 2.68 - if (v.owner.kind == MTH && 2.69 - v.owner != env.info.scope.owner && 2.70 - (v.flags_field & FINAL) == 0) { 2.71 - log.error(tree.pos(), 2.72 - "local.var.accessed.from.icls.needs.final", 2.73 - v); 2.74 - } 2.75 - 2.76 // If we are expecting a variable (as opposed to a value), check 2.77 // that the variable is assignable in the current environment. 2.78 if (pkind() == VAR) 2.79 @@ -2590,7 +2614,7 @@ 2.80 // and are subject to definite assignment checking. 2.81 if ((env.info.enclVar == v || v.pos > tree.pos) && 2.82 v.owner.kind == TYP && 2.83 - canOwnInitializer(env.info.scope.owner) && 2.84 + canOwnInitializer(owner(env)) && 2.85 v.owner == env.info.scope.owner.enclClass() && 2.86 ((v.flags() & STATIC) != 0) == Resolve.isStatic(env) && 2.87 (!env.tree.hasTag(ASSIGN) ||
3.1 --- a/src/share/classes/com/sun/tools/javac/comp/Flow.java Thu Aug 02 18:22:41 2012 +0100 3.2 +++ b/src/share/classes/com/sun/tools/javac/comp/Flow.java Thu Aug 02 18:23:21 2012 +0100 3.3 @@ -43,13 +43,15 @@ 3.4 import static com.sun.tools.javac.code.TypeTags.*; 3.5 import static com.sun.tools.javac.tree.JCTree.Tag.*; 3.6 3.7 -/** This pass implements dataflow analysis for Java programs. 3.8 - * Liveness analysis checks that every statement is reachable. 3.9 - * Exception analysis ensures that every checked exception that is 3.10 - * thrown is declared or caught. Definite assignment analysis 3.11 - * ensures that each variable is assigned when used. Definite 3.12 - * unassignment analysis ensures that no final variable is assigned 3.13 - * more than once. 3.14 +/** This pass implements dataflow analysis for Java programs though 3.15 + * different AST visitor steps. Liveness analysis (see AliveAlanyzer) checks that 3.16 + * every statement is reachable. Exception analysis (see FlowAnalyzer) ensures that 3.17 + * every checked exception that is thrown is declared or caught. Definite assignment analysis 3.18 + * (see AssignAnalyzer) ensures that each variable is assigned when used. Definite 3.19 + * unassignment analysis (see AssignAnalyzer) in ensures that no final variable 3.20 + * is assigned more than once. Finally, local variable capture analysis (see CaptureAnalyzer) 3.21 + * determines that local variables accessed within the scope of an inner class are 3.22 + * either final or effectively-final. 3.23 * 3.24 * <p>The JLS has a number of problems in the 3.25 * specification of these flow analysis problems. This implementation 3.26 @@ -188,10 +190,12 @@ 3.27 private final Check chk; 3.28 private TreeMaker make; 3.29 private final Resolve rs; 3.30 + private final JCDiagnostic.Factory diags; 3.31 private Env<AttrContext> attrEnv; 3.32 private Lint lint; 3.33 private final boolean allowImprovedRethrowAnalysis; 3.34 private final boolean allowImprovedCatchAnalysis; 3.35 + private final boolean allowEffectivelyFinalInInnerClasses; 3.36 3.37 public static Flow instance(Context context) { 3.38 Flow instance = context.get(flowKey); 3.39 @@ -201,8 +205,37 @@ 3.40 } 3.41 3.42 public void analyzeTree(Env<AttrContext> env, TreeMaker make) { 3.43 + new AliveAnalyzer().analyzeTree(env, make); 3.44 + new AssignAnalyzer().analyzeTree(env, make); 3.45 new FlowAnalyzer().analyzeTree(env, make); 3.46 - new AssignAnalyzer().analyzeTree(env, make); 3.47 + new CaptureAnalyzer().analyzeTree(env, make); 3.48 + } 3.49 + 3.50 + /** 3.51 + * Definite assignment scan mode 3.52 + */ 3.53 + enum FlowKind { 3.54 + /** 3.55 + * This is the normal DA/DU analysis mode 3.56 + */ 3.57 + NORMAL("var.might.already.be.assigned", false), 3.58 + /** 3.59 + * This is the speculative DA/DU analysis mode used to speculatively 3.60 + * derive assertions within loop bodies 3.61 + */ 3.62 + SPECULATIVE_LOOP("var.might.be.assigned.in.loop", true); 3.63 + 3.64 + String errKey; 3.65 + boolean isFinal; 3.66 + 3.67 + FlowKind(String errKey, boolean isFinal) { 3.68 + this.errKey = errKey; 3.69 + this.isFinal = isFinal; 3.70 + } 3.71 + 3.72 + boolean isFinal() { 3.73 + return isFinal; 3.74 + } 3.75 } 3.76 3.77 protected Flow(Context context) { 3.78 @@ -214,9 +247,13 @@ 3.79 chk = Check.instance(context); 3.80 lint = Lint.instance(context); 3.81 rs = Resolve.instance(context); 3.82 + diags = JCDiagnostic.Factory.instance(context); 3.83 Source source = Source.instance(context); 3.84 allowImprovedRethrowAnalysis = source.allowImprovedRethrowAnalysis(); 3.85 allowImprovedCatchAnalysis = source.allowImprovedCatchAnalysis(); 3.86 + Options options = Options.instance(context); 3.87 + allowEffectivelyFinalInInnerClasses = source.allowEffectivelyFinalInInnerClasses() && 3.88 + options.isSet("allowEffectivelyFinalInInnerClasses"); //pre-lambda guard 3.89 } 3.90 3.91 /** 3.92 @@ -259,14 +296,16 @@ 3.93 * will typically result in an error unless it is within a 3.94 * try-finally whose finally block cannot complete normally. 3.95 */ 3.96 - abstract static class PendingExit { 3.97 + static class PendingExit { 3.98 JCTree tree; 3.99 3.100 PendingExit(JCTree tree) { 3.101 this.tree = tree; 3.102 } 3.103 3.104 - abstract void resolveJump(); 3.105 + void resolveJump() { 3.106 + //do nothing 3.107 + } 3.108 } 3.109 3.110 abstract void markDead(); 3.111 @@ -309,18 +348,352 @@ 3.112 } 3.113 3.114 /** 3.115 - * This pass implements the first two steps of the dataflow analysis: 3.116 - * (i) liveness analysis checks that every statement is reachable and (ii) 3.117 - * exception analysis to ensure that every checked exception that is 3.118 - * thrown is declared or caught. 3.119 + * This pass implements the first step of the dataflow analysis, namely 3.120 + * the liveness analysis check. This checks that every statement is reachable. 3.121 + * The output of this analysis pass are used by other analyzers. This analyzer 3.122 + * sets the 'finallyCanCompleteNormally' field in the JCTry class. 3.123 + */ 3.124 + class AliveAnalyzer extends BaseAnalyzer<BaseAnalyzer.PendingExit> { 3.125 + 3.126 + /** A flag that indicates whether the last statement could 3.127 + * complete normally. 3.128 + */ 3.129 + private boolean alive; 3.130 + 3.131 + @Override 3.132 + void markDead() { 3.133 + alive = false; 3.134 + } 3.135 + 3.136 + /************************************************************************* 3.137 + * Visitor methods for statements and definitions 3.138 + *************************************************************************/ 3.139 + 3.140 + /** Analyze a definition. 3.141 + */ 3.142 + void scanDef(JCTree tree) { 3.143 + scanStat(tree); 3.144 + if (tree != null && tree.hasTag(JCTree.Tag.BLOCK) && !alive) { 3.145 + log.error(tree.pos(), 3.146 + "initializer.must.be.able.to.complete.normally"); 3.147 + } 3.148 + } 3.149 + 3.150 + /** Analyze a statement. Check that statement is reachable. 3.151 + */ 3.152 + void scanStat(JCTree tree) { 3.153 + if (!alive && tree != null) { 3.154 + log.error(tree.pos(), "unreachable.stmt"); 3.155 + if (!tree.hasTag(SKIP)) alive = true; 3.156 + } 3.157 + scan(tree); 3.158 + } 3.159 + 3.160 + /** Analyze list of statements. 3.161 + */ 3.162 + void scanStats(List<? extends JCStatement> trees) { 3.163 + if (trees != null) 3.164 + for (List<? extends JCStatement> l = trees; l.nonEmpty(); l = l.tail) 3.165 + scanStat(l.head); 3.166 + } 3.167 + 3.168 + /* ------------ Visitor methods for various sorts of trees -------------*/ 3.169 + 3.170 + public void visitClassDef(JCClassDecl tree) { 3.171 + if (tree.sym == null) return; 3.172 + boolean alivePrev = alive; 3.173 + ListBuffer<PendingExit> pendingExitsPrev = pendingExits; 3.174 + Lint lintPrev = lint; 3.175 + 3.176 + pendingExits = new ListBuffer<PendingExit>(); 3.177 + lint = lint.augment(tree.sym.attributes_field); 3.178 + 3.179 + try { 3.180 + // process all the static initializers 3.181 + for (List<JCTree> l = tree.defs; l.nonEmpty(); l = l.tail) { 3.182 + if (!l.head.hasTag(METHODDEF) && 3.183 + (TreeInfo.flags(l.head) & STATIC) != 0) { 3.184 + scanDef(l.head); 3.185 + } 3.186 + } 3.187 + 3.188 + // process all the instance initializers 3.189 + for (List<JCTree> l = tree.defs; l.nonEmpty(); l = l.tail) { 3.190 + if (!l.head.hasTag(METHODDEF) && 3.191 + (TreeInfo.flags(l.head) & STATIC) == 0) { 3.192 + scanDef(l.head); 3.193 + } 3.194 + } 3.195 + 3.196 + // process all the methods 3.197 + for (List<JCTree> l = tree.defs; l.nonEmpty(); l = l.tail) { 3.198 + if (l.head.hasTag(METHODDEF)) { 3.199 + scan(l.head); 3.200 + } 3.201 + } 3.202 + } finally { 3.203 + pendingExits = pendingExitsPrev; 3.204 + alive = alivePrev; 3.205 + lint = lintPrev; 3.206 + } 3.207 + } 3.208 + 3.209 + public void visitMethodDef(JCMethodDecl tree) { 3.210 + if (tree.body == null) return; 3.211 + Lint lintPrev = lint; 3.212 + 3.213 + lint = lint.augment(tree.sym.attributes_field); 3.214 + 3.215 + Assert.check(pendingExits.isEmpty()); 3.216 + 3.217 + try { 3.218 + alive = true; 3.219 + scanStat(tree.body); 3.220 + 3.221 + if (alive && tree.sym.type.getReturnType().tag != VOID) 3.222 + log.error(TreeInfo.diagEndPos(tree.body), "missing.ret.stmt"); 3.223 + 3.224 + List<PendingExit> exits = pendingExits.toList(); 3.225 + pendingExits = new ListBuffer<PendingExit>(); 3.226 + while (exits.nonEmpty()) { 3.227 + PendingExit exit = exits.head; 3.228 + exits = exits.tail; 3.229 + Assert.check(exit.tree.hasTag(RETURN)); 3.230 + } 3.231 + } finally { 3.232 + lint = lintPrev; 3.233 + } 3.234 + } 3.235 + 3.236 + public void visitVarDef(JCVariableDecl tree) { 3.237 + if (tree.init != null) { 3.238 + Lint lintPrev = lint; 3.239 + lint = lint.augment(tree.sym.attributes_field); 3.240 + try{ 3.241 + scan(tree.init); 3.242 + } finally { 3.243 + lint = lintPrev; 3.244 + } 3.245 + } 3.246 + } 3.247 + 3.248 + public void visitBlock(JCBlock tree) { 3.249 + scanStats(tree.stats); 3.250 + } 3.251 + 3.252 + public void visitDoLoop(JCDoWhileLoop tree) { 3.253 + ListBuffer<PendingExit> prevPendingExits = pendingExits; 3.254 + pendingExits = new ListBuffer<PendingExit>(); 3.255 + scanStat(tree.body); 3.256 + alive |= resolveContinues(tree); 3.257 + scan(tree.cond); 3.258 + alive = alive && !tree.cond.type.isTrue(); 3.259 + alive |= resolveBreaks(tree, prevPendingExits); 3.260 + } 3.261 + 3.262 + public void visitWhileLoop(JCWhileLoop tree) { 3.263 + ListBuffer<PendingExit> prevPendingExits = pendingExits; 3.264 + pendingExits = new ListBuffer<PendingExit>(); 3.265 + scan(tree.cond); 3.266 + alive = !tree.cond.type.isFalse(); 3.267 + scanStat(tree.body); 3.268 + alive |= resolveContinues(tree); 3.269 + alive = resolveBreaks(tree, prevPendingExits) || 3.270 + !tree.cond.type.isTrue(); 3.271 + } 3.272 + 3.273 + public void visitForLoop(JCForLoop tree) { 3.274 + ListBuffer<PendingExit> prevPendingExits = pendingExits; 3.275 + scanStats(tree.init); 3.276 + pendingExits = new ListBuffer<PendingExit>(); 3.277 + if (tree.cond != null) { 3.278 + scan(tree.cond); 3.279 + alive = !tree.cond.type.isFalse(); 3.280 + } else { 3.281 + alive = true; 3.282 + } 3.283 + scanStat(tree.body); 3.284 + alive |= resolveContinues(tree); 3.285 + scan(tree.step); 3.286 + alive = resolveBreaks(tree, prevPendingExits) || 3.287 + tree.cond != null && !tree.cond.type.isTrue(); 3.288 + } 3.289 + 3.290 + public void visitForeachLoop(JCEnhancedForLoop tree) { 3.291 + visitVarDef(tree.var); 3.292 + ListBuffer<PendingExit> prevPendingExits = pendingExits; 3.293 + scan(tree.expr); 3.294 + pendingExits = new ListBuffer<PendingExit>(); 3.295 + scanStat(tree.body); 3.296 + alive |= resolveContinues(tree); 3.297 + resolveBreaks(tree, prevPendingExits); 3.298 + alive = true; 3.299 + } 3.300 + 3.301 + public void visitLabelled(JCLabeledStatement tree) { 3.302 + ListBuffer<PendingExit> prevPendingExits = pendingExits; 3.303 + pendingExits = new ListBuffer<PendingExit>(); 3.304 + scanStat(tree.body); 3.305 + alive |= resolveBreaks(tree, prevPendingExits); 3.306 + } 3.307 + 3.308 + public void visitSwitch(JCSwitch tree) { 3.309 + ListBuffer<PendingExit> prevPendingExits = pendingExits; 3.310 + pendingExits = new ListBuffer<PendingExit>(); 3.311 + scan(tree.selector); 3.312 + boolean hasDefault = false; 3.313 + for (List<JCCase> l = tree.cases; l.nonEmpty(); l = l.tail) { 3.314 + alive = true; 3.315 + JCCase c = l.head; 3.316 + if (c.pat == null) 3.317 + hasDefault = true; 3.318 + else 3.319 + scan(c.pat); 3.320 + scanStats(c.stats); 3.321 + // Warn about fall-through if lint switch fallthrough enabled. 3.322 + if (alive && 3.323 + lint.isEnabled(Lint.LintCategory.FALLTHROUGH) && 3.324 + c.stats.nonEmpty() && l.tail.nonEmpty()) 3.325 + log.warning(Lint.LintCategory.FALLTHROUGH, 3.326 + l.tail.head.pos(), 3.327 + "possible.fall-through.into.case"); 3.328 + } 3.329 + if (!hasDefault) { 3.330 + alive = true; 3.331 + } 3.332 + alive |= resolveBreaks(tree, prevPendingExits); 3.333 + } 3.334 + 3.335 + public void visitTry(JCTry tree) { 3.336 + ListBuffer<PendingExit> prevPendingExits = pendingExits; 3.337 + pendingExits = new ListBuffer<PendingExit>(); 3.338 + for (JCTree resource : tree.resources) { 3.339 + if (resource instanceof JCVariableDecl) { 3.340 + JCVariableDecl vdecl = (JCVariableDecl) resource; 3.341 + visitVarDef(vdecl); 3.342 + } else if (resource instanceof JCExpression) { 3.343 + scan((JCExpression) resource); 3.344 + } else { 3.345 + throw new AssertionError(tree); // parser error 3.346 + } 3.347 + } 3.348 + 3.349 + scanStat(tree.body); 3.350 + boolean aliveEnd = alive; 3.351 + 3.352 + for (List<JCCatch> l = tree.catchers; l.nonEmpty(); l = l.tail) { 3.353 + alive = true; 3.354 + JCVariableDecl param = l.head.param; 3.355 + scan(param); 3.356 + scanStat(l.head.body); 3.357 + aliveEnd |= alive; 3.358 + } 3.359 + if (tree.finalizer != null) { 3.360 + ListBuffer<PendingExit> exits = pendingExits; 3.361 + pendingExits = prevPendingExits; 3.362 + alive = true; 3.363 + scanStat(tree.finalizer); 3.364 + tree.finallyCanCompleteNormally = alive; 3.365 + if (!alive) { 3.366 + if (lint.isEnabled(Lint.LintCategory.FINALLY)) { 3.367 + log.warning(Lint.LintCategory.FINALLY, 3.368 + TreeInfo.diagEndPos(tree.finalizer), 3.369 + "finally.cannot.complete"); 3.370 + } 3.371 + } else { 3.372 + while (exits.nonEmpty()) { 3.373 + pendingExits.append(exits.next()); 3.374 + } 3.375 + alive = aliveEnd; 3.376 + } 3.377 + } else { 3.378 + alive = aliveEnd; 3.379 + ListBuffer<PendingExit> exits = pendingExits; 3.380 + pendingExits = prevPendingExits; 3.381 + while (exits.nonEmpty()) pendingExits.append(exits.next()); 3.382 + } 3.383 + } 3.384 + 3.385 + @Override 3.386 + public void visitIf(JCIf tree) { 3.387 + scan(tree.cond); 3.388 + scanStat(tree.thenpart); 3.389 + if (tree.elsepart != null) { 3.390 + boolean aliveAfterThen = alive; 3.391 + alive = true; 3.392 + scanStat(tree.elsepart); 3.393 + alive = alive | aliveAfterThen; 3.394 + } else { 3.395 + alive = true; 3.396 + } 3.397 + } 3.398 + 3.399 + public void visitBreak(JCBreak tree) { 3.400 + recordExit(tree, new PendingExit(tree)); 3.401 + } 3.402 + 3.403 + public void visitContinue(JCContinue tree) { 3.404 + recordExit(tree, new PendingExit(tree)); 3.405 + } 3.406 + 3.407 + public void visitReturn(JCReturn tree) { 3.408 + scan(tree.expr); 3.409 + recordExit(tree, new PendingExit(tree)); 3.410 + } 3.411 + 3.412 + public void visitThrow(JCThrow tree) { 3.413 + scan(tree.expr); 3.414 + markDead(); 3.415 + } 3.416 + 3.417 + public void visitApply(JCMethodInvocation tree) { 3.418 + scan(tree.meth); 3.419 + scan(tree.args); 3.420 + } 3.421 + 3.422 + public void visitNewClass(JCNewClass tree) { 3.423 + scan(tree.encl); 3.424 + scan(tree.args); 3.425 + if (tree.def != null) { 3.426 + scan(tree.def); 3.427 + } 3.428 + } 3.429 + 3.430 + public void visitTopLevel(JCCompilationUnit tree) { 3.431 + // Do nothing for TopLevel since each class is visited individually 3.432 + } 3.433 + 3.434 + /************************************************************************** 3.435 + * main method 3.436 + *************************************************************************/ 3.437 + 3.438 + /** Perform definite assignment/unassignment analysis on a tree. 3.439 + */ 3.440 + public void analyzeTree(Env<AttrContext> env, TreeMaker make) { 3.441 + try { 3.442 + attrEnv = env; 3.443 + Flow.this.make = make; 3.444 + pendingExits = new ListBuffer<PendingExit>(); 3.445 + alive = true; 3.446 + scan(env.tree); 3.447 + } finally { 3.448 + pendingExits = null; 3.449 + Flow.this.make = null; 3.450 + } 3.451 + } 3.452 + } 3.453 + 3.454 + /** 3.455 + * This pass implements the second step of the dataflow analysis, namely 3.456 + * the exception analysis. This is to ensure that every checked exception that is 3.457 + * thrown is declared or caught. The analyzer uses some info that has been set by 3.458 + * the liveliness analyzer. 3.459 */ 3.460 class FlowAnalyzer extends BaseAnalyzer<FlowAnalyzer.FlowPendingExit> { 3.461 3.462 /** A flag that indicates whether the last statement could 3.463 * complete normally. 3.464 */ 3.465 - private boolean alive; 3.466 - 3.467 HashMap<Symbol, List<Type>> preciseRethrowTypes; 3.468 3.469 /** The current class being defined. 3.470 @@ -344,13 +717,11 @@ 3.471 super(tree); 3.472 this.thrown = thrown; 3.473 } 3.474 - 3.475 - void resolveJump() { /*do nothing*/ } 3.476 } 3.477 3.478 @Override 3.479 void markDead() { 3.480 - alive = false; 3.481 + //do nothing 3.482 } 3.483 3.484 /*-------------------- Exceptions ----------------------*/ 3.485 @@ -395,34 +766,6 @@ 3.486 * Visitor methods for statements and definitions 3.487 *************************************************************************/ 3.488 3.489 - /** Analyze a definition. 3.490 - */ 3.491 - void scanDef(JCTree tree) { 3.492 - scanStat(tree); 3.493 - if (tree != null && tree.hasTag(JCTree.Tag.BLOCK) && !alive) { 3.494 - log.error(tree.pos(), 3.495 - "initializer.must.be.able.to.complete.normally"); 3.496 - } 3.497 - } 3.498 - 3.499 - /** Analyze a statement. Check that statement is reachable. 3.500 - */ 3.501 - void scanStat(JCTree tree) { 3.502 - if (!alive && tree != null) { 3.503 - log.error(tree.pos(), "unreachable.stmt"); 3.504 - if (!tree.hasTag(SKIP)) alive = true; 3.505 - } 3.506 - scan(tree); 3.507 - } 3.508 - 3.509 - /** Analyze list of statements. 3.510 - */ 3.511 - void scanStats(List<? extends JCStatement> trees) { 3.512 - if (trees != null) 3.513 - for (List<? extends JCStatement> l = trees; l.nonEmpty(); l = l.tail) 3.514 - scanStat(l.head); 3.515 - } 3.516 - 3.517 /* ------------ Visitor methods for various sorts of trees -------------*/ 3.518 3.519 public void visitClassDef(JCClassDecl tree) { 3.520 @@ -431,7 +774,6 @@ 3.521 JCClassDecl classDefPrev = classDef; 3.522 List<Type> thrownPrev = thrown; 3.523 List<Type> caughtPrev = caught; 3.524 - boolean alivePrev = alive; 3.525 ListBuffer<FlowPendingExit> pendingExitsPrev = pendingExits; 3.526 Lint lintPrev = lint; 3.527 3.528 @@ -448,7 +790,7 @@ 3.529 for (List<JCTree> l = tree.defs; l.nonEmpty(); l = l.tail) { 3.530 if (!l.head.hasTag(METHODDEF) && 3.531 (TreeInfo.flags(l.head) & STATIC) != 0) { 3.532 - scanDef(l.head); 3.533 + scan(l.head); 3.534 errorUncaught(); 3.535 } 3.536 } 3.537 @@ -475,7 +817,7 @@ 3.538 for (List<JCTree> l = tree.defs; l.nonEmpty(); l = l.tail) { 3.539 if (!l.head.hasTag(METHODDEF) && 3.540 (TreeInfo.flags(l.head) & STATIC) == 0) { 3.541 - scanDef(l.head); 3.542 + scan(l.head); 3.543 errorUncaught(); 3.544 } 3.545 } 3.546 @@ -508,7 +850,6 @@ 3.547 thrown = thrownPrev; 3.548 } finally { 3.549 pendingExits = pendingExitsPrev; 3.550 - alive = alivePrev; 3.551 caught = caughtPrev; 3.552 classDef = classDefPrev; 3.553 lint = lintPrev; 3.554 @@ -538,11 +879,7 @@ 3.555 // else we are in an instance initializer block; 3.556 // leave caught unchanged. 3.557 3.558 - alive = true; 3.559 - scanStat(tree.body); 3.560 - 3.561 - if (alive && tree.sym.type.getReturnType().tag != VOID) 3.562 - log.error(TreeInfo.diagEndPos(tree.body), "missing.ret.stmt"); 3.563 + scan(tree.body); 3.564 3.565 List<FlowPendingExit> exits = pendingExits.toList(); 3.566 pendingExits = new ListBuffer<FlowPendingExit>(); 3.567 @@ -575,45 +912,38 @@ 3.568 } 3.569 3.570 public void visitBlock(JCBlock tree) { 3.571 - scanStats(tree.stats); 3.572 + scan(tree.stats); 3.573 } 3.574 3.575 public void visitDoLoop(JCDoWhileLoop tree) { 3.576 ListBuffer<FlowPendingExit> prevPendingExits = pendingExits; 3.577 pendingExits = new ListBuffer<FlowPendingExit>(); 3.578 - scanStat(tree.body); 3.579 - alive |= resolveContinues(tree); 3.580 + scan(tree.body); 3.581 + resolveContinues(tree); 3.582 scan(tree.cond); 3.583 - alive = alive && !tree.cond.type.isTrue(); 3.584 - alive |= resolveBreaks(tree, prevPendingExits); 3.585 + resolveBreaks(tree, prevPendingExits); 3.586 } 3.587 3.588 public void visitWhileLoop(JCWhileLoop tree) { 3.589 ListBuffer<FlowPendingExit> prevPendingExits = pendingExits; 3.590 pendingExits = new ListBuffer<FlowPendingExit>(); 3.591 scan(tree.cond); 3.592 - alive = !tree.cond.type.isFalse(); 3.593 - scanStat(tree.body); 3.594 - alive |= resolveContinues(tree); 3.595 - alive = resolveBreaks(tree, prevPendingExits) || 3.596 - !tree.cond.type.isTrue(); 3.597 + scan(tree.body); 3.598 + resolveContinues(tree); 3.599 + resolveBreaks(tree, prevPendingExits); 3.600 } 3.601 3.602 public void visitForLoop(JCForLoop tree) { 3.603 ListBuffer<FlowPendingExit> prevPendingExits = pendingExits; 3.604 - scanStats(tree.init); 3.605 + scan(tree.init); 3.606 pendingExits = new ListBuffer<FlowPendingExit>(); 3.607 if (tree.cond != null) { 3.608 scan(tree.cond); 3.609 - alive = !tree.cond.type.isFalse(); 3.610 - } else { 3.611 - alive = true; 3.612 } 3.613 - scanStat(tree.body); 3.614 - alive |= resolveContinues(tree); 3.615 + scan(tree.body); 3.616 + resolveContinues(tree); 3.617 scan(tree.step); 3.618 - alive = resolveBreaks(tree, prevPendingExits) || 3.619 - tree.cond != null && !tree.cond.type.isTrue(); 3.620 + resolveBreaks(tree, prevPendingExits); 3.621 } 3.622 3.623 public void visitForeachLoop(JCEnhancedForLoop tree) { 3.624 @@ -621,44 +951,30 @@ 3.625 ListBuffer<FlowPendingExit> prevPendingExits = pendingExits; 3.626 scan(tree.expr); 3.627 pendingExits = new ListBuffer<FlowPendingExit>(); 3.628 - scanStat(tree.body); 3.629 - alive |= resolveContinues(tree); 3.630 + scan(tree.body); 3.631 + resolveContinues(tree); 3.632 resolveBreaks(tree, prevPendingExits); 3.633 - alive = true; 3.634 } 3.635 3.636 public void visitLabelled(JCLabeledStatement tree) { 3.637 ListBuffer<FlowPendingExit> prevPendingExits = pendingExits; 3.638 pendingExits = new ListBuffer<FlowPendingExit>(); 3.639 - scanStat(tree.body); 3.640 - alive |= resolveBreaks(tree, prevPendingExits); 3.641 + scan(tree.body); 3.642 + resolveBreaks(tree, prevPendingExits); 3.643 } 3.644 3.645 public void visitSwitch(JCSwitch tree) { 3.646 ListBuffer<FlowPendingExit> prevPendingExits = pendingExits; 3.647 pendingExits = new ListBuffer<FlowPendingExit>(); 3.648 scan(tree.selector); 3.649 - boolean hasDefault = false; 3.650 for (List<JCCase> l = tree.cases; l.nonEmpty(); l = l.tail) { 3.651 - alive = true; 3.652 JCCase c = l.head; 3.653 - if (c.pat == null) 3.654 - hasDefault = true; 3.655 - else 3.656 + if (c.pat != null) { 3.657 scan(c.pat); 3.658 - scanStats(c.stats); 3.659 - // Warn about fall-through if lint switch fallthrough enabled. 3.660 - if (alive && 3.661 - lint.isEnabled(Lint.LintCategory.FALLTHROUGH) && 3.662 - c.stats.nonEmpty() && l.tail.nonEmpty()) 3.663 - log.warning(Lint.LintCategory.FALLTHROUGH, 3.664 - l.tail.head.pos(), 3.665 - "possible.fall-through.into.case"); 3.666 + } 3.667 + scan(c.stats); 3.668 } 3.669 - if (!hasDefault) { 3.670 - alive = true; 3.671 - } 3.672 - alive |= resolveBreaks(tree, prevPendingExits); 3.673 + resolveBreaks(tree, prevPendingExits); 3.674 } 3.675 3.676 public void visitTry(JCTry tree) { 3.677 @@ -706,17 +1022,15 @@ 3.678 } 3.679 } 3.680 } 3.681 - scanStat(tree.body); 3.682 + scan(tree.body); 3.683 List<Type> thrownInTry = allowImprovedCatchAnalysis ? 3.684 chk.union(thrown, List.of(syms.runtimeExceptionType, syms.errorType)) : 3.685 thrown; 3.686 thrown = thrownPrev; 3.687 caught = caughtPrev; 3.688 - boolean aliveEnd = alive; 3.689 3.690 List<Type> caughtInTry = List.nil(); 3.691 for (List<JCCatch> l = tree.catchers; l.nonEmpty(); l = l.tail) { 3.692 - alive = true; 3.693 JCVariableDecl param = l.head.param; 3.694 List<JCExpression> subClauses = TreeInfo.isMultiCatch(l.head) ? 3.695 ((JCTypeUnion)l.head.param.vartype).alternatives : 3.696 @@ -735,26 +1049,18 @@ 3.697 } 3.698 scan(param); 3.699 preciseRethrowTypes.put(param.sym, chk.intersect(ctypes, rethrownTypes)); 3.700 - scanStat(l.head.body); 3.701 + scan(l.head.body); 3.702 preciseRethrowTypes.remove(param.sym); 3.703 - aliveEnd |= alive; 3.704 } 3.705 if (tree.finalizer != null) { 3.706 List<Type> savedThrown = thrown; 3.707 thrown = List.nil(); 3.708 ListBuffer<FlowPendingExit> exits = pendingExits; 3.709 pendingExits = prevPendingExits; 3.710 - alive = true; 3.711 - scanStat(tree.finalizer); 3.712 - tree.finallyCanCompleteNormally = alive; 3.713 - if (!alive) { 3.714 + scan(tree.finalizer); 3.715 + if (!tree.finallyCanCompleteNormally) { 3.716 // discard exits and exceptions from try and finally 3.717 thrown = chk.union(thrown, thrownPrev); 3.718 - if (lint.isEnabled(Lint.LintCategory.FINALLY)) { 3.719 - log.warning(Lint.LintCategory.FINALLY, 3.720 - TreeInfo.diagEndPos(tree.finalizer), 3.721 - "finally.cannot.complete"); 3.722 - } 3.723 } else { 3.724 thrown = chk.union(thrown, chk.diff(thrownInTry, caughtInTry)); 3.725 thrown = chk.union(thrown, savedThrown); 3.726 @@ -763,11 +1069,9 @@ 3.727 while (exits.nonEmpty()) { 3.728 pendingExits.append(exits.next()); 3.729 } 3.730 - alive = aliveEnd; 3.731 } 3.732 } else { 3.733 thrown = chk.union(thrown, chk.diff(thrownInTry, caughtInTry)); 3.734 - alive = aliveEnd; 3.735 ListBuffer<FlowPendingExit> exits = pendingExits; 3.736 pendingExits = prevPendingExits; 3.737 while (exits.nonEmpty()) pendingExits.append(exits.next()); 3.738 @@ -777,14 +1081,9 @@ 3.739 @Override 3.740 public void visitIf(JCIf tree) { 3.741 scan(tree.cond); 3.742 - scanStat(tree.thenpart); 3.743 + scan(tree.thenpart); 3.744 if (tree.elsepart != null) { 3.745 - boolean aliveAfterThen = alive; 3.746 - alive = true; 3.747 - scanStat(tree.elsepart); 3.748 - alive = alive | aliveAfterThen; 3.749 - } else { 3.750 - alive = true; 3.751 + scan(tree.elsepart); 3.752 } 3.753 } 3.754 3.755 @@ -826,7 +1125,6 @@ 3.756 3.757 public void visitReturn(JCReturn tree) { 3.758 scan(tree.expr); 3.759 - // if not initial constructor, should markDead instead of recordExit 3.760 recordExit(tree, new FlowPendingExit(tree, null)); 3.761 } 3.762 3.763 @@ -898,13 +1196,14 @@ 3.764 /** Perform definite assignment/unassignment analysis on a tree. 3.765 */ 3.766 public void analyzeTree(Env<AttrContext> env, TreeMaker make) { 3.767 + analyzeTree(env, env.tree, make); 3.768 + } 3.769 + public void analyzeTree(Env<AttrContext> env, JCTree tree, TreeMaker make) { 3.770 try { 3.771 attrEnv = env; 3.772 - JCTree tree = env.tree; 3.773 Flow.this.make = make; 3.774 pendingExits = new ListBuffer<FlowPendingExit>(); 3.775 preciseRethrowTypes = new HashMap<Symbol, List<Type>>(); 3.776 - alive = true; 3.777 this.thrown = this.caught = null; 3.778 this.classDef = null; 3.779 scan(tree); 3.780 @@ -921,7 +1220,8 @@ 3.781 * This pass implements (i) definite assignment analysis, which ensures that 3.782 * each variable is assigned when used and (ii) definite unassignment analysis, 3.783 * which ensures that no final variable is assigned more than once. This visitor 3.784 - * depends on the results of the liveliness analyzer. 3.785 + * depends on the results of the liveliness analyzer. This pass is also used to mark 3.786 + * effectively-final local variables/parameters. 3.787 */ 3.788 class AssignAnalyzer extends BaseAnalyzer<AssignAnalyzer.AssignPendingExit> { 3.789 3.790 @@ -972,7 +1272,10 @@ 3.791 Scope unrefdResources; 3.792 3.793 /** Set when processing a loop body the second time for DU analysis. */ 3.794 - boolean loopPassTwo = false; 3.795 + FlowKind flowKind = FlowKind.NORMAL; 3.796 + 3.797 + /** The starting position of the analysed tree */ 3.798 + int startPos; 3.799 3.800 class AssignPendingExit extends BaseAnalyzer.PendingExit { 3.801 3.802 @@ -1004,9 +1307,10 @@ 3.803 */ 3.804 boolean trackable(VarSymbol sym) { 3.805 return 3.806 - (sym.owner.kind == MTH || 3.807 + sym.pos >= startPos && 3.808 + ((sym.owner.kind == MTH || 3.809 ((sym.flags() & (FINAL | HASINIT | PARAMETER)) == FINAL && 3.810 - classDef.sym.isEnclosedBy((ClassSymbol)sym.owner))); 3.811 + classDef.sym.isEnclosedBy((ClassSymbol)sym.owner)))); 3.812 } 3.813 3.814 /** Initialize new trackable variable by setting its address field 3.815 @@ -1019,6 +1323,9 @@ 3.816 System.arraycopy(vars, 0, newvars, 0, nextadr); 3.817 vars = newvars; 3.818 } 3.819 + if ((sym.flags() & FINAL) == 0) { 3.820 + sym.flags_field |= EFFECTIVELY_FINAL; 3.821 + } 3.822 sym.adr = nextadr; 3.823 vars[nextadr] = sym; 3.824 inits.excl(nextadr); 3.825 @@ -1030,7 +1337,17 @@ 3.826 */ 3.827 void letInit(DiagnosticPosition pos, VarSymbol sym) { 3.828 if (sym.adr >= firstadr && trackable(sym)) { 3.829 - if ((sym.flags() & FINAL) != 0) { 3.830 + if ((sym.flags() & EFFECTIVELY_FINAL) != 0) { 3.831 + if (!uninits.isMember(sym.adr)) { 3.832 + //assignment targeting an effectively final variable 3.833 + //makes the variable lose its status of effectively final 3.834 + //if the variable is _not_ definitively unassigned 3.835 + sym.flags_field &= ~EFFECTIVELY_FINAL; 3.836 + } else { 3.837 + uninit(sym); 3.838 + } 3.839 + } 3.840 + else if ((sym.flags() & FINAL) != 0) { 3.841 if ((sym.flags() & PARAMETER) != 0) { 3.842 if ((sym.flags() & UNION) != 0) { //multi-catch parameter 3.843 log.error(pos, "multicatch.parameter.may.not.be.assigned", 3.844 @@ -1041,18 +1358,9 @@ 3.845 sym); 3.846 } 3.847 } else if (!uninits.isMember(sym.adr)) { 3.848 - log.error(pos, 3.849 - loopPassTwo 3.850 - ? "var.might.be.assigned.in.loop" 3.851 - : "var.might.already.be.assigned", 3.852 - sym); 3.853 - } else if (!inits.isMember(sym.adr)) { 3.854 - // reachable assignment 3.855 - uninits.excl(sym.adr); 3.856 - uninitsTry.excl(sym.adr); 3.857 + log.error(pos, flowKind.errKey, sym); 3.858 } else { 3.859 - //log.rawWarning(pos, "unreachable assignment");//DEBUG 3.860 - uninits.excl(sym.adr); 3.861 + uninit(sym); 3.862 } 3.863 } 3.864 inits.incl(sym.adr); 3.865 @@ -1060,6 +1368,17 @@ 3.866 log.error(pos, "var.might.already.be.assigned", sym); 3.867 } 3.868 } 3.869 + //where 3.870 + void uninit(VarSymbol sym) { 3.871 + if (!inits.isMember(sym.adr)) { 3.872 + // reachable assignment 3.873 + uninits.excl(sym.adr); 3.874 + uninitsTry.excl(sym.adr); 3.875 + } else { 3.876 + //log.rawWarning(pos, "unreachable assignment");//DEBUG 3.877 + uninits.excl(sym.adr); 3.878 + } 3.879 + } 3.880 3.881 /** If tree is either a simple name or of the form this.name or 3.882 * C.this.name, and tree represents a trackable variable, 3.883 @@ -1308,66 +1627,79 @@ 3.884 3.885 public void visitDoLoop(JCDoWhileLoop tree) { 3.886 ListBuffer<AssignPendingExit> prevPendingExits = pendingExits; 3.887 - boolean prevLoopPassTwo = loopPassTwo; 3.888 + FlowKind prevFlowKind = flowKind; 3.889 + flowKind = FlowKind.NORMAL; 3.890 + Bits initsSkip = null; 3.891 + Bits uninitsSkip = null; 3.892 pendingExits = new ListBuffer<AssignPendingExit>(); 3.893 int prevErrors = log.nerrors; 3.894 do { 3.895 Bits uninitsEntry = uninits.dup(); 3.896 uninitsEntry.excludeFrom(nextadr); 3.897 - scan(tree.body); 3.898 - resolveContinues(tree); 3.899 + scan(tree.body); 3.900 + resolveContinues(tree); 3.901 scanCond(tree.cond); 3.902 + if (!flowKind.isFinal()) { 3.903 + initsSkip = initsWhenFalse; 3.904 + uninitsSkip = uninitsWhenFalse; 3.905 + } 3.906 if (log.nerrors != prevErrors || 3.907 - loopPassTwo || 3.908 + flowKind.isFinal() || 3.909 uninitsEntry.dup().diffSet(uninitsWhenTrue).nextBit(firstadr)==-1) 3.910 break; 3.911 inits = initsWhenTrue; 3.912 uninits = uninitsEntry.andSet(uninitsWhenTrue); 3.913 - loopPassTwo = true; 3.914 + flowKind = FlowKind.SPECULATIVE_LOOP; 3.915 } while (true); 3.916 - loopPassTwo = prevLoopPassTwo; 3.917 - inits = initsWhenFalse; 3.918 - uninits = uninitsWhenFalse; 3.919 + flowKind = prevFlowKind; 3.920 + inits = initsSkip; 3.921 + uninits = uninitsSkip; 3.922 resolveBreaks(tree, prevPendingExits); 3.923 } 3.924 3.925 public void visitWhileLoop(JCWhileLoop tree) { 3.926 ListBuffer<AssignPendingExit> prevPendingExits = pendingExits; 3.927 - boolean prevLoopPassTwo = loopPassTwo; 3.928 - Bits initsCond; 3.929 - Bits uninitsCond; 3.930 + FlowKind prevFlowKind = flowKind; 3.931 + flowKind = FlowKind.NORMAL; 3.932 + Bits initsSkip = null; 3.933 + Bits uninitsSkip = null; 3.934 pendingExits = new ListBuffer<AssignPendingExit>(); 3.935 int prevErrors = log.nerrors; 3.936 + Bits uninitsEntry = uninits.dup(); 3.937 + uninitsEntry.excludeFrom(nextadr); 3.938 do { 3.939 - Bits uninitsEntry = uninits.dup(); 3.940 - uninitsEntry.excludeFrom(nextadr); 3.941 scanCond(tree.cond); 3.942 - initsCond = initsWhenFalse; 3.943 - uninitsCond = uninitsWhenFalse; 3.944 + if (!flowKind.isFinal()) { 3.945 + initsSkip = initsWhenFalse; 3.946 + uninitsSkip = uninitsWhenFalse; 3.947 + } 3.948 inits = initsWhenTrue; 3.949 uninits = uninitsWhenTrue; 3.950 scan(tree.body); 3.951 resolveContinues(tree); 3.952 if (log.nerrors != prevErrors || 3.953 - loopPassTwo || 3.954 + flowKind.isFinal() || 3.955 uninitsEntry.dup().diffSet(uninits).nextBit(firstadr) == -1) 3.956 break; 3.957 uninits = uninitsEntry.andSet(uninits); 3.958 - loopPassTwo = true; 3.959 + flowKind = FlowKind.SPECULATIVE_LOOP; 3.960 } while (true); 3.961 - loopPassTwo = prevLoopPassTwo; 3.962 - inits = initsCond; 3.963 - uninits = uninitsCond; 3.964 + flowKind = prevFlowKind; 3.965 + //a variable is DA/DU after the while statement, if it's DA/DU assuming the 3.966 + //branch is not taken AND if it's DA/DU before any break statement 3.967 + inits = initsSkip; 3.968 + uninits = uninitsSkip; 3.969 resolveBreaks(tree, prevPendingExits); 3.970 } 3.971 3.972 public void visitForLoop(JCForLoop tree) { 3.973 ListBuffer<AssignPendingExit> prevPendingExits = pendingExits; 3.974 - boolean prevLoopPassTwo = loopPassTwo; 3.975 + FlowKind prevFlowKind = flowKind; 3.976 + flowKind = FlowKind.NORMAL; 3.977 int nextadrPrev = nextadr; 3.978 scan(tree.init); 3.979 - Bits initsCond; 3.980 - Bits uninitsCond; 3.981 + Bits initsSkip = null; 3.982 + Bits uninitsSkip = null; 3.983 pendingExits = new ListBuffer<AssignPendingExit>(); 3.984 int prevErrors = log.nerrors; 3.985 do { 3.986 @@ -1375,29 +1707,33 @@ 3.987 uninitsEntry.excludeFrom(nextadr); 3.988 if (tree.cond != null) { 3.989 scanCond(tree.cond); 3.990 - initsCond = initsWhenFalse; 3.991 - uninitsCond = uninitsWhenFalse; 3.992 + if (!flowKind.isFinal()) { 3.993 + initsSkip = initsWhenFalse; 3.994 + uninitsSkip = uninitsWhenFalse; 3.995 + } 3.996 inits = initsWhenTrue; 3.997 uninits = uninitsWhenTrue; 3.998 - } else { 3.999 - initsCond = inits.dup(); 3.1000 - initsCond.inclRange(firstadr, nextadr); 3.1001 - uninitsCond = uninits.dup(); 3.1002 - uninitsCond.inclRange(firstadr, nextadr); 3.1003 + } else if (!flowKind.isFinal()) { 3.1004 + initsSkip = inits.dup(); 3.1005 + initsSkip.inclRange(firstadr, nextadr); 3.1006 + uninitsSkip = uninits.dup(); 3.1007 + uninitsSkip.inclRange(firstadr, nextadr); 3.1008 } 3.1009 scan(tree.body); 3.1010 resolveContinues(tree); 3.1011 scan(tree.step); 3.1012 if (log.nerrors != prevErrors || 3.1013 - loopPassTwo || 3.1014 + flowKind.isFinal() || 3.1015 uninitsEntry.dup().diffSet(uninits).nextBit(firstadr) == -1) 3.1016 break; 3.1017 uninits = uninitsEntry.andSet(uninits); 3.1018 - loopPassTwo = true; 3.1019 + flowKind = FlowKind.SPECULATIVE_LOOP; 3.1020 } while (true); 3.1021 - loopPassTwo = prevLoopPassTwo; 3.1022 - inits = initsCond; 3.1023 - uninits = uninitsCond; 3.1024 + flowKind = prevFlowKind; 3.1025 + //a variable is DA/DU after a for loop, if it's DA/DU assuming the 3.1026 + //branch is not taken AND if it's DA/DU before any break statement 3.1027 + inits = initsSkip; 3.1028 + uninits = uninitsSkip; 3.1029 resolveBreaks(tree, prevPendingExits); 3.1030 nextadr = nextadrPrev; 3.1031 } 3.1032 @@ -1406,7 +1742,8 @@ 3.1033 visitVarDef(tree.var); 3.1034 3.1035 ListBuffer<AssignPendingExit> prevPendingExits = pendingExits; 3.1036 - boolean prevLoopPassTwo = loopPassTwo; 3.1037 + FlowKind prevFlowKind = flowKind; 3.1038 + flowKind = FlowKind.NORMAL; 3.1039 int nextadrPrev = nextadr; 3.1040 scan(tree.expr); 3.1041 Bits initsStart = inits.dup(); 3.1042 @@ -1421,13 +1758,13 @@ 3.1043 scan(tree.body); 3.1044 resolveContinues(tree); 3.1045 if (log.nerrors != prevErrors || 3.1046 - loopPassTwo || 3.1047 + flowKind.isFinal() || 3.1048 uninitsEntry.dup().diffSet(uninits).nextBit(firstadr) == -1) 3.1049 break; 3.1050 uninits = uninitsEntry.andSet(uninits); 3.1051 - loopPassTwo = true; 3.1052 + flowKind = FlowKind.SPECULATIVE_LOOP; 3.1053 } while (true); 3.1054 - loopPassTwo = prevLoopPassTwo; 3.1055 + flowKind = prevFlowKind; 3.1056 inits = initsStart; 3.1057 uninits = uninitsStart.andSet(uninits); 3.1058 resolveBreaks(tree, prevPendingExits); 3.1059 @@ -1628,7 +1965,6 @@ 3.1060 3.1061 public void visitReturn(JCReturn tree) { 3.1062 scanExpr(tree.expr); 3.1063 - // if not initial constructor, should markDead instead of recordExit 3.1064 recordExit(tree, new AssignPendingExit(tree, inits, uninits)); 3.1065 } 3.1066 3.1067 @@ -1669,7 +2005,9 @@ 3.1068 3.1069 public void visitAssign(JCAssign tree) { 3.1070 JCTree lhs = TreeInfo.skipParens(tree.lhs); 3.1071 - if (!(lhs instanceof JCIdent)) scanExpr(lhs); 3.1072 + if (!(lhs instanceof JCIdent)) { 3.1073 + scanExpr(lhs); 3.1074 + } 3.1075 scanExpr(tree.rhs); 3.1076 letInit(lhs); 3.1077 } 3.1078 @@ -1751,10 +2089,14 @@ 3.1079 /** Perform definite assignment/unassignment analysis on a tree. 3.1080 */ 3.1081 public void analyzeTree(Env<AttrContext> env, TreeMaker make) { 3.1082 + analyzeTree(env, env.tree, make); 3.1083 + } 3.1084 + 3.1085 + public void analyzeTree(Env<AttrContext> env, JCTree tree, TreeMaker make) { 3.1086 try { 3.1087 attrEnv = env; 3.1088 - JCTree tree = env.tree; 3.1089 Flow.this.make = make; 3.1090 + startPos = tree.pos().getStartPosition(); 3.1091 inits = new Bits(); 3.1092 uninits = new Bits(); 3.1093 uninitsTry = new Bits(); 3.1094 @@ -1773,6 +2115,7 @@ 3.1095 scan(tree); 3.1096 } finally { 3.1097 // note that recursive invocations of this method fail hard 3.1098 + startPos = -1; 3.1099 inits = uninits = uninitsTry = null; 3.1100 initsWhenTrue = initsWhenFalse = 3.1101 uninitsWhenTrue = uninitsWhenFalse = null; 3.1102 @@ -1787,4 +2130,162 @@ 3.1103 } 3.1104 } 3.1105 } 3.1106 + 3.1107 + /** 3.1108 + * This pass implements the last step of the dataflow analysis, namely 3.1109 + * the effectively-final analysis check. This checks that every local variable 3.1110 + * reference from a lambda body/local inner class is either final or effectively final. 3.1111 + * As effectively final variables are marked as such during DA/DU, this pass must run after 3.1112 + * AssignAnalyzer. 3.1113 + */ 3.1114 + class CaptureAnalyzer extends BaseAnalyzer<BaseAnalyzer.PendingExit> { 3.1115 + 3.1116 + JCTree currentTree; //local class or lambda 3.1117 + 3.1118 + @Override 3.1119 + void markDead() { 3.1120 + //do nothing 3.1121 + } 3.1122 + 3.1123 + @SuppressWarnings("fallthrough") 3.1124 + void checkEffectivelyFinal(DiagnosticPosition pos, VarSymbol sym) { 3.1125 + if (currentTree != null && 3.1126 + sym.owner.kind == MTH && 3.1127 + sym.pos < currentTree.getStartPosition()) { 3.1128 + switch (currentTree.getTag()) { 3.1129 + case CLASSDEF: 3.1130 + if (!allowEffectivelyFinalInInnerClasses) { 3.1131 + if ((sym.flags() & FINAL) == 0) { 3.1132 + reportInnerClsNeedsFinalError(pos, sym); 3.1133 + } 3.1134 + break; 3.1135 + } 3.1136 + case LAMBDA: 3.1137 + if ((sym.flags() & (EFFECTIVELY_FINAL | FINAL)) == 0) { 3.1138 + reportEffectivelyFinalError(pos, sym); 3.1139 + } 3.1140 + } 3.1141 + } 3.1142 + } 3.1143 + 3.1144 + @SuppressWarnings("fallthrough") 3.1145 + void letInit(JCTree tree) { 3.1146 + tree = TreeInfo.skipParens(tree); 3.1147 + if (tree.hasTag(IDENT) || tree.hasTag(SELECT)) { 3.1148 + Symbol sym = TreeInfo.symbol(tree); 3.1149 + if (currentTree != null && 3.1150 + sym.kind == VAR && 3.1151 + sym.owner.kind == MTH && 3.1152 + ((VarSymbol)sym).pos < currentTree.getStartPosition()) { 3.1153 + switch (currentTree.getTag()) { 3.1154 + case CLASSDEF: 3.1155 + if (!allowEffectivelyFinalInInnerClasses) { 3.1156 + reportInnerClsNeedsFinalError(tree, sym); 3.1157 + break; 3.1158 + } 3.1159 + case LAMBDA: 3.1160 + reportEffectivelyFinalError(tree, sym); 3.1161 + } 3.1162 + } 3.1163 + } 3.1164 + } 3.1165 + 3.1166 + void reportEffectivelyFinalError(DiagnosticPosition pos, Symbol sym) { 3.1167 + String subKey = currentTree.hasTag(LAMBDA) ? 3.1168 + "lambda" : "inner.cls"; 3.1169 + log.error(pos, "cant.ref.non.effectively.final.var", sym, diags.fragment(subKey)); 3.1170 + } 3.1171 + 3.1172 + void reportInnerClsNeedsFinalError(DiagnosticPosition pos, Symbol sym) { 3.1173 + log.error(pos, 3.1174 + "local.var.accessed.from.icls.needs.final", 3.1175 + sym); 3.1176 + } 3.1177 + 3.1178 + /************************************************************************* 3.1179 + * Visitor methods for statements and definitions 3.1180 + *************************************************************************/ 3.1181 + 3.1182 + /* ------------ Visitor methods for various sorts of trees -------------*/ 3.1183 + 3.1184 + public void visitClassDef(JCClassDecl tree) { 3.1185 + JCTree prevTree = currentTree; 3.1186 + try { 3.1187 + currentTree = tree.sym.isLocal() ? tree : null; 3.1188 + super.visitClassDef(tree); 3.1189 + } finally { 3.1190 + currentTree = prevTree; 3.1191 + } 3.1192 + } 3.1193 + 3.1194 + @Override 3.1195 + public void visitLambda(JCLambda tree) { 3.1196 + JCTree prevTree = currentTree; 3.1197 + try { 3.1198 + currentTree = tree; 3.1199 + super.visitLambda(tree); 3.1200 + } finally { 3.1201 + currentTree = prevTree; 3.1202 + } 3.1203 + } 3.1204 + 3.1205 + @Override 3.1206 + public void visitIdent(JCIdent tree) { 3.1207 + if (tree.sym.kind == VAR) { 3.1208 + checkEffectivelyFinal(tree, (VarSymbol)tree.sym); 3.1209 + } 3.1210 + } 3.1211 + 3.1212 + public void visitAssign(JCAssign tree) { 3.1213 + JCTree lhs = TreeInfo.skipParens(tree.lhs); 3.1214 + if (!(lhs instanceof JCIdent)) { 3.1215 + scan(lhs); 3.1216 + } 3.1217 + scan(tree.rhs); 3.1218 + letInit(lhs); 3.1219 + } 3.1220 + 3.1221 + public void visitAssignop(JCAssignOp tree) { 3.1222 + scan(tree.lhs); 3.1223 + scan(tree.rhs); 3.1224 + letInit(tree.lhs); 3.1225 + } 3.1226 + 3.1227 + public void visitUnary(JCUnary tree) { 3.1228 + switch (tree.getTag()) { 3.1229 + case PREINC: case POSTINC: 3.1230 + case PREDEC: case POSTDEC: 3.1231 + scan(tree.arg); 3.1232 + letInit(tree.arg); 3.1233 + break; 3.1234 + default: 3.1235 + scan(tree.arg); 3.1236 + } 3.1237 + } 3.1238 + 3.1239 + public void visitTopLevel(JCCompilationUnit tree) { 3.1240 + // Do nothing for TopLevel since each class is visited individually 3.1241 + } 3.1242 + 3.1243 + /************************************************************************** 3.1244 + * main method 3.1245 + *************************************************************************/ 3.1246 + 3.1247 + /** Perform definite assignment/unassignment analysis on a tree. 3.1248 + */ 3.1249 + public void analyzeTree(Env<AttrContext> env, TreeMaker make) { 3.1250 + analyzeTree(env, env.tree, make); 3.1251 + } 3.1252 + public void analyzeTree(Env<AttrContext> env, JCTree tree, TreeMaker make) { 3.1253 + try { 3.1254 + attrEnv = env; 3.1255 + Flow.this.make = make; 3.1256 + pendingExits = new ListBuffer<PendingExit>(); 3.1257 + scan(tree); 3.1258 + } finally { 3.1259 + pendingExits = null; 3.1260 + Flow.this.make = null; 3.1261 + } 3.1262 + } 3.1263 + } 3.1264 }
4.1 --- a/src/share/classes/com/sun/tools/javac/parser/JavacParser.java Thu Aug 02 18:22:41 2012 +0100 4.2 +++ b/src/share/classes/com/sun/tools/javac/parser/JavacParser.java Thu Aug 02 18:23:21 2012 +0100 4.3 @@ -113,9 +113,9 @@ 4.4 this.allowMulticatch = source.allowMulticatch(); 4.5 this.allowStringFolding = fac.options.getBoolean("allowStringFolding", true); 4.6 this.allowLambda = source.allowLambda() && 4.7 - fac.options.isSet("allowLambda"); 4.8 + fac.options.isSet("allowLambda"); //pre-lambda guard 4.9 this.allowMethodReferences = source.allowMethodReferences() && 4.10 - fac.options.isSet("allowMethodReferences"); 4.11 + fac.options.isSet("allowMethodReferences"); //pre-lambda guard 4.12 this.keepDocComments = keepDocComments; 4.13 docComments = newDocCommentTable(keepDocComments); 4.14 this.keepLineMap = keepLineMap;
5.1 --- a/src/share/classes/com/sun/tools/javac/resources/compiler.properties Thu Aug 02 18:22:41 2012 +0100 5.2 +++ b/src/share/classes/com/sun/tools/javac/resources/compiler.properties Thu Aug 02 18:23:21 2012 +0100 5.3 @@ -168,6 +168,14 @@ 5.4 compiler.err.cant.assign.val.to.final.var=\ 5.5 cannot assign a value to final variable {0} 5.6 5.7 +# 0: symbol, 1: message segment 5.8 +compiler.err.cant.ref.non.effectively.final.var=\ 5.9 + local variables referenced from {1} must be final or effectively final 5.10 + 5.11 + 5.12 +compiler.misc.inner.cls=\ 5.13 + an inner class 5.14 + 5.15 # 0: type 5.16 compiler.err.cant.deref=\ 5.17 {0} cannot be dereferenced
6.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 6.2 +++ b/test/tools/javac/diags/examples/CantRefNonEffectivelyFinalVar.java Thu Aug 02 18:23:21 2012 +0100 6.3 @@ -0,0 +1,34 @@ 6.4 +/* 6.5 + * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. 6.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 6.7 + * 6.8 + * This code is free software; you can redistribute it and/or modify it 6.9 + * under the terms of the GNU General Public License version 2 only, as 6.10 + * published by the Free Software Foundation. 6.11 + * 6.12 + * This code is distributed in the hope that it will be useful, but WITHOUT 6.13 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 6.14 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 6.15 + * version 2 for more details (a copy is included in the LICENSE file that 6.16 + * accompanied this code). 6.17 + * 6.18 + * You should have received a copy of the GNU General Public License version 6.19 + * 2 along with this work; if not, write to the Free Software Foundation, 6.20 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 6.21 + * 6.22 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 6.23 + * or visit www.oracle.com if you need additional information or have any 6.24 + * questions. 6.25 + */ 6.26 + 6.27 +// key: compiler.err.cant.ref.non.effectively.final.var 6.28 +// key: compiler.misc.inner.cls 6.29 +// options: -XDallowEffectivelyFinalInInnerClasses 6.30 + 6.31 +class CantRefNonEffectivelyFinalVar { 6.32 + void test() { 6.33 + int i = 0; 6.34 + new Object() { int j = i; }; 6.35 + i = 2; 6.36 + } 6.37 +}
7.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 7.2 +++ b/test/tools/javac/lambda/EffectivelyFinalTest.java Thu Aug 02 18:23:21 2012 +0100 7.3 @@ -0,0 +1,76 @@ 7.4 +/* 7.5 + * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. 7.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 7.7 + * 7.8 + * This code is free software; you can redistribute it and/or modify it 7.9 + * under the terms of the GNU General Public License version 2 only, as 7.10 + * published by the Free Software Foundation. 7.11 + * 7.12 + * This code is distributed in the hope that it will be useful, but WITHOUT 7.13 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 7.14 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 7.15 + * version 2 for more details (a copy is included in the LICENSE file that 7.16 + * accompanied this code). 7.17 + * 7.18 + * You should have received a copy of the GNU General Public License version 7.19 + * 2 along with this work; if not, write to the Free Software Foundation, 7.20 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 7.21 + * 7.22 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 7.23 + * or visit www.oracle.com if you need additional information or have any 7.24 + * questions. 7.25 + */ 7.26 + 7.27 +/* 7.28 + * @test 7.29 + * @summary Integrate efectively final check with DA/DU analysis 7.30 + * @compile/fail/ref=EffectivelyFinalTest01.out -XDallowEffectivelyFinalInInnerClasses -XDrawDiagnostics EffectivelyFinalTest.java 7.31 + * @compile/fail/ref=EffectivelyFinalTest02.out -source 7 -Xlint:-options -XDrawDiagnostics EffectivelyFinalTest.java 7.32 + */ 7.33 +class EffectivelyFinalTest { 7.34 + 7.35 + void m1(int x) { 7.36 + int y = 1; 7.37 + new Object() { { System.out.println(x+y); } }; //ok - both x and y are EF 7.38 + } 7.39 + 7.40 + void m2(int x) { 7.41 + int y; 7.42 + y = 1; 7.43 + new Object() { { System.out.println(x+y); } }; //ok - both x and y are EF 7.44 + } 7.45 + 7.46 + void m3(int x, boolean cond) { 7.47 + int y; 7.48 + if (cond) y = 1; 7.49 + new Object() { { System.out.println(x+y); } }; //error - y not DA 7.50 + } 7.51 + 7.52 + void m4(int x, boolean cond) { 7.53 + int y; 7.54 + if (cond) y = 1; 7.55 + else y = 2; 7.56 + new Object() { { System.out.println(x+y); } }; //ok - both x and y are EF 7.57 + } 7.58 + 7.59 + void m5(int x, boolean cond) { 7.60 + int y; 7.61 + if (cond) y = 1; 7.62 + y = 2; 7.63 + new Object() { { System.out.println(x+y); } }; //error - y not EF 7.64 + } 7.65 + 7.66 + void m6(int x) { 7.67 + new Object() { { System.out.println(x+1); } }; //error - x not EF 7.68 + x++; 7.69 + } 7.70 + 7.71 + void m7(int x) { 7.72 + new Object() { { System.out.println(x=1); } }; //error - x not EF 7.73 + } 7.74 + 7.75 + void m8() { 7.76 + int y; 7.77 + new Object() { { System.out.println(y=1); } }; //error - y not EF 7.78 + } 7.79 +}
8.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 8.2 +++ b/test/tools/javac/lambda/EffectivelyFinalTest01.out Thu Aug 02 18:23:21 2012 +0100 8.3 @@ -0,0 +1,6 @@ 8.4 +EffectivelyFinalTest.java:46:47: compiler.err.var.might.not.have.been.initialized: y 8.5 +EffectivelyFinalTest.java:60:47: compiler.err.cant.ref.non.effectively.final.var: y, (compiler.misc.inner.cls) 8.6 +EffectivelyFinalTest.java:64:45: compiler.err.cant.ref.non.effectively.final.var: x, (compiler.misc.inner.cls) 8.7 +EffectivelyFinalTest.java:69:45: compiler.err.cant.ref.non.effectively.final.var: x, (compiler.misc.inner.cls) 8.8 +EffectivelyFinalTest.java:74:45: compiler.err.cant.ref.non.effectively.final.var: y, (compiler.misc.inner.cls) 8.9 +5 errors
9.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 9.2 +++ b/test/tools/javac/lambda/EffectivelyFinalTest02.out Thu Aug 02 18:23:21 2012 +0100 9.3 @@ -0,0 +1,14 @@ 9.4 +EffectivelyFinalTest.java:46:47: compiler.err.var.might.not.have.been.initialized: y 9.5 +EffectivelyFinalTest.java:34:45: compiler.err.local.var.accessed.from.icls.needs.final: x 9.6 +EffectivelyFinalTest.java:34:47: compiler.err.local.var.accessed.from.icls.needs.final: y 9.7 +EffectivelyFinalTest.java:40:45: compiler.err.local.var.accessed.from.icls.needs.final: x 9.8 +EffectivelyFinalTest.java:40:47: compiler.err.local.var.accessed.from.icls.needs.final: y 9.9 +EffectivelyFinalTest.java:46:45: compiler.err.local.var.accessed.from.icls.needs.final: x 9.10 +EffectivelyFinalTest.java:53:45: compiler.err.local.var.accessed.from.icls.needs.final: x 9.11 +EffectivelyFinalTest.java:53:47: compiler.err.local.var.accessed.from.icls.needs.final: y 9.12 +EffectivelyFinalTest.java:60:45: compiler.err.local.var.accessed.from.icls.needs.final: x 9.13 +EffectivelyFinalTest.java:60:47: compiler.err.local.var.accessed.from.icls.needs.final: y 9.14 +EffectivelyFinalTest.java:64:45: compiler.err.local.var.accessed.from.icls.needs.final: x 9.15 +EffectivelyFinalTest.java:69:45: compiler.err.local.var.accessed.from.icls.needs.final: x 9.16 +EffectivelyFinalTest.java:74:45: compiler.err.local.var.accessed.from.icls.needs.final: y 9.17 +13 errors