/* * This file is part of the OpenJML project. * Author: David R. Cok */ package org.jmlspecs.openjml.esc; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import javax.tools.JavaFileObject; import org.jmlspecs.annotation.NonNull; import org.jmlspecs.openjml.*; import org.jmlspecs.openjml.JmlTree.JmlBBArrayAccess; import org.jmlspecs.openjml.JmlTree.JmlBBArrayAssignment; import org.jmlspecs.openjml.JmlTree.JmlBBFieldAccess; import org.jmlspecs.openjml.JmlTree.JmlBBFieldAssignment; import org.jmlspecs.openjml.JmlTree.JmlBinary; import org.jmlspecs.openjml.JmlTree.JmlChoose; import org.jmlspecs.openjml.JmlTree.JmlClassDecl; import org.jmlspecs.openjml.JmlTree.JmlCompilationUnit; import org.jmlspecs.openjml.JmlTree.JmlMethodSig; import org.jmlspecs.openjml.JmlTree.JmlGroupName; import org.jmlspecs.openjml.JmlTree.JmlImport; import org.jmlspecs.openjml.JmlTree.JmlLabeledStatement; import org.jmlspecs.openjml.JmlTree.JmlLblExpression; import org.jmlspecs.openjml.JmlTree.JmlMethodClauseCallable; import org.jmlspecs.openjml.JmlTree.JmlMethodClauseConditional; import org.jmlspecs.openjml.JmlTree.JmlMethodClauseDecl; import org.jmlspecs.openjml.JmlTree.JmlMethodClauseExpr; import org.jmlspecs.openjml.JmlTree.JmlMethodClauseGroup; import org.jmlspecs.openjml.JmlTree.JmlMethodClauseSignals; import org.jmlspecs.openjml.JmlTree.JmlMethodClauseSignalsOnly; import org.jmlspecs.openjml.JmlTree.JmlMethodClauseStoreRef; import org.jmlspecs.openjml.JmlTree.JmlMethodDecl; import org.jmlspecs.openjml.JmlTree.JmlMethodInvocation; import org.jmlspecs.openjml.JmlTree.JmlMethodSpecs; import org.jmlspecs.openjml.JmlTree.JmlModelProgramStatement; import org.jmlspecs.openjml.JmlTree.JmlPrimitiveTypeTree; import org.jmlspecs.openjml.JmlTree.JmlQuantifiedExpr; import org.jmlspecs.openjml.JmlTree.JmlSetComprehension; import org.jmlspecs.openjml.JmlTree.JmlSingleton; import org.jmlspecs.openjml.JmlTree.JmlSpecificationCase; import org.jmlspecs.openjml.JmlTree.JmlStatement; import org.jmlspecs.openjml.JmlTree.JmlStatementDecls; import org.jmlspecs.openjml.JmlTree.JmlStatementExpr; import org.jmlspecs.openjml.JmlTree.JmlStatementHavoc; import org.jmlspecs.openjml.JmlTree.JmlStatementLoop; import org.jmlspecs.openjml.JmlTree.JmlStatementSpec; import org.jmlspecs.openjml.JmlTree.JmlStoreRefArrayRange; import org.jmlspecs.openjml.JmlTree.JmlStoreRefKeyword; import org.jmlspecs.openjml.JmlTree.JmlStoreRefListExpression; import org.jmlspecs.openjml.JmlTree.JmlTypeClauseConditional; import org.jmlspecs.openjml.JmlTree.JmlTypeClauseConstraint; import org.jmlspecs.openjml.JmlTree.JmlTypeClauseDecl; import org.jmlspecs.openjml.JmlTree.JmlTypeClauseExpr; import org.jmlspecs.openjml.JmlTree.JmlTypeClauseIn; import org.jmlspecs.openjml.JmlTree.JmlTypeClauseInitializer; import org.jmlspecs.openjml.JmlTree.JmlTypeClauseMaps; import org.jmlspecs.openjml.JmlTree.JmlTypeClauseMonitorsFor; import org.jmlspecs.openjml.JmlTree.JmlTypeClauseRepresents; import org.jmlspecs.openjml.JmlTree.JmlVariableDecl; import org.jmlspecs.openjml.esc.BasicProgram; import org.jmlspecs.openjml.esc.BasicProgram.BasicBlock; import com.sun.tools.javac.code.*; import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Symbol.MethodSymbol; import com.sun.tools.javac.code.Symbol.TypeSymbol; import com.sun.tools.javac.code.Symbol.VarSymbol; import com.sun.tools.javac.code.Type.ArrayType; import com.sun.tools.javac.tree.*; import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCArrayAccess; import com.sun.tools.javac.tree.JCTree.JCArrayTypeTree; import com.sun.tools.javac.tree.JCTree.JCAssert; import com.sun.tools.javac.tree.JCTree.JCAssign; import com.sun.tools.javac.tree.JCTree.JCAssignOp; import com.sun.tools.javac.tree.JCTree.JCBinary; import com.sun.tools.javac.tree.JCTree.JCBlock; import com.sun.tools.javac.tree.JCTree.JCClassDecl; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import com.sun.tools.javac.tree.JCTree.JCConditional; import com.sun.tools.javac.tree.JCTree.JCErroneous; import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.tree.JCTree.JCExpressionStatement; import com.sun.tools.javac.tree.JCTree.JCFieldAccess; import com.sun.tools.javac.tree.JCTree.JCIdent; import com.sun.tools.javac.tree.JCTree.JCImport; import com.sun.tools.javac.tree.JCTree.JCInstanceOf; import com.sun.tools.javac.tree.JCTree.JCLabeledStatement; import com.sun.tools.javac.tree.JCTree.JCLiteral; import com.sun.tools.javac.tree.JCTree.JCMethodDecl; import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; import com.sun.tools.javac.tree.JCTree.JCModifiers; import com.sun.tools.javac.tree.JCTree.JCNewArray; import com.sun.tools.javac.tree.JCTree.JCNewClass; import com.sun.tools.javac.tree.JCTree.JCParens; import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree; import com.sun.tools.javac.tree.JCTree.JCStatement; import com.sun.tools.javac.tree.JCTree.JCSynchronized; import com.sun.tools.javac.tree.JCTree.JCTypeApply; import com.sun.tools.javac.tree.JCTree.JCTypeCast; import com.sun.tools.javac.tree.JCTree.JCTypeParameter; import com.sun.tools.javac.tree.JCTree.JCUnary; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import com.sun.tools.javac.tree.JCTree.JCWildcard; import com.sun.tools.javac.tree.JCTree.LetExpr; import com.sun.tools.javac.tree.JCTree.TypeBoundKind; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.ListBuffer; import com.sun.tools.javac.util.Log; import com.sun.tools.javac.util.Log.WriterKind; import com.sun.tools.javac.util.Name; import com.sun.tools.javac.util.Names; /** This class converts a Java AST into basic block form (including DSA and * passification). All Java (and JML) statements are rewritten into assume and * assert statements, with basic blocks being created to represent the control * flow. In addition, note the following: * <UL> * <LI> No assertions to represent Java or JML semantics are added, except for those * needed to convert control flow into basic blocks * <LI> The name field of JCIdent nodes are rewritten in place to * convert the program to single-assignment form. Note that this means that * expressions and subexpressions of the input tree may not be shared across statements or * within expressions, at least where the sharing would mix different incarnations * of the variable. * <LI> The JML \\old and \\pre expressions are recognized and translated to use * the appropriate single-assignment identifiers. * </UL> * <P> * The input tree must consist of (FIXME) * <UL> * <LI> A valid Java program (with any Java constructs): * <UL> * <LI> assignOp expressions are not allowed * <LI> Java let expressions are allowed * </UL> * <LI> JML assume, assert, comment statements, with JML expressions. * <LI> The JML expressions contain only * <UL> * <LI> Java operators (omitting assignment-operator combinations) * <LI> JML quantified expressions * <LI> set comprehension expressions * <LI> \\old and \\pre expressions * <LI> [ FIXME ??? JML type literals, subtype operations, method calls in specs?] * </UL * </UL> * * <P> * Basic block output form contains only this subset of AST nodes: (FIXME) * <UL> * <LI> JML assume, assert, comment statements * <LI> JCLiteral - numeric (all of them? FIXME), null, boolean, class (String?, character?) * <LI> JCIdent * <LI> JCParens * <LI> JCUnary * <LI> JCBinary * <LI> JCConditional * <LI> JmlBBFieldAccess * <LI> JmlBBArrayAccess * <LI> JmlBBFieldAssign * <LI> JmlBBArrayAssign * <LI> JCMethodInvocation - only pure methods within specifications * <LI> JmlMethodInvocation - old, typeof * <LI> JmlQuantifiedExpr - only forall and exists * <LI> JCTypeCast - but the clazz element now has a JCLiteral (which is a type literal) * <LI> [JCInstanceOf - not present - use a typeof and a subtype operation] * </UL> * <P> * FIXME - comment on the special use of comment statements for tracing * <P> * @author David Cok */ public class BasicBlocker2 extends BasicBlockerParent<BasicProgram.BasicBlock,BasicProgram> { /** Creates an empty new BasicProgram */ @Override public BasicProgram newProgram(Context context) { return new BasicProgram(context); } /** Creates an empty new BasicBlock */ @Override public BasicProgram.BasicBlock newBlock(JCIdent id){ return new BasicProgram.BasicBlock(id); } // THE FOLLOWING ARE ALL FIXED STRINGS //----------------------------------------------------------------- // Names for a bunch of synthetic variables /** Standard name for the variable that represents the heap (which excludes local variables) */ public static final @NonNull String HEAP_VAR = "_heap__"; // FIXME cf JmlAssertionAdder for same string //----------------------------------------------------------------- // Names for various basic blocks /** Standard name for the block that starts the body */ public static final @NonNull String BODY_BLOCK_NAME = "bodyBegin"; /** Standard name for the starting block of the program (just has the preconditions) */ public static final @NonNull String START_BLOCK_NAME = "Start"; // Other somewhat arbitrary identifier names or parts of names /** Prefix for the names of the N-dimensional arrays used to model Java's heap-based arrays */ public static final @NonNull String ARRAY_BASE_NAME = "arrays_"; // THE FOLLOWING FIELDS ARE EXPECTED TO BE CONSTANT FOR THE LIFE OF THE OBJECT // They are either initialized in the constructor or initialized on first use /** General utilities */ @NonNull final protected Utils utils; /** The factory used to create AST nodes, initialized in the constructor */ @NonNull final protected JmlTree.Maker factory; // Caution - the following are handy, but they are shared, so they won't // have proper position information - which is OK for esc at this point /** Holds an AST node for a boolean true literal, initialized in the constructor */ @NonNull final protected JCLiteral trueLiteral; /** Holds an AST node for a boolean false literal, initialized in the constructor */ @NonNull final protected JCLiteral falseLiteral; /** Identifier of a synthesized object field holding the length of an array object, initialized in the constructor */ @NonNull final protected JCIdent lengthIdent; /** Symbol of a synthesized object field holding the length of an array object, initialized in the constructor */ @NonNull final protected VarSymbol lengthSym; // THE FOLLOWING FIELDS ARE USED IN THE COURSE OF DOING THE WORK OF CONVERTING // TO BASIC BLOCKS. They are fields of the class because they need to be // shared across the visitor methods. /** Place to put new definitions, such as the equalities defining auxiliary variables */ protected List<BasicProgram.Definition> newdefs; /** Place to put new background assertions, such as axioms and class predicates */ protected List<JCExpression> background; /** This is an integer that rises monotonically on each use and is used * to make sure new identifiers are unique. */ protected int unique; /** This map records the map from the input JCTree elements to the * rewritten value - or at least the value which designates the value of the * input JCTree. */ @NonNull final BiMap<JCTree,JCExpression> bimap = new BiMap<JCTree,JCExpression>(); // FIXME - document - I think this can go away @NonNull final BiMap<JCTree,JCTree> pathmap = new BiMap<JCTree,JCTree>(); /** A mapping from BasicBlock to the sym->incarnation map giving the map that * corresponds to the state at the exit of the BasicBlock. */ @NonNull final protected Map<BasicBlock,VarMap> blockmaps = new HashMap<BasicBlock,VarMap>(); /** A mapping from labels to the sym->incarnation map operative at the position * of the label. */ @NonNull final protected Map<Name,VarMap> labelmaps = new HashMap<Name,VarMap>(); /** Contains names for which a declaration has been issued. */ final protected Set<Name> isDefined = new HashSet<Name>(); // THESE VARIABLES ARE SET (AND RESET) IN THE COURSE OF COMPUTATION // (so they do not need initialization) /** The map from symbol to incarnation number in current use */ @NonNull protected VarMap currentMap; /** The map immediately after declaration of method parameters; this is the mapping of variables to incarnations to use when in the scope of a \pre (or an \old without a label) */ @NonNull protected VarMap premap; /** The variable that keeps track of heap incarnations */ protected JCIdent heapVar; /** The constructor - a new instance of this class is needed for each * Java program being converted to basic blocks. Some class level fields are * only initialized on construction and are not necessarily cleaned up if * there is an exceptional exit. * @param context the compilation context */ public BasicBlocker2(@NonNull Context context) { super(context); // Since we are not registering a tool or reusing singleton tool // instances we don't self register the instance in the context: // context.put(key, this); // Note - some fields are initialized here, others in the initialize() method this.factory = JmlTree.Maker.instance(context); this.utils = Utils.instance(context); this.scanMode = AST_JAVA_MODE; trueLiteral = treeutils.trueLit; falseLiteral = treeutils.falseLit; // This is the symbol to access the length of an array lengthSym = syms.lengthVar; lengthIdent = treeutils.makeIdent(0,lengthSym); } /** Helper routine to initialize the object before starting its task of * converting to a basic program; if this object is to be reused, all * fields must be appropriately initialized here - do not rely on cleanup * on an exceptional exit from a previous use. */ @Override protected void initialize(@NonNull JCMethodDecl methodDecl, @NonNull JCClassDecl classDecl, @NonNull JmlAssertionAdder assertionAdder) { super.initialize(methodDecl,classDecl,assertionAdder); this.arrayBaseSym.clear(); this.localVars.clear(); this.isDefined.clear(); this.unique = 0; this.newdefs = new LinkedList<BasicProgram.Definition>(); this.background = new LinkedList<JCExpression>(); this.blockmaps.clear(); this.labelmaps.clear(); this.bimap.clear(); this.pathmap.clear(); this.heapVar = treeutils.makeIdent(0,assertionAdder.heapSym); // currentMap is set when starting a block // premap is set during execution } // METHODS /** Visits the argument, returning the result as the value of 'result', * and records the mapping from old to new JCTree in the bimap. * @param tree */ @Override public void scan(JCTree tree) { result = null; super.scan(tree); if (tree instanceof JCExpression && !(tree instanceof JCAssign)) { bimap.put(tree, result); } } /** Scans all items in the list, replacing the list element with the result of the scan. */ public void scanList(com.sun.tools.javac.util.List<JCExpression> trees) { if (trees != null) { for (com.sun.tools.javac.util.List<JCExpression> l = trees; l.nonEmpty(); l = l.tail) { scan(l.head); l.head = result; } } result = null; // Defensive } /** Scans the argument and returns the rewritten result. */ public JCExpression convertExpr(JCExpression expr) { scan(expr); return result; } /** Should not need this when everything is implemented */ protected void notImpl(JCTree that) { log.getWriter(WriterKind.NOTICE).println("NOT IMPLEMENTED: BasicBlocker2 - " + that.getClass()); result = trueLiteral; } /** Called by visit methods that should never be called. */ protected void shouldNotBeCalled(JCTree that) { log.error("esc.internal.error","Did not expect to be calling a " + that.getClass() + " within BasicBlocker2"); throw new JmlInternalError(); } /** Creates an encoded name from a symbol and an incarnation position and the * current value of unique - so each call of this method returns a different * name, even with the same arguments. * If the symbol has a null owner (which is illegal if being compiled for RAC), * the symbol is considered to be a one-time-use temporary and the name is * not encoded. * If the symbol has a negative declaration position, that value is not included in the string * @param sym the symbol being given a logical name * @param incarnationPosition the position (character position in * the source file) at which the symbol is used - note that incarnation positions * are not necessarily unique, but can be used for debugging * @return the new name */ protected Name encodedName(VarSymbol sym, long incarnationPosition) { Symbol own = sym.owner; if (incarnationPosition <= 0 || own == null || (!isConstructor && (sym.flags() & Flags.FINAL) != 0) || (isConstructor && (sym.flags() & (Flags.STATIC|Flags.FINAL)) == (Flags.STATIC|Flags.FINAL))) { Name n = sym.getQualifiedName(); if (sym.pos >= 0 && !n.toString().equals(Strings.thisName)) n = names.fromString(n.toString() + ("_" + sym.pos)); if (own != null && own != methodDecl.sym.owner && own instanceof TypeSymbol) { Name s = own.getQualifiedName(); n = names.fromString(s.toString() + "_" + n.toString()); } return n; } else return names.fromString( sym.getQualifiedName() + (sym.pos < 0 ? "_" : ("_" + sym.pos + "_")) // declaration position + incarnationPosition // new definition position + "___" + (++unique) // unique suffix ); } /** A new name for an array name */ // FIXME ?? protected Name encodedArrayName(VarSymbol sym, int incarnationPosition) { Name n; if (incarnationPosition == 0) { n = sym.getQualifiedName(); } else { n = names.fromString(sym.getQualifiedName() + (sym.pos < 0 ? "_" : ("_" + sym.pos + "_")) + incarnationPosition + "___" + (++unique) ); } if (isDefined.add(n)) { JCIdent id = treeutils.makeIdent(0, sym); id.name = n; addDeclaration(id); } return n; } /** Creates an encoded name for a Type variable. * @param sym * @param declarationPosition * @return the new name */ protected Name encodedTypeName(TypeSymbol sym, int declarationPosition) { return names.fromString(sym.flatName() + (declarationPosition < 0 ? "_" : "_" + declarationPosition) + "__" + (++unique)); } /** Creates a new Ident node, but in this case we supply the nam * rather than using the name from the current incarnation map. * This is just used for DSA assignments. */ protected JCIdent newIdentUse(VarMap map, VarSymbol sym, int useposition) { if (sym.toString().startsWith("aux")) Utils.stop(); Name name = map.getCurrentName(sym); // Creates a name if one has not yet been created JCIdent n = factory.at(useposition).Ident(name); n.sym = sym; n.type = sym.type; return n; } /** Creates an identifier node for a use of a variable at a given source code * position; the current SA version is used. * @param sym the underlying symbol (which gives the declaration location) * @param useposition the source position of its use * @return the new JCIdent node */ protected JCIdent newIdentUse(VarSymbol sym, int useposition) { if (sym.toString().startsWith("aux")) Utils.stop(); Name name = currentMap.getCurrentName(sym); JCIdent n = factory.at(useposition).Ident(name); n.sym = sym; n.type = sym.type; return n; } /** Creates an identifier node for a use of a type variable at a given source code * position; the current SA version is used. */ protected JCIdent newTypeIdentUse(TypeSymbol sym, int useposition) { Name name = currentMap.getCurrentName(sym); JCIdent n = factory.at(useposition).Ident(name); n.sym = sym; n.type = sym.type; return n; } /** Creates an identifier nodes for a new SA version of the variable, that is, * when the variable is assigned to. * @param id the old identifier, giving the root name, symbol and type * @param incarnationPosition the position (and incarnation number) of the new variable * @return the new identifier */ protected JCIdent newIdentIncarnation(JCIdent id, int incarnationPosition) { return newIdentIncarnation((VarSymbol)id.sym,incarnationPosition); } /** Creates a new incarnation of a variable */ protected JCIdent newIdentIncarnation(VarSymbol vsym, int incarnationPosition) { if (vsym.name.toString().startsWith("phase")) Utils.stop(); JCIdent n = factory.at(incarnationPosition).Ident(encodedName(vsym,incarnationPosition)); n.type = vsym.type; n.sym = vsym; currentMap.putSAVersion(vsym,n.name,unique); // unique is used as the new version number if (isDefined.add(n.name)) addDeclaration(n); return n; } // FIXME - review and document protected JCIdent newArrayIdentIncarnation(VarSymbol vsym, int incarnationPosition) { if (vsym.name.toString().startsWith("aux")) Utils.stop(); JCIdent n = factory.at(incarnationPosition).Ident(encodedArrayName(vsym,incarnationPosition)); n.type = vsym.type; n.sym = vsym; currentMap.putSAVersion(vsym,n.name, unique); // unique is used as the new version number if (isDefined.add(n.name)) addDeclaration(n); return n; } // FIXME - document protected JCIdent newArrayIncarnation(Type componentType, int usePosition) { JCIdent id = getArrayIdent(componentType,usePosition); id = newArrayIdentIncarnation((VarSymbol)id.sym,usePosition); return id; } // FIXME - review and document - not used now, but should be for generics protected JCIdent newTypeVarIncarnation(TypeSymbol vsym, int incarnationPosition) { JCIdent n = factory.at(incarnationPosition).Ident(encodedTypeName(vsym,incarnationPosition)); n.type = JmlTypes.instance(context).TYPE; n.sym = vsym; currentMap.putSAVersion(vsym,n.name); return n; } /** Sets all the variables that are supposed to stay in synch with the value of * currentBlock * @param b the new currentBlock */ @Override protected void setCurrentBlock(BasicBlock b) { super.setCurrentBlock(b); currentMap = blockmaps.get(b); if (currentMap == null) currentMap = initMap(currentBlock); else log.error("jml.internal","The currentMap is unexpectedly already defined for block " + b.id.name); // The check above is purely defensive } /** Files away a completed block, adding it to the blocksCompleted list and * to the lookup Map. * @param b the completed block */ @Override protected void completeBlock(@NonNull BasicBlock b) { super.completeBlock(b); currentMap = null; // Defensive - so no inadvertent assignments } /** Converts the top-level block of a method into the elements of a BasicProgram; * this is the entry point to the object; note that most of the work is done * in place, modifying the tree that is the 'block' argument as needed. * * @param methodDecl the method to convert to to a BasicProgram * @param denestedSpecs the specs of the method * @param classDecl the declaration of the containing class * @return the completed BasicProgram */ public @NonNull BasicProgram convertMethodBody(JCBlock block, @NonNull JmlMethodDecl methodDecl, JmlMethodSpecs denestedSpecs, @NonNull JmlClassDecl classDecl, @NonNull JmlAssertionAdder assertionAdder) { initialize(methodDecl,classDecl,assertionAdder); // Get the set of field symbols mentioned in the method body Set<VarSymbol> vsyms = GetSymbols.collectSymbols(block,assertionAdder.classBiMap.getf(classDecl)); // Define the start block BasicBlock startBlock = newBlock(START_BLOCK_NAME,methodDecl.pos); // Handle the start block a little specially // It does not have any statements in it startBlock(startBlock); // Start it so the currentMap, currentBlock, remainingStatements are defined // Add mappings for the method parameters for (JCVariableDecl d: methodDecl.params) { currentMap.putSAVersion(d.sym, 0); } // Add mappings for all fields mentioned in the body of the method for (VarSymbol v: vsyms) { Name n = currentMap.putSAVersion(v,0); // Makes a name with incarnation 0 JCIdent id = treeutils.makeIdent(v.pos, n, v); addDeclaration(id); } // Save the initial mappings premap = currentMap.copy(); labelmaps.put(null,premap); // Define the body block BasicBlock bodyBlock = newBlock(BODY_BLOCK_NAME,methodDecl.body.pos); follows(startBlock,bodyBlock); completeBlock(currentBlock); // End of the start block // Add declarations of method parameters for (JCVariableDecl d: methodDecl.params) { // We reset this with a location of 0 so that the name does not get // changed. This is only because the premap does not know these names. // And will probably have to change when encodedName is made more robust. FIXME JCVariableDecl dd = treeutils.makeVarDef(d.type,d.name,d.sym.owner,0); bodyBlock.statements.add(dd); } // Put all the program statements in the Body Block bodyBlock.statements.addAll(block.getStatements()); processBlock(bodyBlock); // Iteratively creates and processes following blocks // Finished processing all the blocks // Complete the BasicProgram program.startId = startBlock.id; program.definitions = newdefs; program.background = background; return program; } /** Adds an assert statement into the given statement list; no * translation is performed (the input expression must already be transformed) * @param label the label for the assert statement * @param trExpr the expression which must be true * @param declpos the associated position (e.g. of the declaration that causes the assertion) * @param statements the statement list to which to add the new assert statement * @param usepos the source position at which the expression is asserted * @param source the source file corresponding to usepos * @param statement */ protected void addAssert(Label label, JCExpression trExpr, int declpos, List<JCStatement> statements, int usepos, JavaFileObject source, JCTree statement) { JmlTree.JmlStatementExpr st = factory.at(statement.pos()).JmlExpressionStatement(JmlTokenKind.ASSERT,label,trExpr); st.optionalExpression = null; st.source = source; // source file in which st.pos resides //st.line = -1; st.associatedPos = declpos; st.associatedSource = null; // OK - always same as source st.type = null; // no type for a statement copyEndPosition(st,trExpr); statements.add(st); } // /** Adds a new assume statement to the end of the currentBlock; the assume statement is // * given the declaration pos and label from the arguments; it is presumed the input expression is // * translated, as is the produced assume statement. // * @param pos the declaration position of the assumption // * @param label the kind of assumption // * @param expr the (translated) expression being assumed // */ // protected JmlStatementExpr addAssume(int pos, Label label, JCExpression expr) { // return addAssume(pos,label,expr,currentBlock.statements); // } // /** Adds a new assume statement to the end of the given statements list; the assume statement is * given the declaration pos and label from the arguments; it is presumed the input expression is * translated, as is the produced assume statement. * @param pos the declaration position of the assumption * @param label the kind of assumption * @param that the (translated) expression being assumed * @param statements the list to add the new assume statement to */ protected JmlStatementExpr addAssume(int pos, Label label, JCExpression that, List<JCStatement> statements) { return super.addAssume(pos,label,that,statements); } /** Adds an assumption to the given statement list */ protected JmlStatementExpr addAssume(int startpos, JCTree endpos, Label label, JCExpression that, List<JCStatement> statements) { return super.addAssume(startpos,endpos,label,that,statements); } /** Adds an axiom to the axiom set */ protected void addAxiom(int pos, Label label, JCExpression that, List<JCStatement> statements) { background.add(that); } /** Adds a (copy of) a JCIdent in the list of declarations of the BasicProgram being produced */ protected void addDeclaration(JCIdent that) { JCIdent t = treeutils.makeIdent(0,that.sym); t.name = that.name; program.declarations.add(t); } // FIXME - REVIEW and document static protected String encodeType(Type t) { // FIXME String? char? void? unsigned? TypeTag tag = t.getTag(); if (t instanceof ArrayType) { return "refA$" + encodeType(((ArrayType)t).getComponentType()); } else if (!t.isPrimitive()) { return "REF"; } else if (tag == TypeTag.INT || tag == TypeTag.SHORT || tag == TypeTag.LONG || tag == TypeTag.BYTE) { return "int"; } else if (tag == TypeTag.BOOLEAN) { return "bool"; } else if (tag == TypeTag.FLOAT || tag == TypeTag.DOUBLE) { return "real"; } else if (tag == TypeTag.CHAR) { return "int"; } else { return "unknown"; } } /** This is a cache of VarSymbols so that there is a unique instance for * each array component type used in the program. */ private Map<String,VarSymbol> arrayBaseSym = new HashMap<String,VarSymbol>(); /** Returns a VarSymbol for the heap array that manages all the arrays of * the given component type; this VarSymbol is unique to the type (within * this instance of BasicBlocker2). */ protected VarSymbol getArrayBaseSym(Type componentType) { String s = ARRAY_BASE_NAME + encodeType(componentType); VarSymbol vsym = arrayBaseSym.get(s); if (vsym == null) { Name n = names.fromString(s); Type t = new ArrayType(componentType,syms.arrayClass); vsym = new VarSymbol(0,n,t,null); // null -> No owner vsym.pos = 0; arrayBaseSym.put(s,vsym); } return vsym; } /** Return an identifier for the heap array that manages the given component type. */ protected JCIdent getArrayIdent(Type componentType, int pos) { VarSymbol vsym = getArrayBaseSym(componentType); return newIdentUse(vsym,pos); } // FIXME - review the following with havoc \everything in mind. /** Returns the initial VarMap of the given block; the returned map is a combination * of the maps from all preceding blocks, with appropriate DSA assignments added. * @param block * @return the VarMap for the given block */ protected VarMap initMap(BasicBlock block) { VarMap newMap = new VarMap(); currentMap = newMap; // System.out.println("CREATING BLOCK " + block.id); // for (BasicBlock b: block.preceders()) { // System.out.println("INPUT " + b.id); // System.out.println(blockmaps.get(b)); // } if (block.preceders().size() == 0) { // keep the empty one } else if (block.preceders().size() == 1) { newMap.putAll(blockmaps.get(block.preceders().get(0))); } else { // Here we do the DSA step of combining the results of the blocks that precede // the block we are about to process. The situation is this: a particular symbol, // sym say, may have been modified in any of the preceding blocks. In each case // a new incarnation and a new identifier Name will have been assigned. A record // of that current Identifier Name is in the VarMap for the block. But we need a single // Name to use in this new block. So we pick a new Name to use in the new block, // and for each preceding block we add an assumption of the form newname = oldname. // This assumption is added to the end of the preceding block. // In this version, we use the maximum incarnation as the new name. int pos = block.id.pos; List<VarMap> all = new LinkedList<VarMap>(); VarMap combined = new VarMap(); for (BasicBlock b : block.preceders()) { VarMap m = blockmaps.get(b); all.add(m); combined.putAll(m); } //combined.everythingSAversion = maxe; for (VarSymbol sym: combined.keySet()) { if (sym.owner instanceof Symbol.ClassSymbol) { // If the symbol is owned by a class, then it is implicitly part of each VarMap, // even if it is not explicitly listed. Name maxName = null; long max = -1; //int num = 0; for (VarMap m: all) { Long i = m.getSAVersionNum(sym); //if (i != max) num++; if (i > max) { max = i; maxName = m.getName(sym); } } Name newName = maxName; // if (num > 1) { // JCIdent id = newIdentIncarnation(sym,block.id.pos); // relies on the uniqueness value to be different // // Need to declare this before all relevant blocks, so we do it at the very beginning // program.declarations.add(id); // newName = id.name; // } newMap.putSAVersion(sym,newName,max); for (BasicBlock b: block.preceders) { VarMap m = blockmaps.get(b); Long i = m.getSAVersionNum(sym); if (i < max) { // Type information ? - FIXME JCIdent pold = newIdentUse(m,sym,pos); JCIdent pnew = newIdentUse(newMap,sym,pos); JCBinary eq = treeutils.makeEquality(pos,pnew,pold); addAssume(pos,Label.DSA,eq,b.statements); } } } else { // If the symbol is owned by the method, then if it is not // in every inherited map, // then it has gone out of scope and need not be repeated. Also // it is not subject to havoc \everything Name maxName = null; Long max = -1L; boolean skip = false; for (VarMap m: all) { Name n = m.getName(sym); if (n == null) { skip = true; break; } Long i = m.getSAVersionNum(sym); if (i > max) { max = i; maxName = n; } } if (skip) continue; Name newName = maxName; // boolean different = false; // for (VarMap m: all) { // Name n = m.getName(sym); // if (!newName.equals(n)) { different = true; break; } // } // if (different) { // max++; // JCIdent id = newIdentIncarnation(sym,pos); // relies on the uniqueness value to be different // // Need to declare this before all relevant blocks, so we do it at the very beginning // //program.declarations.add(id); // newName = id.name; // } newMap.putSAVersion(sym,newName,max); //if (different) { for (BasicBlock b: block.preceders) { VarMap m = blockmaps.get(b); Long i = m.getSAVersionNum(sym); if (i < max) { // No position information for these nodes // Type information put in, though I don't know that we need it JCIdent pold = newIdentUse(m,sym,pos); JCIdent pnew = newIdentUse(newMap,sym,pos); JCBinary eq = treeutils.makeEquality(pos,pnew,pold); addAssume(pos,Label.DSA,eq,b.statements); } } //} } } } // Note - this is the map at the start of processing a block // log.noticeWriter.println("MAP FOR BLOCK " + block.id + JmlTree.eol + newMap.toString()); blockmaps.put(block,newMap); return newMap; } // FIXME - review this /** Creates a translated expression whose value is the given type; * the result is a JML type, e.g. a representation of an instantiated generic.*/ protected JCExpression makeTypeLiteral(Type type, int pos) { return treeutils.trType(pos,type); } /** Makes the equivalent of an instanceof operation: \typeof(e) <: \type(type) */ protected JCExpression makeNNInstanceof(JCExpression e, int epos, Type type, int typepos) { JCExpression e1 = treeutils.makeTypeof(e); JCExpression e2 = makeTypeLiteral(type,typepos); JCExpression ee = treeutils.makeJmlBinary(epos,JmlTokenKind.SUBTYPE_OF,e1,e2); return ee; } // FIXME - review and document protected JCExpression makeSignalsOnly(JmlMethodClauseSignalsOnly clause) { JCExpression e = treeutils.makeBooleanLiteral(clause.pos,false); JCExpression id = factory.at(0).JmlSingleton(JmlTokenKind.BSEXCEPTION); for (JCExpression typetree: clause.list) { int pos = typetree.getStartPosition(); e = treeutils.makeBinary(pos, JCTree.Tag.OR, makeNNInstanceof(id, pos, typetree.type, pos), e); } return e; } // STATEMENT NODES // OK @Override public void visitJmlLabeledStatement(JmlLabeledStatement that) { VarMap map = currentMap.copy(); labelmaps.put(that.label,map); // if that.label is null, this is the premap super.visitJmlLabeledStatement(that); } // FIXME - REVIEW public void visitExec(JCExpressionStatement that) { // This includes assignments and stand-alone method invocations scan(that.expr); } // FIXME - needs review - al;ready converted to a BasicBlock assert? public void visitAssert(JCAssert that) { // This is a Java assert statement currentBlock.statements.add(comment(that)); JCExpression cond = convertExpr(that.cond); JCExpression detail = convertExpr(that.detail); //JCExpression detail = (that.detail); // FIXME - what to do with detail // FIXME - for now turn cond into a JML assertion // FIXME - need a label for the assert statement // FIXME - set line and source addAssert(Label.EXPLICIT_ASSERT, cond, that.cond.getStartPosition(), currentBlock.statements, that.cond.getStartPosition(),log.currentSourceFile(),that); } // FIXME - needs review public void visitApply(JCMethodInvocation that) { JCExpression now; JCExpression obj; MethodSymbol msym; Type.ForAll tfa = null; if (that.meth instanceof JCFieldAccess) { JCFieldAccess fa = (JCFieldAccess)that.meth; msym = (MethodSymbol)(fa.sym); if (msym == null || utils.isJMLStatic(msym)) obj = null; // msym is null for injected methods such as box and unbox else { obj = ( fa.selected ); // FIXME - should do better than converting to String //if (!fa.selected.type.toString().endsWith("JMLTYPE")) checkForNull(obj,fa.pos,trueLiteral,null); } if (msym != null && msym.type instanceof Type.ForAll) tfa = (Type.ForAll)msym.type; } else if (that.meth instanceof JCIdent) { // This is a defined function that will be passed on // continue on } else { // FIXME - not implemented log.warning("esc.not.implemented","BasicBlocker2.visitApply for " + that); msym = null; obj = null; result = trueLiteral; return; } // FIXME - what does this translation mean? // ListBuffer<JCExpression> newtypeargs = new ListBuffer<JCExpression>(); // for (JCExpression arg: that.typeargs) { // JCExpression n = trExpr(arg); // newtypeargs.append(n); // } ListBuffer<JCExpression> newargs = new ListBuffer<JCExpression>(); for (JCExpression arg: that.args) { scan(arg); newargs.append(result); } that.args = newargs.toList(); result = that; // pushTypeArgs(); // if (tfa != null) { // // tfa is the declaration of a parameterized method // // that is the actual call, which may not have explicit parameters // Iterator<Type> tv = tfa.tvars.iterator(); // Iterator<JCExpression> e = that.typeargs.iterator(); // if (e.hasNext()) { // while (tv.hasNext()) { // typeargs.put(tv.next().tsym,e.next().type); // } // } else { // log.noticeWriter.println("NOT IMPLEMENTED - parameterized method call with implicit type parameters"); // } // } // FIXME - concerned that the position here is not after the // positions of all of the arguments // if (inSpecExpression) { // result = insertSpecMethodCall(that.pos,msym,obj,that.typeargs,newargs.toList()); // } else { // result = insertMethodCall(that,msym,obj,that.getTypeArguments(),newargs.toList()); // typeargs ? FIXME // } //popTypeArgs(); // toLogicalForm.put(that,result); return; } // FIXME - review this //boolean extraEnv = false; public void visitJmlMethodInvocation(JmlMethodInvocation that) { if (that.token == JmlTokenKind.BSOLD || that.token == JmlTokenKind.BSPRE || that.token == JmlTokenKind.BSPAST) { VarMap savedMap = currentMap; try { if (that.args.size() == 1) { currentMap = premap; that.args.get(0).accept(this); } else { JCIdent label = (JCIdent)that.args.get(1); currentMap = labelmaps.get(label.name); if (currentMap == null) { // When method axioms are inserted they can appear before the label, // in which case control flow comes here. SO we are counting on proper // reporting of out of scope labels earlier. // This should have already been reported //log.error(label.pos,"jml.unknown.label",label.name.toString()); // Just use the current map currentMap = savedMap; } that.args.get(0).accept(this); that.args = com.sun.tools.javac.util.List.<JCExpression>of(that.args.get(0)); } that.token = JmlTokenKind.BSSAME; // A no-op // TODO - Review this } finally { currentMap = savedMap; } } else if (that.token == JmlTokenKind.SUBTYPE_OF || that.token == JmlTokenKind.JSUBTYPE_OF) { scan(that.args.get(0)); JCExpression lhs = result; scan(that.args.get(1)); JCExpression rhs = result; that.args = com.sun.tools.javac.util.List.<JCExpression>of(lhs,rhs); result = that; } else if (that.token == JmlTokenKind.BSNONNULLELEMENTS) { scan(that.args.get(0)); JCExpression arg = result; JCExpression argarrays = getArrayIdent(syms.objectType,that.pos); that.args = com.sun.tools.javac.util.List.<JCExpression>of(arg,argarrays); result = that; } else if (that.token == null || that.token == JmlTokenKind.BSTYPELC || that.token == JmlTokenKind.BSTYPEOF || that.token == JmlTokenKind.BSDISTINCT) { //super.visitApply(that); // See testBox - this comes from the implicitConversion - should it be a JCMethodInvocation instead? scan(that.typeargs); scan(that.meth); if (that.meth != null) that.meth = result; scanList(that.args); result = that; } else if (that.token == JmlTokenKind.BSELEMTYPE || that.token == JmlTokenKind.BSERASURE) { scan(that.typeargs); scan(that.meth); if (that.meth != null) that.meth = result; scanList(that.args); result = that; } else if (that.token == JmlTokenKind.BSSAME) { // In this context, BSSAME is a noop scanList(that.args); result = that; } else { log.error(that.pos, "esc.internal.error", "Did not expect this kind of Jml node in BasicBlocker2: " + that.token.internedName()); shouldNotBeCalled(that); } } // FIXME - REVIEW and document protected List<Type> allTypeArgs(Type type) { ListBuffer<Type> list = new ListBuffer<Type>(); allTypeArgs(list,type); return list.toList(); } // FIXME - REVIEW and document protected void allTypeArgs(ListBuffer<Type> list, Type type) { if (type == Type.noType) return; allTypeArgs(list,type.getEnclosingType()); list.appendList(type.getTypeArguments()); } // FIXME - review and document protected void havoc(JCExpression storeref) { if (storeref instanceof JCIdent) { newIdentIncarnation((JCIdent)storeref,storeref.pos); } else if (storeref instanceof JCFieldAccess) { JCFieldAccess fa = (JCFieldAccess)storeref; if (fa.name == null) { // Should not come here - as a store-ref of the form o.* // should have been expanded into the actual list of // non-wildcard locations log.error(fa.pos,"jml.internal","Unexpected wildcard store-ref in havoc call"); } else { if (utils.isJMLStatic(fa.sym)) { newIdentIncarnation((VarSymbol)fa.sym, storeref.pos); } else { int sp = fa.pos; scan(fa.selected); JCIdent oldfield = newIdentUse((VarSymbol)fa.sym,sp); if (isDefined.add(oldfield.name)) { if (utils.jmlverbose >= Utils.JMLDEBUG) log.getWriter(WriterKind.NOTICE).println("AddedFF " + oldfield.sym + " " + oldfield.name); addDeclaration(oldfield); } JCIdent newfield = newIdentIncarnation(oldfield,sp); if (isDefined.add(newfield.name)) { if (utils.jmlverbose >= Utils.JMLDEBUG) log.getWriter(WriterKind.NOTICE).println("AddedFF " + newfield.sym + " " + newfield.name); addDeclaration(newfield); } if (fa.selected != null) { JmlBBFieldAccess acc = new JmlBBFieldAccess(newfield,fa.selected); acc.pos = sp; acc.type = fa.type; JmlBBFieldAssignment expr = new JmlBBFieldAssignment(newfield,oldfield,fa.selected,acc); expr.pos = sp; expr.type = fa.type; addAssume(sp,Label.HAVOC,expr,currentBlock.statements); } } } } else if (storeref instanceof JmlStoreRefKeyword) { JmlTokenKind t = ((JmlStoreRefKeyword)storeref).token; if (t == JmlTokenKind.BSEVERYTHING || t == JmlTokenKind.BSNOTSPECIFIED) { for (VarSymbol vsym: currentMap.keySet()) { // Local variables are not affected by havoc \everything // The owner of a local symbol is a MethodSymbol // Also, final fields are not affected by havoc \everything if (vsym.owner instanceof ClassSymbol && (vsym.flags() & Flags.FINAL) != Flags.FINAL) { newIdentIncarnation(vsym, storeref.pos); } } // FIXME - symbols added after this havoc \everything will not have new incarnations??? } } else if (storeref instanceof JCArrayAccess) { // Array Access JCArrayAccess aa = (JCArrayAccess)storeref; int sp = storeref.pos; JCIdent arr = getArrayIdent(aa.type,aa.pos); JCExpression ex = aa.indexed; JCExpression index = aa.index; JCIdent nid = newArrayIncarnation(aa.type,sp); scan(ex); ex = result; scan(index); index = result; JmlBBArrayAccess rhs = new JmlBBArrayAccess(nid,ex,index); rhs.pos = sp; rhs.type = aa.type; JCExpression expr = new JmlBBArrayAssignment(nid,arr,ex,index,rhs); expr.pos = sp; expr.type = aa.type; treeutils.copyEndPosition(expr, aa); // FIXME - set line and source addAssume(sp,Label.HAVOC,expr,currentBlock.statements); //log.error(storeref.pos,"jml.internal","Ignoring unknown kind of storeref in havoc: " + storeref); } else if (storeref instanceof JmlStoreRefArrayRange) { // Array Access int sp = storeref.pos; JmlStoreRefArrayRange aa = (JmlStoreRefArrayRange)storeref; if (aa.lo == aa.hi && aa.lo != null) { // Single element JCIdent arr = getArrayIdent(aa.type,aa.pos); JCExpression ex = aa.expression; JCExpression index = aa.lo; JCIdent nid = newArrayIncarnation(aa.type,sp); scan(ex); ex = result; scan(index); index = result; Name nm = names.fromString("__BBtmp_" + (++unique)); JCVariableDecl decl = treeutils.makeVarDef(aa.type, nm, null, sp); JCIdent id = treeutils.makeIdent(sp,decl.sym); addDeclaration(id); JmlBBArrayAccess rhs = new JmlBBArrayAccess(nid,ex,index); rhs.pos = sp; rhs.type = aa.type; JCExpression expr = new JmlBBArrayAssignment(nid,arr,ex,index,id); expr.pos = sp; expr.type = aa.type; treeutils.copyEndPosition(expr, aa); // FIXME - set line and source addAssume(sp,Label.HAVOC,expr,currentBlock.statements); } else if (aa.lo == null && aa.hi == null) { // Entire array JCIdent arr = getArrayIdent(aa.type,aa.pos); JCExpression ex = aa.expression; JCIdent nid = newArrayIncarnation(aa.type,sp); scan(ex); ex = result; JCExpression expr = new JmlBBArrayAssignment(nid,arr,ex,null,null); expr.pos = sp; expr.type = aa.type; treeutils.copyEndPosition(expr, aa); // FIXME - set line and source addAssume(sp,Label.HAVOC,expr,currentBlock.statements); } else { // Range of array JCIdent arr = getArrayIdent(aa.type,aa.pos); JCExpression ex = aa.expression; JCIdent nid = newArrayIncarnation(aa.type,sp); scan(ex); ex = result; JCExpression expr = new JmlBBArrayAssignment(nid,arr,ex,null,null); expr.pos = sp; expr.type = aa.type; treeutils.copyEndPosition(expr, aa); int p = aa.pos; scan(aa.lo); JCExpression lo = result; JCVariableDecl decl = treeutils.makeVarDef(syms.intType, names.fromString("_JMLARANGE_" + (++unique)), null, p); JCIdent ind = treeutils.makeIdent(p, decl.sym); JCExpression comp = treeutils.makeBinary(p,JCTree.Tag.LT,treeutils.intltSymbol,ind,lo); JCExpression newelem = new JmlBBArrayAccess(nid,ex,ind); newelem.pos = p; newelem.type = aa.type; JCExpression oldelem = new JmlBBArrayAccess(arr,ex,ind); oldelem.pos = p; oldelem.type = aa.type; JCExpression eq = treeutils.makeEquality(p,newelem,oldelem); if (aa.hi != null) { scan(aa.hi); JCExpression hi = result; comp = treeutils.makeOr(p, comp, treeutils.makeBinary(p,JCTree.Tag.LT,treeutils.intltSymbol,hi,ind)); } // FIXME - set line and source expr = factory.at(p).JmlQuantifiedExpr(JmlTokenKind.BSFORALL,com.sun.tools.javac.util.List.<JCVariableDecl>of(decl),comp,eq); expr.setType(syms.booleanType); addAssume(sp,Label.HAVOC,expr,currentBlock.statements); //log.warning(storeref.pos,"jml.internal","Ignoring unknown kind of storeref in havoc: " + storeref); } } else { log.error(storeref.pos,"jml.internal","Ignoring unknown kind of storeref in havoc: " + storeref); } } // // FIXME - review and document // protected void havocEverything(JCExpression preCondition, int newpos) { // // FIXME - if the precondition is true, then we do not need to add the // // assumptions - we just need to call newIdentIncarnation to make a new // // value in the map. This would shorten the VC. How often is this // // really the case? Actually the preCondition does not need to be true, // // it just needs to encompass all allowed cases. // // // FIXME - check on special variables - should they/are they havoced? // // this // // terminationVar // // exceptionVar // // resultVar // // exception // // others? // // // Change everything in the current map // for (VarSymbol vsym : currentMap.keySet()) { // if (vsym.owner == null || vsym.owner.type.tag != TypeTag.CLASS) { // continue; // } // JCIdent oldid = newIdentUse(vsym,newpos); // JCIdent newid = newIdentIncarnation(vsym,newpos); // JCExpression e = factory.at(newpos).Conditional(preCondition,newid,oldid); // e.type = vsym.type; // e = treeutils.makeEquality(newpos,newid,e); // addAssume(newpos,Label.HAVOC,e,currentBlock.statements); // } // //currentMap.everythingSAversion = newpos; // FIXME - this now applies to every not-yet-referenced variable, independent of the preCondition // } /** This method is not called for top-level classes, since the BasicBlocker is invoked * directly for each method. */ // FIXME - what about for anonymous classes or local classes or nested classes @Override public void visitClassDef(JCClassDecl that) { // Nested classes are found in JmlEsc. We get to this point if there is a local // class declaration within method body. System.out.println("GOT HERE!"); JmlEsc.instance(context).visitClassDef(that); } // FIXME - review this, and compare to the above @Override public void visitJmlClassDecl(JmlClassDecl that) { // Nested classes are found in JmlEsc. We get to this point if there is a local // class declaration within method body. System.out.println("GOT HERE TOO!"); JmlEsc.instance(context).visitClassDef(that); } // OK @Override public void visitJmlStatementExpr(JmlStatementExpr that) { if (that.token == JmlTokenKind.COMMENT) { // Comments are included in the BB program without rewriting // This is essential to how counterexample path construction works currentBlock.statements.add(that); } else if (that.token == JmlTokenKind.ASSUME || that.token == JmlTokenKind.ASSERT) { JmlStatementExpr st = M.at(that.pos()).JmlExpressionStatement(that.token,that.label,convertExpr(that.expression)); st.id = that.id; st.optionalExpression = convertExpr(that.optionalExpression); st.associatedPos = that.associatedPos; st.associatedSource = that.associatedSource; st.description = that.description; st.source = that.source; st.type = that.type; copyEndPosition(st,that); currentBlock.statements.add(st); } else { log.error(that.pos,"esc.internal.error","Unknown token in BasicBlocker2.visitJmlStatementExpr: " + that.token.internedName()); } } // OK @Override public void visitJmlStatementHavoc(JmlStatementHavoc that) { for (JCExpression item : that.storerefs) { havoc(item); } } protected boolean isFinal(Symbol sym) { return (sym.flags() & Flags.FINAL) != 0; } // Visit methods for Expressions for the most part just use the super class's // visit methods. These just call visitors on each subexpression. // Everything is transformed in place. // There are a few nodes that get special treatment: // JCIdent - the name is overwritten with the single-assignment name (note that // the name will be out of synch with the symbol) // \old and \pre expressions - these need to find the correct scope and translate // JCIdent nodes within their scopes using the correct single-assignment names // OK @Override public void visitIdent(JCIdent that) { if (that.sym instanceof Symbol.VarSymbol){ Symbol.VarSymbol vsym = (Symbol.VarSymbol)that.sym; if (localVars.contains(vsym)) { // no change to local vars (e.g. quantifier and let decls) } else { if (vsym.toString().startsWith("aux")) Utils.stop(); that.name = currentMap.getCurrentName(vsym); if (isDefined.add(that.name)) { if (utils.jmlverbose >= Utils.JMLDEBUG) log.getWriter(WriterKind.NOTICE).println("Added " + vsym + " " + that.name); addDeclaration(that); } } } else if (that.sym == null) { // Temporary variables that are introduced by decomposing expressions do not have associated symbols // They are also only initialized once and only used locally, so we do not track them for DSA purposes // Nor do we need to adjust their names. // Also, they will have been declared in a declaration, at which time addDeclaration will have been called. // Just skip. } else if (that.sym instanceof Symbol.TypeSymbol) { // Includes class, type parameter, package // Type symbols also are never assigned, so we don't need to // track their names. So these are left alone also. } else { log.error(that.pos,"jml.internal","THIS KIND OF IDENT IS NOT HANDLED: " + that + " " + that.sym.getClass()); } result = that; } // OK public void visitLiteral(JCLiteral that) { result = that; } @Override public void visitSelect(JCFieldAccess that) { if (!(that.sym instanceof Symbol.VarSymbol)) { result = that; return; } // This is a qualified type name VarSymbol vsym = (Symbol.VarSymbol)that.sym; Name n; if (isFinal(that.sym) && (!methodDecl.sym.isConstructor() || utils.isJMLStatic(that.sym))) { n = labelmaps.get(null).getCurrentName(vsym); } else { n = currentMap.getCurrentName((Symbol.VarSymbol)that.sym); } that.name = n; if (utils.isJMLStatic(that.sym)) { JCIdent id = treeutils.makeIdent(that.pos,that.sym); id.name = n; if (isDefined.add(n)) { addDeclaration(id); } result = id; } else { if (isDefined.add(n)) { JCIdent id = treeutils.makeIdent(that.pos,that.sym); id.name = n; addDeclaration(id); } scan(that.selected); result = that; } } public void visitIndexed(JCArrayAccess that) { scan(that.indexed); JCExpression indexed = result; scan(that.index); JCExpression index = result; JCIdent arr = getArrayIdent(that.type,that.pos); if (that instanceof JmlBBArrayAccess) { that.indexed = indexed; that.index = index; ((JmlBBArrayAccess)that).arraysId = arr; result = that; } else { log.warning(that,"jml.internal","Did not expect a JCArrayAccess node in BasicBlocker2.visitIndexed"); result = new JmlBBArrayAccess(arr,indexed,index); } } @Override public void visitAssignop(JCAssignOp that) { // These should be desugared to assignments. shouldNotBeCalled(that); } // FIXME - review public void visitAssign(JCAssign that) { //scan(that.lhs); JCExpression left = that.lhs; JCExpression right = convertExpr(that.rhs); result = doAssignment(that.type,left,right,that.pos,that); bimap.put(left, result); bimap.putf(that, result); copyEndPosition(result,that); } // // FIXME - embedded assignments to array elements are not implemented; no warning either // FIXME - is all implicit casting handled // Note that only the right expression is translated. protected JCExpression doAssignment(Type restype, JCExpression left, JCExpression right, int pos, JCExpression statement) { int sp = left.getPreferredPosition(); JCStatement newStatement; JCExpression newExpr; if (left instanceof JCIdent) { JCIdent id = (JCIdent)left; JCIdent newid = newIdentIncarnation(id,sp); //currentBlock.statements.add(treeutils.makeVarDef(newid.type, newid.name, id.sym.owner, pos)); JCBinary expr = treeutils.makeEquality(pos,newid,right); //copyEndPosition(expr,right); newStatement = addAssume(sp,Label.ASSIGNMENT,expr,currentBlock.statements); newExpr = newid; } else if (left instanceof JCArrayAccess) { Type ctype = left.type; JCIdent arr = getArrayIdent(ctype,right.pos); JCExpression ex = ((JCArrayAccess)left).indexed; JCExpression index = ((JCArrayAccess)left).index; JCIdent nid = newArrayIncarnation(right.type,sp); scan(ex); ex = result; scan(index); index = result; scan(right); right = result; //JCExpression rhs = makeStore(ex,index,right); JCExpression expr = new JmlBBArrayAssignment(nid,arr,ex,index,right); // FIXME - implicit conversion? expr.pos = pos; expr.type = restype; treeutils.copyEndPosition(expr, right); // FIXME - set line and source newStatement = addAssume(sp,Label.ASSIGNMENT,expr,currentBlock.statements); newExpr = left; } else if (left instanceof JCFieldAccess) { VarSymbol sym = (VarSymbol)selectorSym(left); if (utils.isJMLStatic(sym)) { JCIdent id = newIdentUse(sym,sp); JCIdent newid = newIdentIncarnation(id,sp); // currentBlock.statements.add(treeutils.makeVarDef(newid.type, newid.name, id.sym.owner, pos)); JCBinary expr = treeutils.makeEquality(pos,newid,right); //copyEndPosition(expr,right); newStatement = addAssume(statement.getStartPosition(),Label.ASSIGNMENT,expr,currentBlock.statements); newExpr = newid; } else { JCFieldAccess fa = (JCFieldAccess)left; scan(fa.selected); JCIdent oldfield = newIdentUse((VarSymbol)fa.sym,sp); if (isDefined.add(oldfield.name)) { addDeclaration(oldfield); } JCIdent newfield = newIdentIncarnation(oldfield,sp); if (isDefined.add(newfield.name)) { addDeclaration(newfield); } JCExpression expr = new JmlBBFieldAssignment(newfield,oldfield,fa.selected,right); expr.pos = pos; expr.type = restype; treeutils.copyEndPosition(expr, right); // FIXME - set line and source newStatement = addAssume(sp,Label.ASSIGNMENT,expr,currentBlock.statements); newIdentIncarnation(heapVar,pos); newExpr = left; } } else { log.error("jml.internal","Unexpected case in BasicBlocker2.doAssignment: " + left.getClass() + " " + left); return null; } pathmap.put(statement,newStatement); return newExpr; } protected Symbol selectorSym(JCTree tree) { if (tree instanceof JCIdent) return ((JCIdent)tree).sym; if (tree instanceof JCFieldAccess) return ((JCFieldAccess)tree).sym; log.error("jml.internal","Unexpected case in selectorSym: " + tree.getClass() + " " + tree); return null; } // OK -= except FIXME - review newIdentIncarnation public void visitVarDef(JCVariableDecl that) { currentBlock.statements.add(comment(that)); JCIdent lhs = newIdentIncarnation(that.sym,that.getPreferredPosition()); isDefined.add(lhs.name); if (utils.jmlverbose >= Utils.JMLDEBUG) log.getWriter(WriterKind.NOTICE).println("Added " + lhs.sym + " " + lhs.name); if (that.init != null) { // Create and store the new lhs incarnation before translating the // initializer because the initializer is in the scope of the newly // declared variable. Actually if there is such a situation, it // will likely generate an error about use of an uninitialized variable. scan(that.init); JCBinary expr = treeutils.makeBinary(that.pos,JCBinary.Tag.EQ,lhs,that.init); addAssume(that.getStartPosition(),Label.ASSIGNMENT,expr,currentBlock.statements); } } // public void visitJmlVariableDecl(JmlVariableDecl that) { // JCIdent id; // if (that.sym == null || that.sym.owner == null) { //// if (that.init != null) { //// scan(that.init); //// that.init = result; //// } // Name n = encodedName(that.sym,0L); // that.name = n; // id = factory.at(0).Ident(n); // id.sym = that.sym; // id.type = that.type; // if (isDefined.add(n)) { // addDeclaration(id); // } // // currentMap.putSAVersion(that.sym,n,0); // //currentBlock.statements.add(that); // } else { // // FIXME - why not make a declaration? // id = newIdentIncarnation(that.sym,that.getPreferredPosition()); // isDefined.add(id.name); // that.name = id.name; // } // scan(that.ident); // FIXME - is this needed since we already set the encodedname // if (that.init != null) { // scan(that.init); // that.init = result; // JCBinary expr = treeutils.makeBinary(that.pos,JCBinary.EQ, that.ident != null ? that.ident : id,that.init); // addAssume(that.getStartPosition(),Label.ASSIGNMENT,expr,currentBlock.statements); // } // } public void visitJmlVariableDecl(JmlVariableDecl that) { if (that.sym == null || that.sym.owner == null) { if (that.sym.toString().startsWith("aux")) Utils.stop(); if (that.init != null) { scan(that.init); that.init = result; } Name n = encodedName(that.sym,0L); that.name = n; if (isDefined.add(n)) { // JCIdent id = factory.at(0).Ident(n); // id.sym = that.sym; // id.type = that.type; // addDeclaration(id); } currentMap.putSAVersion(that.sym,n,0); currentBlock.statements.add(that); scan(that.ident); // FIXME - is this needed since we already set the encodedname } else { // FIXME - why not make a declaration? JCIdent lhs = newIdentIncarnation(that.sym,that.getPreferredPosition()); isDefined.add(lhs.name); that.name = lhs.name; scan(that.ident); // FIXME - is this needed since we already set the encodedname if (that.init != null) { scan(that.init); that.init = result; JCBinary expr = treeutils.makeBinary(that.pos,JCBinary.Tag.EQ, that.ident != null ? that.ident : lhs,that.init); addAssume(that.getStartPosition(),Label.ASSIGNMENT,expr,currentBlock.statements); } } } // OK @Override public void visitSynchronized(JCSynchronized that) { super.visitSynchronized(that); } public void visitWildcard(JCWildcard that) { notImpl(that); } public void visitTypeBoundKind(TypeBoundKind that) { notImpl(that); } public void visitAnnotation(JCAnnotation that) { notImpl(that); } public void visitModifiers(JCModifiers that) { notImpl(that); } public void visitErroneous(JCErroneous that) { notImpl(that); } public void visitLetExpr(LetExpr that) { // FIXME - do we need to add these so they are not rewritten? for (JCVariableDecl d: that.defs) { scan(d.init); } scan(that.expr); result = that; } @Override public void visitTypeIdent(JCPrimitiveTypeTree that) { // A primitive type result = that; } @Override public void visitTypeArray(JCArrayTypeTree that) { // An array type (e.g., A[] ) result = that; } @Override public void visitTypeApply(JCTypeApply that) { // This is the application of a generic type to its parameters // e.g., List<Integer> or List<T> // Just skip result = that; } @Override public void visitTypeParameter(JCTypeParameter that) { // This is a parameter of a generic type definition // e.g., T, or T extends X, or T super Y notImpl(that); } // Note: all control flow statements are handled by the parent class: // Block, Break, Case, Catch, Continue, loops, If // Switch, Try, Throw, Return // FIXME _ implement @Override public void visitJmlSetComprehension(JmlSetComprehension that) { notImpl(that); } @Override public void visitJmlStoreRefListExpression(JmlStoreRefListExpression that) { notImpl(that); } @Override public void visitJmlChoose(JmlChoose that) { notImpl(that); } @Override public void visitJmlMethodSig(JmlMethodSig that) { notImpl(that); } @Override public void visitJmlMethodClauseCallable(JmlMethodClauseCallable that) { notImpl(that); } @Override public void visitJmlModelProgramStatement(JmlModelProgramStatement that) { notImpl(that); } @Override public void visitJmlGroupName(JmlGroupName that) { notImpl(that); } @Override public void visitJmlTypeClauseIn(JmlTypeClauseIn that) { notImpl(that); } @Override public void visitJmlTypeClauseMaps(JmlTypeClauseMaps that) { notImpl(that); } @Override public void visitJmlTypeClauseExpr(JmlTypeClauseExpr that) { notImpl(that); } @Override public void visitJmlTypeClauseDecl(JmlTypeClauseDecl that) { notImpl(that); } @Override public void visitJmlTypeClauseInitializer(JmlTypeClauseInitializer that) { notImpl(that); } @Override public void visitJmlTypeClauseConstraint(JmlTypeClauseConstraint that) { notImpl(that); } @Override public void visitJmlTypeClauseRepresents(JmlTypeClauseRepresents that) { notImpl(that); } @Override public void visitJmlTypeClauseConditional(JmlTypeClauseConditional that) { notImpl(that); } @Override public void visitJmlTypeClauseMonitorsFor(JmlTypeClauseMonitorsFor that) { notImpl(that); } @Override public void visitJmlMethodClauseGroup(JmlMethodClauseGroup that) { notImpl(that); } @Override public void visitJmlMethodClauseDecl(JmlMethodClauseDecl that) { notImpl(that); } @Override public void visitJmlMethodClauseExpr(JmlMethodClauseExpr that) { notImpl(that); } @Override public void visitJmlMethodClauseConditional(JmlMethodClauseConditional that) { notImpl(that); } @Override public void visitJmlMethodClauseSignals(JmlMethodClauseSignals that) { notImpl(that); } @Override public void visitJmlMethodClauseSigOnly(JmlMethodClauseSignalsOnly that) { notImpl(that); } @Override public void visitJmlMethodClauseStoreRef(JmlMethodClauseStoreRef that) { notImpl(that); } @Override public void visitJmlSpecificationCase(JmlSpecificationCase that){ notImpl(that); } @Override public void visitJmlMethodSpecs(JmlMethodSpecs that) { notImpl(that); } @Override public void visitJmlPrimitiveTypeTree(JmlPrimitiveTypeTree that){ notImpl(that); } @Override public void visitJmlStoreRefKeyword(JmlStoreRefKeyword that) { notImpl(that); } @Override public void visitJmlStoreRefArrayRange(JmlStoreRefArrayRange that){ notImpl(that); } // These should all be translated away prior to calling the basic blocker @Override public void visitJmlBinary(JmlBinary that) { shouldNotBeCalled(that); } @Override public void visitJmlLblExpression(JmlLblExpression that) { shouldNotBeCalled(that); } // These should not be implemented @Override public void visitTopLevel(JCCompilationUnit that) { shouldNotBeCalled(that); } @Override public void visitImport(JCImport that) { shouldNotBeCalled(that); } @Override public void visitJmlCompilationUnit(JmlCompilationUnit that) { shouldNotBeCalled(that); } @Override public void visitJmlImport(JmlImport that) { shouldNotBeCalled(that); } @Override public void visitJmlMethodDecl(JmlMethodDecl that) { shouldNotBeCalled(that); } @Override public void visitJmlStatement(JmlStatement that) { shouldNotBeCalled(that); } @Override public void visitJmlStatementSpec(JmlStatementSpec that) { shouldNotBeCalled(that); } @Override public void visitJmlStatementDecls(JmlStatementDecls that) { shouldNotBeCalled(that); } @Override public void visitMethodDef(JCMethodDecl that) { shouldNotBeCalled(that); } final protected List<Symbol> localVars = new LinkedList<Symbol>(); @Override public void visitJmlQuantifiedExpr(JmlQuantifiedExpr that) { for (JCVariableDecl d: that.decls) { localVars.add(d.sym); } try { that.range = convertExpr(that.range); that.value = convertExpr(that.value); result = that; } finally { for (JCVariableDecl d: that.decls) { localVars.remove(d.sym); } } } // OK @Override public void visitBinary(JCBinary that) { that.lhs = convertExpr(that.lhs); that.rhs = convertExpr(that.rhs); result = that; } // OK @Override public void visitUnary(JCUnary that) { that.arg = convertExpr(that.arg); result = that; } // OK @Override public void visitParens(JCParens that) { that.expr = convertExpr(that.expr); result = that; } // OK @Override public void visitConditional(JCConditional that) { that.cond = convertExpr(that.cond); that.truepart = convertExpr(that.truepart); that.falsepart = convertExpr(that.falsepart); result = that; } // FIXME - review @Override public void visitJmlSingleton(JmlSingleton that) { notImpl(that); } // OK // FIXME - does this expression type appear? @Override public void visitTypeTest(JCInstanceOf that) { that.expr = convertExpr(that.expr); scan(that.clazz); // FIXME - if the type tree is rewritten, we are not capturing the result result = that; } // OK // FIXME - does this expression type appear? in REF case it should be a noop @Override public void visitTypeCast(JCTypeCast that) { //scan(that.clazz); // FIXME - if the type tree is rewritten, we are not capturing the result that.expr = convertExpr(that.expr); result = M.TypeCast(that.clazz, result); result.type = that.type; } @Override public void visitNewClass(JCNewClass that) { // This AST node should be transformed away by JmlAssertionAdder shouldNotBeCalled(that); } @Override public void visitNewArray(JCNewArray that) { // This AST node should be transformed away by JmlAssertionAdder shouldNotBeCalled(that); } // Do not need to override these methods // @Override public void visitSkip(JCSkip that) { super.visitSkip(that); } @Override public void visitJmlStatementLoop(JmlStatementLoop that) { shouldNotBeCalled(that); // These are the specs for loops - they are handled in the loop visitors } // FIXME - what about AssignOp, NewClass, NewArray /** This class implements a map from variable (as a Symbol) to a unique name * as used in Single-Assignment form. At any given point in the program there is * a current mapping from symbols to names, giving the name that holds the value * for the symbol at that location. When a variable is assigned a new value, it * gets a new current Single-Assignment name and the map is updated. Copies of * these maps are saved with each block, representing the state of the map at the * end of the block. * <P> * FIXME - explain this better, and the everythingIncarnation * Each Symbol also has an incarnation number. The number is incremented as new * incarnations happen. The number is used to form the variable's SA name. * <P> * The everythingIncarnation value is used as the default incarnation number * for symbols that have not yet been used. This is 0 in the pre-state. It * is needed, for example, for the following circumstance. Some method call * havocs everything, then a class field is used (without having been used * before and therefore not having an entry in the name maps). That class field * must use a SA Version number different than 0. If it is subsequently * used in an \old expression, it will use an SA Version number of 0 in that * circumstance and must be distinguished from the use after everything has * been havoced. */ // The class is intentionally not static - so it can use encodedName public class VarMap { // The maps allow VarSymbol or TypeSymbol (for TypeVar) private Map<VarSymbol,Long> mapSAVersion = new HashMap<VarSymbol,Long>(); private Map<TypeSymbol,Long> maptypeSAVersion = new HashMap<TypeSymbol,Long>(); private Map<Symbol,Name> mapname = new HashMap<Symbol,Name>(); /** Returns a copy of the map */ public VarMap copy() { VarMap v = new VarMap(); v.mapSAVersion.putAll(this.mapSAVersion); v.maptypeSAVersion.putAll(this.maptypeSAVersion); v.mapname.putAll(this.mapname); return v; } /** Returns the name for a variable symbol as stored in this map */ public /*@Nullable*/ Name getName(VarSymbol vsym) { Name s = mapname.get(vsym); return s; } /** Returns the name for a variable symbol as stored in this map, creating (and * storing) one if it is not present. */ public /*@NonNull*/ Name getCurrentName(VarSymbol vsym) { Name s = mapname.get(vsym); if (s == null) { // If there was no mapping at all, we add the name to // all existing maps, with an incarnation number of 0. // This makes sure that any maps at labels have a definition // of the variable. // FIXME - this does not handle a havoc between labels, s = encodedName(vsym,vsym.pos); for (VarMap map: blockmaps.values()) { if (map.mapname.get(vsym) == null) map.putSAVersion(vsym,s,0L); } for (VarMap map: labelmaps.values()) { if (map.mapname.get(vsym) == null) map.putSAVersion(vsym,s,0L); } if (isDefined.add(s)) { JCIdent idd = treeutils.makeIdent(vsym.pos,s,vsym); addDeclaration(idd); } putSAVersion(vsym,s,unique); } return s; } /** Returns the name for a type symbol as stored in this map; returns null * if no name is stored */ public /*@ Nullable */ Name getName(TypeSymbol vsym) { Name s = mapname.get(vsym); return s; } /** Returns the name for a type symbol as stored in this map, creating (and * storing) one if it is not present. */ public /*@NonNull*/ Name getCurrentName(TypeSymbol vsym) { Name s = mapname.get(vsym); if (s == null) { s = encodedTypeName(vsym,0); putSAVersion(vsym,s); } return s; } /** Returns the incarnation number (single-assignment version * number) for the symbol */ public Long getSAVersionNum(VarSymbol vsym) { Long i = mapSAVersion.get(vsym); if (i == null) { Name n = encodedName(vsym,0L); for (VarMap map: blockmaps.values()) { map.putSAVersion(vsym,n,0L); } for (VarMap map: labelmaps.values()) { map.putSAVersion(vsym,n,0L); } if (isDefined.add(n)) { JCIdent id = treeutils.makeIdent(vsym.pos,n,vsym); addDeclaration(id); } i = 0L; } return i; } /** Returns the incarnation number (single-assignment version * number) for the type symbol */ public Long getSAVersionNum(TypeSymbol vsym) { Long i = maptypeSAVersion.get(vsym); if (i == null) { maptypeSAVersion.put(vsym,(i=0L)); } return i; } /** Stores a new SA version of a symbol, with a custom name */ public void putSAVersion(VarSymbol vsym, Name s, long version) { mapSAVersion.put(vsym,version); mapname.put(vsym,s); } /** Stores a new SA version of a symbol */ public Name putSAVersion(VarSymbol vsym, long version) { Name s = encodedName(vsym,version); mapSAVersion.put(vsym,version); mapname.put(vsym,s); return s; } /** Stores a new SA version of a type symbol */ public void putSAVersion(TypeSymbol vsym, Name s) { maptypeSAVersion.put(vsym,0L); mapname.put(vsym,s); } /** Adds everything in the argument map into the receiver's map */ public void putAll(VarMap m) { mapSAVersion.putAll(m.mapSAVersion); maptypeSAVersion.putAll(m.maptypeSAVersion); mapname.putAll(m.mapname); } /** Removes a symbol from the map, as when it goes out of scope or * when a temporary variable is no longer needed. */ public Long remove(Symbol v) { mapname.remove(v); return mapSAVersion.remove(v); } /** Returns the Set of all variable Symbols that are in the map; * note that variables that are in scope but have not been used * will not necessarily be present in the map. */ public Set<VarSymbol> keySet() { return mapSAVersion.keySet(); } /** Returns a debug representation of the map */ public String toString() { StringBuilder s = new StringBuilder(); s.append("["); Iterator<Map.Entry<VarSymbol,Long>> entries = mapSAVersion.entrySet().iterator(); while (entries.hasNext()) { Map.Entry<VarSymbol,Long> entry = entries.next(); s.append(entry.getKey()); s.append("="); s.append(entry.getValue()); s.append(","); } Iterator<Map.Entry<TypeSymbol,Long>> entriest = maptypeSAVersion.entrySet().iterator(); while (entries.hasNext()) { Map.Entry<TypeSymbol,Long> entry = entriest.next(); s.append(entry.getKey()); s.append("="); s.append(entry.getValue()); s.append(","); } s.append("]"); return s.toString(); } } static class GetSymbols extends JmlTreeScanner { boolean noMethods = false; public static Set<VarSymbol> collectSymbols(JCBlock method, JCClassDecl classDecl) { GetSymbols gs = new GetSymbols(); gs.noMethods = false; method.accept(gs); gs.noMethods = true; if (classDecl != null) classDecl.accept(gs); // classDecl can be null when we have not translated the whole class, just the method - e.g. doEsc from the API (FIXME) return gs.syms; } private Set<VarSymbol> syms = new HashSet<VarSymbol>(); public GetSymbols() { scanMode = JmlTreeScanner.AST_SPEC_MODE; } public void visitClassDef(JCClassDecl that) { scan(that.mods); scan(that.typarams); scan(that.extending); scan(that.implementing); for (JCTree def: that.defs) { if (!noMethods || !(def instanceof JCMethodDecl)) scan(def); } } public void visitIdent(JCIdent that) { if (that.sym instanceof VarSymbol && that.sym.owner instanceof ClassSymbol) syms.add((VarSymbol)that.sym); } public void visitSelect(JCFieldAccess that) { if (that.sym instanceof VarSymbol && that.sym.owner instanceof ClassSymbol && !that.sym.toString().equals("length")) syms.add((VarSymbol)that.sym); } } }