Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright (c) 1999, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
//todo: one might eliminate uninits.andSets when monotonic
package com.sun.tools.javac.comp;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import com.sun.source.tree.LambdaExpressionTree.BodyKind;
import com.sun.tools.javac.code.*;
import com.sun.tools.javac.code.Scope.WriteableScope;
import com.sun.tools.javac.code.Source.Feature;
import com.sun.tools.javac.resources.CompilerProperties.Errors;
import com.sun.tools.javac.resources.CompilerProperties.Warnings;
import com.sun.tools.javac.tree.*;
import com.sun.tools.javac.util.*;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
import com.sun.tools.javac.util.JCDiagnostic.Error;
import com.sun.tools.javac.util.JCDiagnostic.Warning;
import com.sun.tools.javac.code.Kinds.Kind;
import com.sun.tools.javac.code.Symbol.*;
import com.sun.tools.javac.tree.JCTree.*;
import static com.sun.tools.javac.code.Flags.*;
import static com.sun.tools.javac.code.Flags.BLOCK;
import static com.sun.tools.javac.code.Kinds.Kind.*;
import static com.sun.tools.javac.code.TypeTag.BOOLEAN;
import static com.sun.tools.javac.code.TypeTag.VOID;
import com.sun.tools.javac.jvm.Target;
import static com.sun.tools.javac.tree.JCTree.Tag.*;
/** This pass implements dataflow analysis for Java programs though
* different AST visitor steps. Liveness analysis (see AliveAnalyzer) checks that
* every statement is reachable. Exception analysis (see FlowAnalyzer) ensures that
* every checked exception that is thrown is declared or caught. Definite assignment analysis
* (see AssignAnalyzer) ensures that each variable is assigned when used. Definite
* unassignment analysis (see AssignAnalyzer) in ensures that no final variable
* is assigned more than once. Finally, local variable capture analysis (see CaptureAnalyzer)
* determines that local variables accessed within the scope of an inner class/lambda
* are either final or effectively-final.
*
*
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 constructors.
*
*
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 "{@code Set thrown}" that records the type of all
* exceptions that can be thrown.
*
*
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.
*/
public class Flow {
protected static final Context.Key flowKey = new Context.Key<>();
private final Names names;
private final Log log;
private final Symtab syms;
private final Types types;
private final Check chk;
private TreeMaker make;
private final Resolve rs;
private final JCDiagnostic.Factory diags;
private Env attrEnv;
private Lint lint;
private final boolean allowEffectivelyFinalInInnerClasses;
public static Flow instance(Context context) {
Flow instance = context.get(flowKey);
if (instance == null)
instance = new Flow(context);
return instance;
}
public void analyzeTree(Env env, TreeMaker make) {
new AliveAnalyzer().analyzeTree(env, make);
new AssignAnalyzer().analyzeTree(env, make);
new FlowAnalyzer().analyzeTree(env, make);
new CaptureAnalyzer().analyzeTree(env, make);
}
public void analyzeLambda(Env env, JCLambda that, TreeMaker make, boolean speculative) {
Log.DiagnosticHandler diagHandler = null;
//we need to disable diagnostics temporarily; the problem is that if
//a lambda expression contains e.g. an unreachable statement, an error
//message will be reported and will cause compilation to skip the flow analysis
//step - if we suppress diagnostics, we won't stop at Attr for flow-analysis
//related errors, which will allow for more errors to be detected
if (!speculative) {
diagHandler = new Log.DiscardDiagnosticHandler(log);
}
try {
new LambdaAliveAnalyzer().analyzeTree(env, that, make);
} finally {
if (!speculative) {
log.popDiagnosticHandler(diagHandler);
}
}
}
public List analyzeLambdaThrownTypes(final Env env,
JCLambda that, TreeMaker make) {
//we need to disable diagnostics temporarily; the problem is that if
//a lambda expression contains e.g. an unreachable statement, an error
//message will be reported and will cause compilation to skip the flow analysis
//step - if we suppress diagnostics, we won't stop at Attr for flow-analysis
//related errors, which will allow for more errors to be detected
Log.DiagnosticHandler diagHandler = new Log.DiscardDiagnosticHandler(log);
try {
new LambdaAssignAnalyzer(env).analyzeTree(env, that, make);
LambdaFlowAnalyzer flowAnalyzer = new LambdaFlowAnalyzer();
flowAnalyzer.analyzeTree(env, that, make);
return flowAnalyzer.inferredThrownTypes;
} finally {
log.popDiagnosticHandler(diagHandler);
}
}
public boolean aliveAfter(Env env, JCTree that, TreeMaker make) {
//we need to disable diagnostics temporarily; the problem is that if
//"that" contains e.g. an unreachable statement, an error
//message will be reported and will cause compilation to skip the flow analysis
//step - if we suppress diagnostics, we won't stop at Attr for flow-analysis
//related errors, which will allow for more errors to be detected
Log.DiagnosticHandler diagHandler = new Log.DiscardDiagnosticHandler(log);
try {
SnippetAliveAnalyzer analyzer = new SnippetAliveAnalyzer();
analyzer.analyzeTree(env, that, make);
return analyzer.isAlive();
} finally {
log.popDiagnosticHandler(diagHandler);
}
}
public boolean breaksOutOf(Env env, JCTree loop, JCTree body, TreeMaker make) {
//we need to disable diagnostics temporarily; the problem is that if
//"that" contains e.g. an unreachable statement, an error
//message will be reported and will cause compilation to skip the flow analysis
//step - if we suppress diagnostics, we won't stop at Attr for flow-analysis
//related errors, which will allow for more errors to be detected
Log.DiagnosticHandler diagHandler = new Log.DiscardDiagnosticHandler(log);
try {
SnippetBreakAnalyzer analyzer = new SnippetBreakAnalyzer();
analyzer.analyzeTree(env, body, make);
return analyzer.breaksOut();
} finally {
log.popDiagnosticHandler(diagHandler);
}
}
/**
* Definite assignment scan mode
*/
enum FlowKind {
/**
* This is the normal DA/DU analysis mode
*/
NORMAL("var.might.already.be.assigned", false),
/**
* This is the speculative DA/DU analysis mode used to speculatively
* derive assertions within loop bodies
*/
SPECULATIVE_LOOP("var.might.be.assigned.in.loop", true);
final String errKey;
final boolean isFinal;
FlowKind(String errKey, boolean isFinal) {
this.errKey = errKey;
this.isFinal = isFinal;
}
boolean isFinal() {
return isFinal;
}
}
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);
diags = JCDiagnostic.Factory.instance(context);
Source source = Source.instance(context);
Target target = Target.instance(context);
allowEffectivelyFinalInInnerClasses = Feature.EFFECTIVELY_FINAL_IN_INNER_CLASSES.allowedInSource(source, target);
}
/**
* Base visitor class for all visitors implementing dataflow analysis logic.
* This class define the shared logic for handling jumps (break/continue statements).
*/
static abstract class BaseAnalyzer extends TreeScanner {
enum JumpKind {
BREAK(JCTree.Tag.BREAK) {
@Override
JCTree getTarget(JCTree tree) {
return ((JCBreak)tree).target;
}
},
CONTINUE(JCTree.Tag.CONTINUE) {
@Override
JCTree getTarget(JCTree tree) {
return ((JCContinue)tree).target;
}
},
YIELD(JCTree.Tag.YIELD) {
@Override
JCTree getTarget(JCTree tree) {
return ((JCYield)tree).target;
}
};
final JCTree.Tag treeTag;
private JumpKind(Tag treeTag) {
this.treeTag = treeTag;
}
abstract JCTree getTarget(JCTree tree);
}
/** The currently pending exits that go from current inner blocks
* to an enclosing block, in source order.
*/
ListBuffer pendingExits;
/** A pending exit. These are the statements return, break, and
* continue. In addition, exception-throwing expressions or
* statements are put here when not known to be caught. This
* will typically result in an error unless it is within a
* try-finally whose finally block cannot complete normally.
*/
static class PendingExit {
JCTree tree;
PendingExit(JCTree tree) {
this.tree = tree;
}
void resolveJump() {
//do nothing
}
}
abstract void markDead();
/** Record an outward transfer of control. */
void recordExit(PendingExit pe) {
pendingExits.append(pe);
markDead();
}
/** Resolve all jumps of this statement. */
private Liveness resolveJump(JCTree tree,
ListBuffer oldPendingExits,
JumpKind jk) {
boolean resolved = false;
List exits = pendingExits.toList();
pendingExits = oldPendingExits;
for (; exits.nonEmpty(); exits = exits.tail) {
PendingExit exit = exits.head;
if (exit.tree.hasTag(jk.treeTag) &&
jk.getTarget(exit.tree) == tree) {
exit.resolveJump();
resolved = true;
} else {
pendingExits.append(exit);
}
}
return Liveness.from(resolved);
}
/** Resolve all continues of this statement. */
Liveness resolveContinues(JCTree tree) {
return resolveJump(tree, new ListBuffer(), JumpKind.CONTINUE);
}
/** Resolve all breaks of this statement. */
Liveness resolveBreaks(JCTree tree, ListBuffer oldPendingExits) {
return resolveJump(tree, oldPendingExits, JumpKind.BREAK);
}
/** Resolve all yields of this statement. */
Liveness resolveYields(JCTree tree, ListBuffer oldPendingExits) {
return resolveJump(tree, oldPendingExits, JumpKind.YIELD);
}
@Override
public void scan(JCTree tree) {
if (tree != null && (
tree.type == null ||
tree.type != Type.stuckType)) {
super.scan(tree);
}
}
public void visitPackageDef(JCPackageDecl tree) {
// Do nothing for PackageDecl
}
protected void scanSyntheticBreak(TreeMaker make, JCTree swtch) {
if (swtch.hasTag(SWITCH_EXPRESSION)) {
JCYield brk = make.at(Position.NOPOS).Yield(null);
brk.target = swtch;
scan(brk);
} else {
JCBreak brk = make.at(Position.NOPOS).Break(null);
brk.target = swtch;
scan(brk);
}
}
}
/**
* This pass implements the first step of the dataflow analysis, namely
* the liveness analysis check. This checks that every statement is reachable.
* The output of this analysis pass are used by other analyzers. This analyzer
* sets the 'finallyCanCompleteNormally' field in the JCTry class.
*/
class AliveAnalyzer extends BaseAnalyzer {
/** A flag that indicates whether the last statement could
* complete normally.
*/
private Liveness alive;
@Override
void markDead() {
alive = Liveness.DEAD;
}
/*************************************************************************
* Visitor methods for statements and definitions
*************************************************************************/
/** Analyze a definition.
*/
void scanDef(JCTree tree) {
scanStat(tree);
if (tree != null && tree.hasTag(JCTree.Tag.BLOCK) && alive == Liveness.DEAD) {
log.error(tree.pos(),
Errors.InitializerMustBeAbleToCompleteNormally);
}
}
/** Analyze a statement. Check that statement is reachable.
*/
void scanStat(JCTree tree) {
if (alive == Liveness.DEAD && tree != null) {
log.error(tree.pos(), Errors.UnreachableStmt);
if (!tree.hasTag(SKIP)) alive = Liveness.RECOVERY;
}
scan(tree);
}
/** Analyze list of statements.
*/
void scanStats(List extends JCStatement> trees) {
if (trees != null)
for (List extends JCStatement> l = trees; l.nonEmpty(); l = l.tail)
scanStat(l.head);
}
/* ------------ Visitor methods for various sorts of trees -------------*/
public void visitClassDef(JCClassDecl tree) {
if (tree.sym == null) return;
Liveness alivePrev = alive;
ListBuffer pendingExitsPrev = pendingExits;
Lint lintPrev = lint;
pendingExits = new ListBuffer<>();
lint = lint.augment(tree.sym);
try {
// process all the static initializers
for (List l = tree.defs; l.nonEmpty(); l = l.tail) {
if (!l.head.hasTag(METHODDEF) &&
(TreeInfo.flags(l.head) & STATIC) != 0) {
scanDef(l.head);
clearPendingExits(false);
}
}
// process all the instance initializers
for (List l = tree.defs; l.nonEmpty(); l = l.tail) {
if (!l.head.hasTag(METHODDEF) &&
(TreeInfo.flags(l.head) & STATIC) == 0) {
scanDef(l.head);
clearPendingExits(false);
}
}
// process all the methods
for (List l = tree.defs; l.nonEmpty(); l = l.tail) {
if (l.head.hasTag(METHODDEF)) {
scan(l.head);
}
}
} finally {
pendingExits = pendingExitsPrev;
alive = alivePrev;
lint = lintPrev;
}
}
public void visitMethodDef(JCMethodDecl tree) {
if (tree.body == null) return;
Lint lintPrev = lint;
lint = lint.augment(tree.sym);
Assert.check(pendingExits.isEmpty());
try {
alive = Liveness.ALIVE;
scanStat(tree.body);
tree.completesNormally = alive != Liveness.DEAD;
if (alive == Liveness.ALIVE && !tree.sym.type.getReturnType().hasTag(VOID))
log.error(TreeInfo.diagEndPos(tree.body), Errors.MissingRetStmt);
clearPendingExits(true);
} finally {
lint = lintPrev;
}
}
private void clearPendingExits(boolean inMethod) {
List exits = pendingExits.toList();
pendingExits = new ListBuffer<>();
while (exits.nonEmpty()) {
PendingExit exit = exits.head;
exits = exits.tail;
Assert.check((inMethod && exit.tree.hasTag(RETURN)) ||
log.hasErrorOn(exit.tree.pos()));
}
}
public void visitVarDef(JCVariableDecl tree) {
if (tree.init != null) {
Lint lintPrev = lint;
lint = lint.augment(tree.sym);
try{
scan(tree.init);
} finally {
lint = lintPrev;
}
}
}
public void visitBlock(JCBlock tree) {
scanStats(tree.stats);
}
public void visitDoLoop(JCDoWhileLoop tree) {
ListBuffer prevPendingExits = pendingExits;
pendingExits = new ListBuffer<>();
scanStat(tree.body);
alive = alive.or(resolveContinues(tree));
scan(tree.cond);
alive = alive.and(!tree.cond.type.isTrue());
alive = alive.or(resolveBreaks(tree, prevPendingExits));
}
public void visitWhileLoop(JCWhileLoop tree) {
ListBuffer prevPendingExits = pendingExits;
pendingExits = new ListBuffer<>();
scan(tree.cond);
alive = Liveness.from(!tree.cond.type.isFalse());
scanStat(tree.body);
alive = alive.or(resolveContinues(tree));
alive = resolveBreaks(tree, prevPendingExits).or(
!tree.cond.type.isTrue());
}
public void visitForLoop(JCForLoop tree) {
ListBuffer prevPendingExits = pendingExits;
scanStats(tree.init);
pendingExits = new ListBuffer<>();
if (tree.cond != null) {
scan(tree.cond);
alive = Liveness.from(!tree.cond.type.isFalse());
} else {
alive = Liveness.ALIVE;
}
scanStat(tree.body);
alive = alive.or(resolveContinues(tree));
scan(tree.step);
alive = resolveBreaks(tree, prevPendingExits).or(
tree.cond != null && !tree.cond.type.isTrue());
}
public void visitForeachLoop(JCEnhancedForLoop tree) {
visitVarDef(tree.var);
ListBuffer prevPendingExits = pendingExits;
scan(tree.expr);
pendingExits = new ListBuffer<>();
scanStat(tree.body);
alive = alive.or(resolveContinues(tree));
resolveBreaks(tree, prevPendingExits);
alive = Liveness.ALIVE;
}
public void visitLabelled(JCLabeledStatement tree) {
ListBuffer prevPendingExits = pendingExits;
pendingExits = new ListBuffer<>();
scanStat(tree.body);
alive = alive.or(resolveBreaks(tree, prevPendingExits));
}
public void visitSwitch(JCSwitch tree) {
ListBuffer prevPendingExits = pendingExits;
pendingExits = new ListBuffer<>();
scan(tree.selector);
boolean hasDefault = false;
for (List l = tree.cases; l.nonEmpty(); l = l.tail) {
alive = Liveness.ALIVE;
JCCase c = l.head;
if (c.pats.isEmpty())
hasDefault = true;
else {
for (JCExpression pat : c.pats) {
scan(pat);
}
}
scanStats(c.stats);
c.completesNormally = alive != Liveness.DEAD;
if (alive != Liveness.DEAD && c.caseKind == JCCase.RULE) {
scanSyntheticBreak(make, tree);
alive = Liveness.DEAD;
}
// Warn about fall-through if lint switch fallthrough enabled.
if (alive == Liveness.ALIVE &&
lint.isEnabled(Lint.LintCategory.FALLTHROUGH) &&
c.stats.nonEmpty() && l.tail.nonEmpty())
log.warning(Lint.LintCategory.FALLTHROUGH,
l.tail.head.pos(),
Warnings.PossibleFallThroughIntoCase);
}
if (!hasDefault) {
alive = Liveness.ALIVE;
}
alive = alive.or(resolveBreaks(tree, prevPendingExits));
}
@Override
public void visitSwitchExpression(JCSwitchExpression tree) {
ListBuffer prevPendingExits = pendingExits;
pendingExits = new ListBuffer<>();
scan(tree.selector);
Set