package polyglot.visit;
import java.util.*;
import java.util.Map.Entry;
import polyglot.ast.*;
import polyglot.frontend.Job;
import polyglot.types.*;
import polyglot.util.Position;
/**
* Visitor which checks that all local variables must be defined before use,
* and that final variables and fields are initialized correctly.
*
* The checking of the rules is implemented in the methods leaveCall(Node)
* and check(FlowGraph, Term, Item, Item).
*
* If language extensions have new constructs that use local variables, they can
* override the method <code>checkOther</code> to check that the uses of these
* local variables are correctly initialized. (The implementation of the method will
* probably call checkLocalInstanceInit to see if the local used is initialized).
*
* If language extensions have new constructs that assign to local variables,
* they can override the method <code>flowOther</code> to capture the way
* the new construct's initialization behavior.
*
*/
public class InitChecker extends DataFlow
{
public InitChecker(Job job, TypeSystem ts, NodeFactory nf) {
super(job, ts, nf,
true /* forward analysis */,
false /* perform dataflow when leaving CodeDecls, not when entering */);
}
protected ClassBodyInfo currCBI = null;
/**
* This class is just a data structure containing relevant information
* needed for performing initialization checking of a class declaration.
*
* These objects form a stack, since class declarations can be nested.
*/
protected static class ClassBodyInfo {
/**
* The info for the outer ClassBody. The <code>ClassBodyInfo</code>s
* form a stack.
*/
ClassBodyInfo outer = null;
/** The current CodeDecl being processed by the dataflow equations */
CodeDecl currCodeDecl = null;
/**
* A Map of all the final fields in the class currently being processed
* to MinMaxInitCounts. This Map is used as the basis for the Maps returned
* in createInitialItem().
* */
Map currClassFinalFieldInitCounts = new HashMap();
/**
* List of all the constructors. These will be checked once all the
* initializer blocks have been processed.
*/
List allConstructors = new ArrayList();
/**
* Map from ConstructorInstances to ConstructorInstances detailing
* which constructors call which constructors.
* This is used in checking the initialization of final fields.
*/
Map constructorCalls = new HashMap();
/**
* Map from ConstructorInstances to Sets of FieldInstances, detailing
* which final non-static fields each constructor initializes.
* This is used in checking the initialization of final fields.
*/
Map fieldsConstructorInitializes = new HashMap();
/**
* Set of LocalInstances from the outer class body that were used
* during the declaration of this class. We need to track this
* in order to correctly populate <code>localsUsedInClassBodies</code>
*/
Set outerLocalsUsed = new HashSet();
/**
* Map from <code>ClassBody</code>s to <code>Set</code>s of
* <code>LocalInstance</code>s. If localsUsedInClassBodies(C) = S, then
* the class body C is an inner class declared in the current code
* declaration, and S is the set of LocalInstances that are defined
* in the current code declaration, but are used in the declaration
* of the class C. We need this information in order to ensure that
* these local variables are definitely assigned before the class
* declaration of C.
*/
Map localsUsedInClassBodies = new HashMap();
/**
* Set of LocalInstances that we have seen declarations for in this
* class. This set allows us to determine which local instances
* are simply being used before they are declared (if they are used in
* their own initialization) or are locals declared in an enclosing
* class.
*/
Set localDeclarations = new HashSet();
}
/**
* Class representing the initialization counts of variables. The
* different values of the counts that we are interested in are ZERO,
* ONE and MANY.
*/
protected static class InitCount {
static InitCount ZERO = new InitCount(0);
static InitCount ONE = new InitCount(1);
static InitCount MANY = new InitCount(2);
protected int count;
protected InitCount(int i) {
count = i;
}
public int hashCode() {
return count;
}
public boolean equals(Object o) {
if (o instanceof InitCount) {
return this.count == ((InitCount)o).count;
}
return false;
}
public String toString() {
if (count == 0) {
return "0";
}
else if (count == 1) {
return "1";
}
else if (count == 2) {
return "many";
}
throw new RuntimeException("Unexpected value for count");
}
public InitCount increment() {
if (count == 0) {
return ONE;
}
return MANY;
}
public static InitCount min(InitCount a, InitCount b) {
if (ZERO.equals(a) || ZERO.equals(b)) {
return ZERO;
}
if (ONE.equals(a) || ONE.equals(b)) {
return ONE;
}
return MANY;
}
public static InitCount max(InitCount a, InitCount b) {
if (MANY.equals(a) || MANY.equals(b)) {
return MANY;
}
if (ONE.equals(a) || ONE.equals(b)) {
return ONE;
}
return ZERO;
}
}
/**
* Class to record counts of the minimum and maximum number of times
* a variable or field has been initialized or assigned to.
*/
protected static class MinMaxInitCount {
protected InitCount min, max;
MinMaxInitCount(InitCount min, InitCount max) {
MinMaxInitCount.this.min = min;
MinMaxInitCount.this.max = max;
}
InitCount getMin() { return min; }
InitCount getMax() { return max; }
public int hashCode() {
return min.hashCode() * 4 + max.hashCode();
}
public String toString() {
return "[ min: " + min + "; max: " + max + " ]";
}
public boolean equals(Object o) {
if (o instanceof MinMaxInitCount) {
return this.min.equals(((MinMaxInitCount)o).min) &&
this.max.equals(((MinMaxInitCount)o).max);
}
return false;
}
static MinMaxInitCount join(MinMaxInitCount initCount1, MinMaxInitCount initCount2) {
if (initCount1 == null) {
return initCount2;
}
if (initCount2 == null) {
return initCount1;
}
MinMaxInitCount t = new MinMaxInitCount(
InitCount.min(initCount1.getMin(), initCount2.getMin()),
InitCount.max(initCount1.getMax(), initCount2.getMax()));
return t;
}
}
/**
* Dataflow items for this dataflow are maps of VarInstances to counts
* of the min and max number of times those variables/fields have
* been initialized. These min and max counts are then used to determine
* if variables have been initialized before use, and that final variables
* are not initialized too many times.
*
* This class is immutable.
*/
protected static class DataFlowItem extends Item {
protected Map initStatus; // map of VarInstances to MinMaxInitCount
protected DataFlowItem(Map m) {
this.initStatus = Collections.unmodifiableMap(m);
}
public String toString() {
return initStatus.toString();
}
public boolean equals(Object o) {
if (o instanceof DataFlowItem) {
return this.initStatus.equals(((DataFlowItem)o).initStatus);
}
return false;
}
public int hashCode() {
return (initStatus.hashCode());
}
}
protected static class BottomItem extends Item {
public boolean equals(Object i) {
return i == this;
}
public int hashCode() {
return -5826349;
}
}
protected static final Item BOTTOM = new BottomItem();
/**
* Initialise the FlowGraph to be used in the dataflow analysis.
* @return null if no dataflow analysis should be performed for this
* code declaration; otherwise, an apropriately initialized
* FlowGraph.
*/
protected FlowGraph initGraph(CodeDecl code, Term root) {
currCBI.currCodeDecl = code;
return new FlowGraph(root, forward);
}
/**
* Overridden superclass method.
*
* Set up the state that must be tracked during a Class Declaration.
*/
protected NodeVisitor enterCall(Node n) throws SemanticException {
if (n instanceof ClassBody) {
// we are starting to process a class declaration, but have yet
// to do any of the dataflow analysis.
// set up the new ClassBodyInfo, and make sure that it forms
// a stack.
setupClassBody((ClassBody)n);
}
return super.enterCall(n);
}
/**
* Postpone the checking of constructors until the end of the class
* declaration is encountered, to ensure that all initializers are
* processed first.
*
* Also, at the end of the class declaration, check that all static final
* fields have been initialized at least once, and that for each constructor
* all non-static final fields must have been initialized at least once,
* taking into account the constructor calls.
*
*/
protected Node leaveCall(Node old, Node n, NodeVisitor v) throws SemanticException {
if (n instanceof ConstructorDecl) {
// postpone the checking of the constructors until all the
// initializer blocks have been processed.
currCBI.allConstructors.add(n);
return n;
}
if (n instanceof ClassBody) {
// Now that we are at the end of the class declaration, and can
// be sure that all of the initializer blocks have been processed,
// we can now process the constructors.
for (Iterator iter = currCBI.allConstructors.iterator();
iter.hasNext(); ) {
ConstructorDecl cd = (ConstructorDecl)iter.next();
// rely on the fact that our dataflow does not change the AST,
// so we can discard the result of this call.
dataflow(cd);
}
// check that all static fields have been initialized exactly once
checkStaticFinalFieldsInit((ClassBody)n);
// check that at the end of each constructor all non-static final
// fields are initialzed.
checkNonStaticFinalFieldsInit((ClassBody)n);
// copy the locals used to the outer scope
if (currCBI.outer != null) {
currCBI.outer.localsUsedInClassBodies.put(n,
currCBI.outerLocalsUsed);
}
// pop the stack
currCBI = currCBI.outer;
}
return super.leaveCall(old, n, v);
}
protected void setupClassBody(ClassBody n) throws SemanticException {
ClassBodyInfo newCDI = new ClassBodyInfo();
newCDI.outer = currCBI;
currCBI = newCDI;
// set up currClassFinalFieldInitCounts to contain mappings
// for all the final fields of the class.
Iterator classMembers = n.members().iterator();
while (classMembers.hasNext()) {
ClassMember cm = (ClassMember)classMembers.next();
if (cm instanceof FieldDecl) {
FieldDecl fd = (FieldDecl)cm;
if (fd.flags().isFinal()) {
MinMaxInitCount initCount;
if (fd.init() != null) {
// the field has an initializer
initCount = new MinMaxInitCount(InitCount.ONE,InitCount.ONE);
// do dataflow over the initialization expression
// to pick up any uses of outer local variables.
if (currCBI.outer != null)
dataflow(fd.init());
}
else {
// the field does not have an initializer
initCount = new MinMaxInitCount(InitCount.ZERO,InitCount.ZERO);
}
newCDI.currClassFinalFieldInitCounts.put(fd.fieldInstance(),
initCount);
}
}
}
}
/**
* Check that each static final field is initialized exactly once.
*
* @param cb The ClassBody of the class declaring the fields to check.
* @throws SemanticException
*/
protected void checkStaticFinalFieldsInit(ClassBody cb) throws SemanticException {
// check that all static fields have been initialized exactly once.
for (Iterator iter = currCBI.currClassFinalFieldInitCounts.entrySet().iterator();
iter.hasNext(); ) {
Map.Entry e = (Map.Entry)iter.next();
if (e.getKey() instanceof FieldInstance) {
FieldInstance fi = (FieldInstance)e.getKey();
if (fi.flags().isStatic() && fi.flags().isFinal()) {
MinMaxInitCount initCount = (MinMaxInitCount)e.getValue();
if (InitCount.ZERO.equals(initCount.getMin())) {
throw new SemanticException("field \"" + fi.name() +
"\" might not have been initialized",
cb.position());
}
}
}
}
}
/**
* Check that each non static final field has been initialized exactly once,
* taking into account the fact that constructors may call other
* constructors.
*
* @param cb The ClassBody of the class declaring the fields to check.
* @throws SemanticException
*/
protected void checkNonStaticFinalFieldsInit(ClassBody cb) throws SemanticException {
// for each non-static final field instance, check that all
// constructors intialize it exactly once, taking into account constructor calls.
for (Iterator iter = currCBI.currClassFinalFieldInitCounts.keySet().iterator();
iter.hasNext(); ) {
FieldInstance fi = (FieldInstance)iter.next();
if (fi.flags().isFinal() && !fi.flags().isStatic()) {
// the field is final and not static
// it must be initialized exactly once.
// navigate up through all of the the constructors
// that this constructor calls.
boolean fieldInitializedBeforeConstructors = false;
MinMaxInitCount ic = (MinMaxInitCount)
currCBI.currClassFinalFieldInitCounts.get(fi);
if (ic != null && !InitCount.ZERO.equals(ic.getMin())) {
fieldInitializedBeforeConstructors = true;
}
for (Iterator iter2 = currCBI.allConstructors.iterator();
iter2.hasNext(); ) {
ConstructorDecl cd = (ConstructorDecl)iter2.next();
ConstructorInstance ciStart = cd.constructorInstance();
ConstructorInstance ci = ciStart;
boolean isInitialized = fieldInitializedBeforeConstructors;
while (ci != null) {
Set s = (Set)currCBI.fieldsConstructorInitializes.get(ci);
if (s != null && s.contains(fi)) {
if (isInitialized) {
throw new SemanticException("field \"" + fi.name() +
"\" might have already been initialized",
cd.position());
}
isInitialized = true;
}
ci = (ConstructorInstance)currCBI.constructorCalls.get(ci);
}
if (!isInitialized) {
throw new SemanticException("field \"" + fi.name() +
"\" might not have been initialized",
ciStart.position());
}
}
}
}
}
/**
* Construct a flow graph for the <code>Expr</code> provided, and call
* <code>dataflow(FlowGraph)</code>. Is also responsible for calling
* <code>post(FlowGraph, Term)</code> after
* <code>dataflow(FlowGraph)</code> has been called.
* There is no need to push a CFG onto the stack, as dataflow is not
* performed on entry in this analysis.
*/
protected void dataflow(Expr root) throws SemanticException {
// Build the control flow graph.
FlowGraph g = new FlowGraph(root, forward);
CFGBuilder v = createCFGBuilder(ts, g);
v.visitGraph();
dataflow(g);
post(g, root);
}
/**
* The initial item to be given to the entry point of the dataflow contains
* the init counts for the final fields.
*/
public Item createInitialItem(FlowGraph graph, Term node) {
if (node == graph.startNode()) {
return createInitDFI();
}
return BOTTOM;
}
private DataFlowItem createInitDFI() {
return new DataFlowItem(new HashMap(currCBI.currClassFinalFieldInitCounts));
}
/**
* The confluence operator for <code>Initializer</code>s and
* <code>Constructor</code>s needs to be a
* little special, as we are only concerned with non-exceptional flows in
* these cases.
* This method ensures that a slightly different confluence is performed
* for these <code>Term</code>s, otherwise
* <code>confluence(List, Term)</code> is called instead.
*/
protected Item confluence(List items, List itemKeys, Term node, FlowGraph graph) {
if (node instanceof Initializer || node instanceof ConstructorDecl) {
List filtered = filterItemsNonException(items, itemKeys);
if (filtered.isEmpty()) {
return createInitDFI();
}
else if (filtered.size() == 1) {
return (Item)filtered.get(0);
}
else {
return confluence(filtered, node, graph);
}
}
return confluence(items, node, graph);
}
/**
* The confluence operator is essentially the union of all of the
* inItems. However, if two or more of the initCount maps from
* the inItems each have a MinMaxInitCounts entry for the same
* VarInstance, the conflict must be resolved, by using the
* minimum of all mins and the maximum of all maxs.
*/
public Item confluence(List inItems, Term node, FlowGraph graph) {
// Resolve any conflicts pairwise.
Iterator iter = inItems.iterator();
Map m = null;
while (iter.hasNext()) {
Item itm = (Item)iter.next();
if (itm == BOTTOM) continue;
if (m == null) {
m = new HashMap(((DataFlowItem)itm).initStatus);
}
else {
Map n = ((DataFlowItem)itm).initStatus;
for (Iterator iter2 = n.entrySet().iterator(); iter2.hasNext(); ) {
Map.Entry entry = (Map.Entry)iter2.next();
VarInstance v = (VarInstance)entry.getKey();
MinMaxInitCount initCount1 = (MinMaxInitCount)m.get(v);
MinMaxInitCount initCount2 = (MinMaxInitCount)entry.getValue();
m.put(v, MinMaxInitCount.join(initCount1, initCount2));
}
}
}
if (m == null) return BOTTOM;
return new DataFlowItem(m);
}
protected Map flow(List inItems, List inItemKeys, FlowGraph graph, Term n, Set edgeKeys) {
return this.flowToBooleanFlow(inItems, inItemKeys, graph, n, edgeKeys);
}
/**
* Perform the appropriate flow operations for the Terms. This method
* delegates to other appropriate methods in this class, for modularity.
*
* To summarize:
* - Formals: declaration of a Formal param, just insert a new
* MinMaxInitCount for the LocalInstance.
* - LocalDecl: a declaration of a local variable, just insert a new
* MinMaxInitCount for the LocalInstance as appropriate
* based on whether the declaration has an initializer or not.
* - Assign: if the LHS of the assign is a local var or a field that we
* are interested in, then increment the min and max counts
* for that local var or field.
*/
public Map flow(Item trueItem, Item falseItem, Item otherItem, FlowGraph graph, Term n, Set succEdgeKeys) {
Item inItem = safeConfluence(trueItem, FlowGraph.EDGE_KEY_TRUE,
falseItem, FlowGraph.EDGE_KEY_FALSE,
otherItem, FlowGraph.EDGE_KEY_OTHER,
n, graph);
if (inItem == BOTTOM) {
return itemToMap(BOTTOM, succEdgeKeys);
}
DataFlowItem inDFItem = ((DataFlowItem)inItem);
Map ret = null;
if (n instanceof Formal) {
// formal argument declaration.
ret = flowFormal(inDFItem, graph, (Formal)n, succEdgeKeys);
}
else if (n instanceof LocalDecl) {
// local variable declaration.
ret = flowLocalDecl(inDFItem, graph, (LocalDecl)n, succEdgeKeys);
}
else if (n instanceof LocalAssign) {
// assignment to a local variable
ret = flowLocalAssign(inDFItem, graph, (LocalAssign)n, succEdgeKeys);
}
else if (n instanceof FieldAssign) {
// assignment to a field
ret = flowFieldAssign(inDFItem, graph, (FieldAssign)n, succEdgeKeys);
}
else if (n instanceof ConstructorCall) {
// call to another constructor.
ret = flowConstructorCall(inDFItem, graph, (ConstructorCall)n, succEdgeKeys);
}
else if (n instanceof Expr && ((Expr)n).type().isBoolean() &&
(n instanceof Binary || n instanceof Unary)) {
if (trueItem == null) trueItem = inDFItem;
if (falseItem == null) falseItem = inDFItem;
ret = flowBooleanConditions(trueItem, falseItem, inDFItem, graph, (Expr)n, succEdgeKeys);
}
else {
ret = flowOther(inDFItem, graph, n, succEdgeKeys);
}
if (ret != null) {
return ret;
}
return itemToMap(inItem, succEdgeKeys);
}
/**
* Perform the appropriate flow operations for declaration of a formal
* parameter
*/
protected Map flowFormal(DataFlowItem inItem, FlowGraph graph, Formal f, Set succEdgeKeys) {
Map m = new HashMap(inItem.initStatus);
// a formal argument is always defined.
m.put(f.localInstance(), new MinMaxInitCount(InitCount.ONE,InitCount.ONE));
// record the fact that we have seen the formal declaration
currCBI.localDeclarations.add(f.localInstance());
return itemToMap(new DataFlowItem(m), succEdgeKeys);
}
/**
* Perform the appropriate flow operations for declaration of a local
* variable
*/
protected Map flowLocalDecl(DataFlowItem inItem,
FlowGraph graph,
LocalDecl ld,
Set succEdgeKeys) {
Map m = new HashMap(inItem.initStatus);
MinMaxInitCount initCount = (MinMaxInitCount)m.get(ld.localInstance());
//if (initCount == null) {
if (ld.init() != null) {
// declaration of local var with initialization.
initCount = new MinMaxInitCount(InitCount.ONE,
InitCount.ONE);
}
else {
// declaration of local var with no initialization.
initCount = new MinMaxInitCount(InitCount.ZERO,InitCount.ZERO);
}
m.put(ld.localInstance(), initCount);
// }
// else {
// the initCount is not null. We now have a problem. Why is the
// initCount not null? Has this variable been assigned in its own
// initialization, or is this a declaration inside a loop body?
// XXX@@@ THIS IS A BUG THAT NEEDS TO BE FIXED.
// Currently, the declaration "final int i = (i=5);" will
// not be rejected, as we cannot distinguish between that and
// "while (true) {final int i = 4;}"
// }
// record the fact that we have seen a local declaration
currCBI.localDeclarations.add(ld.localInstance());
return itemToMap(new DataFlowItem(m), succEdgeKeys);
}
/**
* Perform the appropriate flow operations for assignment to a local
* variable
*/
protected Map flowLocalAssign(DataFlowItem inItem,
FlowGraph graph,
LocalAssign a,
Set succEdgeKeys) {
Local l = (Local) a.left();
Map m = new HashMap(inItem.initStatus);
MinMaxInitCount initCount = (MinMaxInitCount)m.get(l.localInstance());
// initcount could be null if the local is defined in the outer
// class, or if we have not yet seen its declaration (i.e. the
// local is used in its own initialization)
if (initCount == null) {
initCount = new MinMaxInitCount(InitCount.ZERO,InitCount.ZERO);
}
initCount = new MinMaxInitCount(initCount.getMin().increment(),
initCount.getMax().increment());
m.put(l.localInstance(), initCount);
return itemToMap(new DataFlowItem(m), succEdgeKeys);
}
/**
* Perform the appropriate flow operations for assignment to a field
*/
protected Map flowFieldAssign(DataFlowItem inItem,
FlowGraph graph,
FieldAssign a,
Set succEdgeKeys) {
Field f = (Field)a.left();
FieldInstance fi = f.fieldInstance();
if (fi.flags().isFinal() && isFieldsTargetAppropriate(f)) {
// this field is final and the target for this field is
// appropriate for what we are interested in.
Map m = new HashMap(inItem.initStatus);
MinMaxInitCount initCount = (MinMaxInitCount)m.get(fi);
// initCount may be null if the field is defined in an
// outer class.
if (initCount != null) {
initCount = new MinMaxInitCount(initCount.getMin().increment(),
initCount.getMax().increment());
m.put(fi, initCount);
return itemToMap(new DataFlowItem(m), succEdgeKeys);
}
}
return null;
}
/**
* Perform the appropriate flow operations for a constructor call
*/
protected Map flowConstructorCall(DataFlowItem inItem,
FlowGraph graph,
ConstructorCall cc,
Set succEdgeKeys) {
if (ConstructorCall.THIS.equals(cc.kind())) {
// currCodeDecl must be a ConstructorDecl, as that
// is the only place constructor calls are allowed
// record the fact that the current constructor calls the other
// constructor
currCBI.constructorCalls.put(((ConstructorDecl)currCBI.currCodeDecl).constructorInstance(),
cc.constructorInstance());
}
return null;
}
/**
* Allow subclasses to override if necessary.
*/
protected Map flowOther(DataFlowItem inItem, FlowGraph graph, Node n, Set succEdgeKeys) {
return null;
}
/**
* Determine if we are interested in this field on the basis of the
* target of the field. To wit, if the field
* is static, then the target of the field must be the current class; if
* the field is not static then the target must be "this".
*/
protected boolean isFieldsTargetAppropriate(Field f) {
if (f.fieldInstance().flags().isStatic()) {
ClassType containingClass = (ClassType)currCBI.currCodeDecl.codeInstance().container();
return containingClass.equals(f.fieldInstance().container());
}
else {
return (f.target() instanceof Special &&
Special.THIS.equals(((Special)f.target()).kind()));
}
}
/**
* Check that the conditions of initialization are not broken.
*
* To summarize the conditions:
* - Local variables must be initialized before use, (i.e. min count > 0)
* - Final local variables (including Formals) cannot be assigned to more
* than once (i.e. max count <= 1)
* - Final non-static fields whose target is this cannot be assigned to
* more than once
* - Final static fields whose target is the current class cannot be
* assigned to more than once
*
*
* This method is also responsible for maintaining state between the
* dataflows over Initializers, by copying back the appropriate
* MinMaxInitCounts to the map currClassFinalFieldInitCounts.
*/
public void check(FlowGraph graph, Term n, Item inItem, Map outItems) throws SemanticException {
DataFlowItem dfIn = (DataFlowItem)inItem;
if (dfIn == null) {
// There is no input data flow item. This can happen if we are
// checking an unreachable term, and so no Items have flowed
// through the term. For example, in the code fragment:
// a: do { break a; } while (++i < 10);
// the expression "++i < 10" is unreachable, but the as there is
// no unreachable statement, the Java Language Spec permits it.
// Set inItem to a default Item
dfIn = createInitDFI();
}
DataFlowItem dfOut = null;
if (outItems != null && !outItems.isEmpty()) {
// due to the flow equations, all DataFlowItems in the outItems map
// are the same, so just take the first one.
dfOut = (DataFlowItem)outItems.values().iterator().next();
if (n instanceof Local) {
checkLocal(graph, (Local)n, dfIn, dfOut);
}
else if (n instanceof LocalAssign) {
checkLocalAssign(graph, (LocalAssign)n, dfIn, dfOut);
}
else if (n instanceof FieldAssign) {
checkFieldAssign(graph, (FieldAssign)n, dfIn, dfOut);
}
else if (n instanceof ClassBody) {
checkClassBody(graph, (ClassBody)n, dfIn, dfOut);
}
else {
checkOther(graph, n, dfIn, dfOut);
}
}
else {
// this local assign node has not had data flow performed over it.
// probably a node in a finally block. Just ignore it.
}
if (n == graph.finishNode()) {
if (currCBI.currCodeDecl instanceof Initializer) {
finishInitializer(graph,
(Initializer)currCBI.currCodeDecl,
dfIn,
dfOut);
}
if (currCBI.currCodeDecl instanceof ConstructorDecl) {
finishConstructorDecl(graph,
(ConstructorDecl)currCBI.currCodeDecl,
dfIn,
dfOut);
}
}
}
/**
* Perform necessary actions upon seeing the Initializer
* <code>initializer</code>.
*/
protected void finishInitializer(FlowGraph graph,
Initializer initializer,
DataFlowItem dfIn,
DataFlowItem dfOut) {
// We are finishing the checking of an intializer.
// We need to copy back the init counts of any fields back into
// currClassFinalFieldInitCounts, so that the counts are
// correct for the next initializer or constructor.
Iterator iter = dfOut.initStatus.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry e = (Map.Entry)iter.next();
if (e.getKey() instanceof FieldInstance) {
FieldInstance fi = (FieldInstance)e.getKey();
if (fi.flags().isFinal()) {
// we don't need to join the init counts, as all
// dataflows will go through all of the
// initializers
currCBI.currClassFinalFieldInitCounts.put(fi,
e.getValue());
}
}
}
}
/**
* Perform necessary actions upon seeing the ConstructorDecl
* <code>cd</code>.
*/
protected void finishConstructorDecl(FlowGraph graph,
ConstructorDecl cd,
DataFlowItem dfIn,
DataFlowItem dfOut) {
ConstructorInstance ci = cd.constructorInstance();
// we need to set currCBI.fieldsConstructorInitializes correctly.
// It is meant to contain the non-static final fields that the
// constructor ci initializes.
//
// Note that dfOut.initStatus contains only the MinMaxInitCounts
// for _normal_ termination of the constructor (see the
// method confluence). This means that if dfOut says the min
// count of the initialization for a final non-static field
// is one, and that is different from what is recoreded in
// currCBI.currClassFinalFieldInitCounts (which is the counts
// of the initializations performed by initializers), then
// the constructor does indeed initialize the field.
Set s = new HashSet();
// go through every final non-static field in dfOut.initStatus
Iterator iter = dfOut.initStatus.entrySet().iterator();
while (iter.hasNext()) {
Entry e = (Entry)iter.next();
if (e.getKey() instanceof FieldInstance &&
((FieldInstance)e.getKey()).flags().isFinal() &&
!((FieldInstance)e.getKey()).flags().isStatic()) {
// we have a final non-static field
FieldInstance fi = (FieldInstance)e.getKey();
MinMaxInitCount initCount = (MinMaxInitCount)e.getValue();
MinMaxInitCount origInitCount = (MinMaxInitCount)currCBI.currClassFinalFieldInitCounts.get(fi);
if (initCount.getMin() == InitCount.ONE &&
(origInitCount == null || origInitCount.getMin() == InitCount.ZERO)) {
// the constructor initialized this field
s.add(fi);
}
}
}
if (!s.isEmpty()) {
currCBI.fieldsConstructorInitializes.put(ci, s);
}
}
/**
* Check that the local variable <code>l</code> is used correctly.
*/
protected void checkLocal(FlowGraph graph,
Local l,
DataFlowItem dfIn,
DataFlowItem dfOut)
throws SemanticException {
if (!currCBI.localDeclarations.contains(l.localInstance())) {
// it's a local variable that has not been declared within
// this scope. The only way this can arise is from an
// inner class that is not a member of a class (typically
// a local class, or an anonymous class declared in a method,
// constructor or initializer).
// We need to check that it is a final local, and also
// keep track of it, to ensure that it has been definitely
// assigned at this point.
currCBI.outerLocalsUsed.add(l.localInstance());
}
else {
MinMaxInitCount initCount = (MinMaxInitCount)
dfIn.initStatus.get(l.localInstance());
if (initCount != null && InitCount.ZERO.equals(initCount.getMin())) {
// the local variable may not have been initialized.
// However, we only want to complain if the local is reachable
if (l.reachable()) {
throw new SemanticException("Local variable \"" + l.name() +
"\" may not have been initialized",
l.position());
}
}
}
}
protected void checkLocalInstanceInit(LocalInstance li,
DataFlowItem dfIn,
Position pos)
throws SemanticException {
MinMaxInitCount initCount = (MinMaxInitCount)dfIn.initStatus.get(li);
if (initCount != null && InitCount.ZERO.equals(initCount.getMin())) {
// the local variable may not have been initialized.
throw new SemanticException("Local variable \"" + li.name() +
"\" may not have been initialized",
pos);
}
}
/**
* Check that the assignment to a local variable is correct.
*/
protected void checkLocalAssign(FlowGraph graph,
LocalAssign a,
DataFlowItem dfIn,
DataFlowItem dfOut)
throws SemanticException {
LocalInstance li = ((Local)a.left()).localInstance();
if (!currCBI.localDeclarations.contains(li)) {
throw new SemanticException("Final local variable \"" + li.name() +
"\" cannot be assigned to in an inner class.",
a.position());
}
MinMaxInitCount initCount = (MinMaxInitCount)
dfOut.initStatus.get(li);
if (li.flags().isFinal() && InitCount.MANY.equals(initCount.getMax())) {
throw new SemanticException("variable \"" + li.name() +
"\" might already have been assigned to",
a.position());
}
}
/**
* Check that the assignment to a field is correct.
*/
protected void checkFieldAssign(FlowGraph graph,
FieldAssign a,
DataFlowItem dfIn,
DataFlowItem dfOut)
throws SemanticException {
Field f = (Field)a.left();
FieldInstance fi = f.fieldInstance();
if (fi.flags().isFinal()) {
if ((currCBI.currCodeDecl instanceof ConstructorDecl ||
currCBI.currCodeDecl instanceof Initializer) &&
isFieldsTargetAppropriate(f)) {
// we are in a constructor or initializer block and
// if the field is static then the target is the class
// at hand, and if it is not static then the
// target of the field is this.
// So a final field in this situation can be
// assigned to at most once.
MinMaxInitCount initCount = (MinMaxInitCount)
dfOut.initStatus.get(fi);
if (InitCount.MANY.equals(initCount.getMax())) {
throw new SemanticException("field \"" + fi.name() +
"\" might already have been assigned to",
a.position());
}
}
else {
// not in a constructor or intializer, or the target is
// not appropriate. So we cannot assign
// to a final field at all.
throw new SemanticException("Cannot assign a value " +
"to final field \"" + fi.name() + "\"",
a.position());
}
}
}
/**
* Check that the set of <code>LocalInstance</code>s
* <code>localsUsed</code>, which is the set of locals used in the inner
* class declared by <code>cb</code>
* are initialized before the class declaration.
* @throws SemanticException
*/
protected void checkClassBody(FlowGraph graph,
ClassBody cb,
DataFlowItem dfIn,
DataFlowItem dfOut)
throws SemanticException {
// we need to check that the locals used inside this class body
// have all been defined at this point.
Set localsUsed = (Set)currCBI.localsUsedInClassBodies.get(cb);
if (localsUsed != null) {
checkLocalsUsedByInnerClass(graph,
cb,
localsUsed,
dfIn,
dfOut);
}
}
/**
* Check that the set of <code>LocalInstance</code>s
* <code>localsUsed</code>, which is the set of locals used in the inner
* class declared by <code>cb</code>
* are initialized before the class declaration.
*/
protected void checkLocalsUsedByInnerClass(FlowGraph graph,
ClassBody cb,
Set localsUsed,
DataFlowItem dfIn,
DataFlowItem dfOut)
throws SemanticException {
for (Iterator iter = localsUsed.iterator(); iter.hasNext(); ) {
LocalInstance li = (LocalInstance)iter.next();
MinMaxInitCount initCount = (MinMaxInitCount)
dfOut.initStatus.get(li);
if (!currCBI.localDeclarations.contains(li)) {
// the local wasn't defined in this scope.
currCBI.outerLocalsUsed.add(li);
}
else if (initCount == null || InitCount.ZERO.equals(initCount.getMin())) {
// initCount will in general not be null, as the local variable
// li is declared in the current class; however, if the inner
// class is declared in the initializer of the local variable
// declaration, then initCount could in fact be null, as we
// leave the inner class before we have performed flowLocalDecl
// for the local variable declaration.
throw new SemanticException("Local variable \"" + li.name() +
"\" must be initialized before the class " +
"declaration.",
cb.position());
}
}
}
/**
* Allow subclasses to override the checking of other nodes, if needed.
*/
protected void checkOther(FlowGraph graph,
Node n,
DataFlowItem dfIn,
DataFlowItem dfOut)
throws SemanticException {
}
}