Top Description Inners Fields Constructors Methods
com.sun.tools.javac.comp

public Class Flow

extends Object
Class Inheritance
Imports
java.util.Map, .Map.Entry, .HashMap, .HashSet, .Set, .Arrays, .Collections, .IdentityHashMap, .Iterator, java.util.function.Consumer, .Predicate, com.sun.source.tree.CaseTree, .LambdaExpressionTree.BodyKind, com.sun.tools.javac.code.*, .Scope.WriteableScope, com.sun.tools.javac.code.Symbol.*, .Kinds.Kind, .Type.TypeVar, com.sun.tools.javac.resources.CompilerProperties.Errors, .CompilerProperties.Warnings, .CompilerProperties.Fragments, com.sun.tools.javac.tree.*, com.sun.tools.javac.tree.JCTree.*, com.sun.tools.javac.util.*, .JCDiagnostic.DiagnosticPosition, .JCDiagnostic.Error, .JCDiagnostic.Warning, .JCDiagnostic.Fragment, java.util.stream.Collectors

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 "Set<Type> 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.

Nested and Inner Type Summary

Modifier and TypeClass and Description
pack-priv class
Flow.AliveAnalyzer

This pass implements the first step of the dataflow analysis, namely the liveness analysis check.

public class
Flow.AssignAnalyzer

This pass implements (i) definite assignment analysis, which ensures that each variable is assigned when used and (ii) definite unassignment analysis, which ensures that no final variable is assigned more than once.

pack-priv abstract static class
Flow.BaseAnalyzer

Base visitor class for all visitors implementing dataflow analysis logic.

pack-priv static record
pack-priv class
Flow.CaptureAnalyzer

This pass implements the last step of the dataflow analysis, namely the effectively-final analysis check.

pack-priv class
Flow.FlowAnalyzer

This pass implements the second step of the dataflow analysis, namely the exception analysis.

pack-priv static enum
Flow.FlowKind

Definite assignment scan mode

pack-priv class
Flow.LambdaAliveAnalyzer

Specialized pass that performs reachability analysis on a lambda

pack-priv class
Flow.LambdaAssignAnalyzer

Specialized pass that performs DA/DU on a lambda

pack-priv class
Flow.LambdaFlowAnalyzer

Specialized pass that performs inference of thrown types for lambdas.

pack-priv static enum
pack-priv static interface
pack-priv static record
pack-priv class
Flow.SnippetAliveAnalyzer

Determine if alive after the given tree.

pack-priv class

Field Summary

Modifier and TypeField and Description
private Env<AttrContext>
private final Check
private final JCDiagnostic.Factory
protected static final Context.Key<Flow>
private final Infer
private Lint
private final Log
private TreeMaker
private final Names
private final Resolve
private final Symtab
private final Types

Constructor Summary

AccessConstructor and Description
protected
Flow(Context context)

Method Summary

Modifier and TypeMethod and Description
public boolean
public void
analyzeLambda(Env<AttrContext> env, JCTree.JCLambda that, TreeMaker make, boolean speculative)

public List<Type>
public void
public boolean
breaksToTree(Env<AttrContext> env, JCTree breakTo, JCTree body, TreeMaker make)

public static Flow
instance(Context context)

public Flow.PatternDescription
Inherited from java.lang.Object:
cloneequalsfinalizegetClasshashCodenotifynotifyAlltoStringwaitwaitwait