/* * This file is part of the OpenJML project. * Author: David R. Cok */ package org.jmlspecs.openjml.esc; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.jmlspecs.annotation.NonNull; import org.jmlspecs.annotation.Nullable; import org.jmlspecs.openjml.*; import org.jmlspecs.openjml.JmlTree.JmlBinary; import org.jmlspecs.openjml.JmlTree.JmlBlock; 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.JmlDoWhileLoop; import org.jmlspecs.openjml.JmlTree.JmlEnhancedForLoop; import org.jmlspecs.openjml.JmlTree.JmlForLoop; 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.JmlTree.JmlWhileLoop; import org.jmlspecs.openjml.esc.BasicProgramParent.BlockParent; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Symbol.VarSymbol; import com.sun.tools.javac.code.Symtab; import com.sun.tools.javac.code.TypeTag; 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.JCBreak; import com.sun.tools.javac.tree.JCTree.JCCase; import com.sun.tools.javac.tree.JCTree.JCCatch; 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.JCContinue; import com.sun.tools.javac.tree.JCTree.JCDoWhileLoop; import com.sun.tools.javac.tree.JCTree.JCEnhancedForLoop; 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.JCForLoop; import com.sun.tools.javac.tree.JCTree.JCIdent; import com.sun.tools.javac.tree.JCTree.JCIf; 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.JCReturn; import com.sun.tools.javac.tree.JCTree.JCStatement; import com.sun.tools.javac.tree.JCTree.JCSwitch; import com.sun.tools.javac.tree.JCTree.JCSynchronized; import com.sun.tools.javac.tree.JCTree.JCThrow; import com.sun.tools.javac.tree.JCTree.JCTry; 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.JCWhileLoop; import com.sun.tools.javac.tree.JCTree.JCWildcard; import com.sun.tools.javac.tree.JCTree.LetExpr; import com.sun.tools.javac.tree.JCTree.Tag; import com.sun.tools.javac.tree.JCTree.TypeBoundKind; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; import com.sun.tools.javac.util.ListBuffer; import com.sun.tools.javac.util.Log.WriterKind; import com.sun.tools.javac.util.Log; import com.sun.tools.javac.util.Name; import com.sun.tools.javac.util.Names; import com.sun.tools.javac.util.Position; /** This class is a base class for converting a Java AST into a basic block * program. The methods here take care of converting control flow * into basic blocks, including adding assumptions at the beginning of blocks * to control feasible paths. For example, an if(b) ... statement is replaced by * two basic blocks, one for the then branch, beginning with 'assume b', and one * for the else block, beginning with 'assume !b'. * Derived classes are expected to handle tasks such as * converting to single-assignment form. * <P> * These Java statements are handled: if, switch, loops (for, while, do, foreach), * return, throw, break continue. * <P> * Expression ASTs are used as is (without copying), so there may be some * structure sharing in the resulting basic block program. * <P> * The class expects a new object to be instantiated for each method to be * converted to a Basic Block program, including for methods in local * and anonymous classes. * * @typeparam T basic block type * @typeparam P basic block program type * @author David Cok */ abstract public class BasicBlockerParent<T extends BlockParent<T>, P extends BasicProgramParent<T>> extends JmlTreeScanner { // THE FOLLOWING ARE ALL FIXED STRINGS //----------------------------------------------------------------- // Names for various basic blocks /** The prefix used for names of blocks */ public static final @NonNull String blockPrefix = "BL_"; //$NON-NLS-1$ /** Standard name for the block that starts the body */ public static final @NonNull String BODY_BLOCK_NAME = "bodyBegin"; //$NON-NLS-1$ /** Standard name for the starting block of the program (just has the preconditions) */ public static final @NonNull String START_BLOCK_NAME = "Start"; //$NON-NLS-1$ /** Suffix for the name of a basic block for a finally block */ public static final String FINALLY = "_finally"; //$NON-NLS-1$ /** Suffix for the name of a basic block which is the target for return and * throw statements in the try statement body */ public static final String TRYTARGET = "tryTarget"; //$NON-NLS-1$ /** Suffix for the name of a basic block which is the no exception path * upon exit from the try statement body */ public static final String TRYNOEXCEPTION = "noException"; //$NON-NLS-1$ /** Suffix for the name of a basic block which is the normal exit path * after a finally block */ public static final String TRYFINALLYNORMAL = "finallyNormal"; //$NON-NLS-1$ /** Suffix for the name of a basic block which is the exit path after a * finally block when there is an outstanding return or exception */ public static final String TRYFINALLYEXIT = "finallyExit"; //$NON-NLS-1$ /** Suffix for the name of a basic block which holds the body of a catch clause */ public static final String CATCH = "catch"; //$NON-NLS-1$ /** Suffix for the name of a basic block representing the case where an * exception is not caught by any catch clause */ public static final String NOCATCH = "nocatch"; //$NON-NLS-1$ /** Suffix for the name of a basic block for a finally block */ public static final String AFTERTRY = "_AfterTry"; //$NON-NLS-1$ /** Suffix for the name of a basic block that comes after a switch statement */ public static final String AFTERLABEL = "_AfterLabel"; //$NON-NLS-1$ /** Suffix for the name of a basic block that comes after a switch statement */ public static final String AFTERSWITCH = "_AfterSwitch"; //$NON-NLS-1$ /** Suffix for the name of a basic block holding the body of a loop */ public static final String LOOPBODY = "_LoopBody"; //$NON-NLS-1$ /** Suffix for the name of a basic block holding the code after a loop */ public static final String LOOPAFTER = "_LoopAfter"; //$NON-NLS-1$ /** Suffix for the name of a basic block holding the code after a loop */ public static final String LOOPAFTERDO = "_LoopAfterDo"; //$NON-NLS-1$ /** Suffix for the name of a basic block holding the code where continue statements go */ public static final String LOOPCONTINUE = "_LoopContinue"; //$NON-NLS-1$ /** Suffix for the name of a basic block holding the code where break statements go */ public static final String LOOPBREAK = "_LoopBreak"; //$NON-NLS-1$ /** Suffix for the name of a basic block to which control transfers if the loop condition fails */ public static final String LOOPEND = "_LoopEnd"; //$NON-NLS-1$ /** Suffix for the name of a basic block for the then branch of an if statement */ public static final String THENSUFFIX = "_then"; //$NON-NLS-1$ /** Suffix for the name of a basic block for the else branch of an if statement */ public static final String ELSESUFFIX = "_else"; //$NON-NLS-1$ /** Suffix for the name of a basic block after an if statement */ public static final String AFTERIF = "_afterIf"; //$NON-NLS-1$ /** Suffix for the name of a basic block after a return statement */ public static final String RETURN = "_return"; //$NON-NLS-1$ /** Suffix for the name of a basic block after a throw statement */ public static final String THROW = "_throw"; //$NON-NLS-1$ /** Suffix for the name of a basic block for the body of a switch case */ public static final String CASEBODY = "_caseBody_"; //$NON-NLS-1$ /** Suffix for the name of a basic block for the case expression test */ public static final String CASETEST = "_caseTest"; //$NON-NLS-1$ /** Suffix for the name of a basic block for the switch default case */ public static final String CASEDEFAULT = "_switchDefault"; //$NON-NLS-1$ /** Part of the name for switch expressions */ public static final String SWITCHEXPR = "_switchExpression_"; //$NON-NLS-1$ // 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 // Some are here for the benefit of derived classes and not used in this // class directly /** The compilation context */ @NonNull final protected Context context; /** The log to which to send error, warning and notice messages */ @NonNull final protected Log log; /** The Names table from the compilation context, initialized in the constructor */ @NonNull final protected Names names; /** The specifications database for this compilation context, initialized in the constructor */ @NonNull final protected JmlSpecs specs; /** The symbol table from the compilation context, initialized in the constructor */ @NonNull final protected Symtab syms; /** The JmlTreeUtils object, holding a bunch of tree-making utilities */ @NonNull final protected JmlTreeUtils treeutils; /** The factory used to create AST nodes, initialized in the constructor */ @NonNull final protected JmlTree.Maker M; // The following fields depend on the method being converted but // are otherwise fixed for the life of the object /** The declaration of the method under conversion */ protected JmlMethodDecl methodDecl; /** True if the method being converted is a constructor */ protected boolean isConstructor; /** True if the method being converted is static */ protected boolean isStatic; /** The program being constructed */ protected P program = null; /** The symbol used to hold the int location of the terminating statement. */ protected VarSymbol terminationSym; /** The symbol used to designate the exception thrown by the method. */ protected VarSymbol exceptionSym; // 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. Other such fields are declared close // to their points of use in the remainder of this file. /** A map of names to blocks */ final protected java.util.Map<String,T> blockLookup = new java.util.HashMap<String,T>(); /** A variable to hold the block currently being processed */ protected T currentBlock; /** Ordered list of statements from the current block that are yet to be processed into basic program form */ protected List<JCStatement> remainingStatements; /** A counter used to make sure that block names are unique */ protected int blockCount = 0; /** Holds the result of any of the visit methods that produce JCExpressions, since the visitor * template used here does not have a return value. [We could have used the templated visitor, * but other methods do not need to return anything, we don't need the additional parameter, * and that visitor is complicated by the use of interfaces for the formal parameters.] */ protected JCExpression result; /** The constructor, but use the instance() method to get a new instance, * in order to support extension. This constructor should only be * invoked by a derived class constructor. * @param context the compilation context */ protected BasicBlockerParent(@NonNull Context context) { this.context = context; this.log = Log.instance(context); this.M = JmlTree.Maker.instance(context); this.names = Names.instance(context); this.syms = Symtab.instance(context); this.specs = JmlSpecs.instance(context); this.treeutils = JmlTreeUtils.instance(context); this.scanMode = AST_JAVA_MODE; } /** Instantiated by derived classes to create a new (empty) basic block program */ abstract public P newProgram(Context context); /** Creates a new block of the appropriate type. */ abstract public T newBlock(JCIdent id); /** Creates a block name - note that this format is presumed when * proof failures are being traced and understood. * @param pos the character position of the statement for which the block is being generated * @param kind a suffix to indicate the reason for block * @return a composite name for a block */ // The format of the block name is relied upon in JmlEsc.reportInvalidAssertion protected String blockName(int pos, String kind) { // The block count is appended to be sure that the id is unique. Blocks // can originate from the same DiagnosticPosition and so the pos and key // are not always enough to distinguish them. So the pos and the key // are not entirely necessary, but they do serve some documentation // function. return blockNamePrefix(pos,kind) + "_" + (++blockCount); } protected String blockNamePrefix(int pos, String kind) { return blockPrefix + pos + kind; } // UTILITY METHODS /** Should not need this when everything is implemented */ protected void notImpl(JCTree that) { log.getWriter(WriterKind.NOTICE).println("Internal error - visit method NOT IMPLEMENTED: " + getClass() + " - "+ that.getClass()); result = treeutils.trueLit; } /** 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 " + getClass()); throw new JmlInternalError(); } // GENERAL AND HELPER METHODS FOR PROCESSING BLOCKS /** Does the conversion of a block with Java statements into basic program * form. Newly created blocks should be processed by recursive calls * to this method. This method operates by calling startBlock to * initialize block processing (which also sets the value of currentBlock * and puts all statements from the currentBlock on the remainingStatements * list), then calling processCurrentBlock() to process everything on * remainingStatements; processCurrentBlock() also calls completeBlock() to * wrap up any processing on the block. * * @param block the block to process */ protected void processBlock(@NonNull T block) { if (block.preceders().isEmpty()) { // Delete any blocks that do not follow anything // This can happen for example if the block is an afterIf block // and both the then branch and the else branch terminate with // a return or throw statement. If the block does contain some // statement then those will never be executed. They should // have been warned about by the compiler. Here we will // log a warning, ignore the block, and continue processing. // Note that the block will still have an id and be in the // id map (blockLookup). if (!block.statements.isEmpty() && !block.id().name.toString().contains(TRYFINALLYNORMAL) && !block.id().name.toString().contains("finallyExit")) { log.warning("jml.internal","A basic block has no predecessors - ignoring it: " + block.id); } program.blocks.remove(block); for (T b: block.followers()) { b.preceders().remove(block); } return; } if (!program.blocks.contains(block)) { startBlock(block); processCurrentBlock(); } else { log.warning("jml.internal","Basic block " + block.id + " is being re-processed"); } } /** Finishes processing statements on the remainingStatements list; * call this to complete processing the currentBlock once processing * has been started (e.g., when new statements have been added to the * remainingStatements list). */ protected void processCurrentBlock() { while (!remainingStatements.isEmpty()) { JCStatement s = remainingStatements.remove(0); if (s != null) s.accept(this); // A defensive check - statements in the list should not be null } completeBlock(currentBlock); } /** Initialize the processing of the given block: * <UL> * <LI> checks that any preceding blocks are already processed (if not, something has gone wrong) * <LI> does special processing for finally and lop-after blocks * <LI> sets the argument as the current block (and sets the currentMap and the remainingstatements) * </UL> * * @param b the block for which to initialize processing */ protected void startBlock(@NonNull T b) { // Check that all preceding blocks are actually completed // This is defensive programming and should not actually be needed //log.noticeWriter.println("Checking block " + b.id()); loop: while (true) { for (T pb: b.preceders()) { //log.noticeWriter.println(" " + b.id() + " follows " + pb.id()); if (!program.blocks.contains(pb)) { log.getWriter(WriterKind.NOTICE).println("Internal Error: block " + pb.id.name + " precedes block " + b.id.name + " , but was not processed before it"); //$NON-NLS-1$ //$NON-NLS-2$ processBlock(pb); continue loop; // the list of preceding blocks might have changed - check it over again } } break; // all preceding blocks were processed } //log.noticeWriter.println("Starting block " + b.id); program.blocks.add(b); setCurrentBlock(b); } /** Sets all the variables that are supposed to stay in synch with the value of * currentBlock * @param b the new currentBlock */ protected void setCurrentBlock(T b) { if (currentBlock != null) { log.warning("jml.internal.notsobad","Starting block " + b.id + " when " + currentBlock.id + " is not completed"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ } currentBlock = b; remainingStatements = currentBlock.statements; currentBlock.statements = new ArrayList<JCStatement>(); } /** Allows derived classes to do any cleanup activity. * @param b the completed block */ protected void completeBlock(@NonNull T b) { // remainingStatements should be null, but we don't bother to do the defensive check currentBlock = null; } /** Updates the data structures to indicate that the after block follows the * before block * @param before block that precedes after * @param after block that follows before */ protected void follows(@NonNull T before, @NonNull T after) { before.followers().add(after); after.preceders().add(before); } /** Updates the data structures to indicate that all the after blocks follow the * before block * @param before block that precedes after * @param after list of blocks that follow before */ protected void follows(@NonNull T before, @NonNull List<T> after) { for (T b: after) { before.followers().add(b); b.preceders().add(before); } } /** Inserts the after block after the before block, replacing anything that * used to follow the before block * @param before block whose follow list is to be changed * @param after new following block */ protected void replaceFollows(@NonNull T before, @NonNull T after) { for (T b: before.followers()) { b.preceders().remove(before); } before.followers().clear(); follows(before,after); } /** Inserts the after blocks after the before block, replacing anything that * used to follow the before block * @param before * @param after */ protected void replaceFollows(@NonNull T before, @NonNull List<T> after) { for (T b: before.followers()) { b.preceders().remove(before); } before.followers().clear(); for (T b: after) { follows(before,b); } } /** Creates a new block of type T; * the 'previousBlock' gives up its followers to the newly created block. */ public T newBlock(JCIdent id, T previousBlock) { T nb = newBlock(id); List<T> s = nb.followers(); // empty, just don't create a new empty list nb.followers = previousBlock.followers(); previousBlock.followers = s; for (T f: nb.followers()) { f.preceders().remove(previousBlock); f.preceders().add(nb); } String name = id.name.toString(); blockLookup.put(name,nb); name = name.substring(0,name.lastIndexOf("_")); blockLookup.put(name, nb); return nb; } /** Returns a new, empty block * * @param name the name to give the block * @param pos a position to associate with the JCIdent for the block * @return the new block */ protected @NonNull T newBlock(@NonNull String key, int pos) { String name = blockName(pos,key); JCIdent id = treeutils.makeIdent(pos,name,syms.booleanType); T bb = newBlock(id); blockLookup.put(name,bb); name = name.substring(0,name.lastIndexOf("_")); blockLookup.put(name, bb); return bb; } /** Returns a new, empty block, but the new block takes all of the * followers of the given block; the previousBlock will then have no * followers. * * @param name the name to give the block * @param pos a position to associate with the JCIdent for the block * @param previousBlock the block that is giving up its followers * @return the new block */ protected @NonNull T newBlock(@NonNull String key, int pos, @NonNull T previousBlock) { String name = blockName(pos,key); // See the comment in the newBlock(...) method above JCIdent id = treeutils.makeIdent(pos,name,syms.booleanType); T bb = newBlock(id,previousBlock); blockLookup.put(name, bb); name = name.substring(0,name.lastIndexOf("_")); blockLookup.put(name, bb); return bb; } /** Returns a new, empty T, but the new block takes all of the * followers and the remaining statements of the current block; the * currentBlock will then have no remaining statements and no followers. * * @param name the name to give the block * @param pos a position to associate with the block * @return the new block */ protected T newBlockWithRest(@NonNull String key, int pos) { T b = newBlock(key,pos,currentBlock);// it gets all the followers of the current block // We do this switch to avoid creating more new lists List<JCStatement> temp = b.statements; // empty b.statements = remainingStatements; // it gets all of the remaining statements remainingStatements = temp; // empty return b; } /** A helper initialization routine for derived classes, called internally at * the start of converting a method body */ protected void initialize(@NonNull JCMethodDecl methodDecl, @NonNull JCClassDecl classDecl, @NonNull JmlAssertionAdder assertionAdder) { this.methodDecl = (JmlMethodDecl)methodDecl; this.program = newProgram(context); this.program.methodDecl = methodDecl; this.isConstructor = methodDecl.sym.isConstructor(); this.isStatic = methodDecl.sym.isStatic(); if (classDecl.sym == null) { log.error("jml.internal","The class declaration in convertMethodBody appears not to be typechecked"); } this.terminationSym = (VarSymbol)assertionAdder.terminationSymbols.get(methodDecl); this.exceptionSym = (VarSymbol)assertionAdder.exceptionSymbols.get(methodDecl); this.blockLookup.clear(); this.loopStack.clear(); this.continueMap.clear(); this.breakBlocks.clear(); this.breakStack.clear(); this.catchStack.clear(); this.finallyStack.clear(); this.blockCount = 0; // Fields that do not need initialization: result, remainingStatements, currentBlock } /** Associates end position information with newnode, taking the information * from srcnode; the information is stored in the end-position table that * is part of log.currentSource(). No action happens if either argument is null. */ public void copyEndPosition(@Nullable JCTree newnode, @Nullable JCTree srcnode) { EndPosTable z = log.currentSource().getEndPosTable(); if (z != null && srcnode != null) { // srcnode can be null when processing a switch statement int end = srcnode.getEndPosition(z); if (end != Position.NOPOS) z.storeEnd(newnode, end); } } // METHODS TO ADD ASSUME AND ASSERT 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) { JmlStatementExpr st = M.at(pos).JmlExpressionStatement(JmlTokenKind.ASSUME,label,that); copyEndPosition(st,that); st.type = null; // statements do not have a type statements.add(st); return st; } ListBuffer<JCStatement> temp = new ListBuffer<>(); protected void addAssumeCheck(java.util.List<JCStatement> statements, String bbname) { JmlEsc.instance(context).assertionAdder.addAssumeCheck(treeutils.trueLit, temp, "BB-Assume" ); statements.add(temp.first()); temp.clear(); } /** Adds a new assume statement to the end of the given statements list; the assume statement is * given the declaration pos, endpos 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 startpos, JCTree endpos, Label label, JCExpression that, List<JCStatement> statements) { if (startpos < 0) startpos = that.pos; JmlStatementExpr st = M.at(startpos).JmlExpressionStatement(JmlTokenKind.ASSUME,label,that); copyEndPosition(st,endpos); st.type = null; // statements do not have a type statements.add(st); return st; } /** This generates a comment statement (not added to any statement list) whose content is the * given String. */ public JmlStatementExpr comment(DiagnosticPosition pos, String s) { return M.at(pos).JmlExpressionStatement(JmlTokenKind.COMMENT,null,M.Literal(s)); } /** This generates a comment statement (not in any statement list) whose content is the * given JCTree, pretty-printed. */ public JmlStatementExpr comment(JCTree t) { return comment(t.pos(),JmlPretty.write(t,false)); } // JAVA CONTROL STATEMENT NODES // Translation of a switch statement // switch (swexpr) { // case A: // SA; // break; // case B: // SB; // // fall through // case C: // SC; // break; // default: // SD; // } // translates to // -- continuation of current block: // assume $$switchExpression$<pos>$<pos> == swexpr; // goto $$case$<A>,$$case$<B>,$$case$<C>,$$defaultcase$<D> // $$case$<A>: // assume $$switchExpression$<pos>$<pos> == A; // SA // goto $$BL$<pos>$switchEnd // $$case$<B>: // assume $$switchExpression$<pos>$<pos> == B; // SB // goto $$caseBody$<C> // $$case$<C>: // assume $$switchExpression$<pos>$<pos> == C; // goto $$caseBody$<C> // $$caseBody$<C>: // Need this block because B fallsthrough to C // SC // goto $$BL$<pos>$switchEnd // $$defaultcase$<D>: // assume !($$switchExpression$<pos>$<pos> == A && ...); // SD // goto $$BL$<pos>$switchEnd // $$BL$<pos>$switchEnd: // .... // // FIXME - need to implement/test for switch expressions that are Strings or Enums @Override public void visitSwitch(JCSwitch that) { currentBlock.statements.add(comment(that.pos(),"switch ...")); int pos = that.pos; int swpos = that.selector.getStartPosition(); scan(that.selector); JCExpression switchExpression = result; List<JCCase> cases = that.cases; T previousBreakBlock = breakBlocks.get(names.empty); // Create the ending block name T blockAfter = null; try { breakStack.add(0,that); // We create a new auxiliary variable to hold the switch value, so // we can track its value and so the subexpression does not get // replicated. We add an assumption about its value and add it to // list of new variables String switchName = SWITCHEXPR + pos; JCIdent vd = treeutils.makeIdent(swpos,switchName,switchExpression.type); scan(vd); program.declarations.add(vd); JCExpression newexpr = treeutils.makeBinary(swpos,JCTree.Tag.EQ,vd,switchExpression); addAssume(swpos,switchExpression,Label.SWITCH_VALUE,newexpr,currentBlock.statements); T switchStart = currentBlock; // Now create an (unprocessed) block for everything that follows the // switch statement blockAfter = newBlockWithRest(AFTERSWITCH,pos);// it gets all the followers of the current block breakBlocks.put(names.empty, blockAfter); // Now we need to make a block for each of the case statements, // adding them to the todo list to be processed at the end // Note that there might be nesting of other switch statements etc. java.util.LinkedList<T> blocks = new java.util.LinkedList<T>(); T prevCaseBlock = null; JCExpression defaultCond = treeutils.falseLit; JmlTree.JmlStatementExpr defaultAsm = null; boolean fallthrough = false; // Nothing to fall through to the first case for (JCCase caseStatement: cases) { /*@ nullable */ JCExpression caseValue = caseStatement.getExpression(); List<JCStatement> stats = caseStatement.getStatements(); int casepos = caseStatement.getStartPosition(); // create a block for this case test T blockForTest = newBlock(caseValue == null ? CASEDEFAULT : CASETEST , caseValue == null ? casepos: caseStatement.getStartPosition()); // FIXME - this is a meaningless distinction, goiven the assignment to casepos blocks.add(blockForTest); follows(switchStart,blockForTest); // create the case test, or null if this is the default case JCIdent vdd = treeutils.makeIdent(caseValue == null ? Position.NOPOS : caseValue.getStartPosition(),vd.sym); /*@ nullable */ JCBinary eq = caseValue == null ? null : treeutils.makeBinary(caseValue.getStartPosition(),JCTree.Tag.EQ,vdd,(caseValue)); JmlStatementExpr asm = addAssume(vdd.pos, Label.CASECONDITION,eq,blockForTest.statements); // continue to build up the default case test if (caseValue == null) defaultAsm = asm; // remember the assumption for the default case else defaultCond = treeutils.makeOr(caseValue.getStartPosition(),eq,defaultCond); // It is always safe for the value of 'fallthrough' to be true, // since processing of the blocks will take care of changing a // block's followers in the case where there is no fall through. // However, it is a bit of an optimization to know when 'fallthrough' // is false, since we avoid creating an unnecessary block. if (!fallthrough) { // statements can go in the same block blockForTest.statements.addAll(stats); follows(blockForTest,blockAfter); // The following block is reset if there is fallthrough to a next block prevCaseBlock = blockForTest; } else { // (possibly) falling through from the previous case // since there is fall-through, the statements need to go in their own block T blockForStats = newBlock(CASEBODY,caseStatement.getStartPosition()); blockForStats.statements.addAll(stats); follows(blockForTest,blockForStats); replaceFollows(prevCaseBlock,blockForStats); follows(blockForStats,blockAfter); // The following block is reset if there turns out to be fallthrough blocks.add(blockForStats); prevCaseBlock = blockForStats; } // Compute and remember whether control falls through to the next case fallthrough = stats.isEmpty() || !(prevCaseBlock.statements.get(prevCaseBlock.statements.size()-1) instanceof JCBreak); } // Now fix up the default case (which is not necessarily last). // Fortunately we remembered it int dpos = defaultAsm == null ? pos : defaultAsm.pos; JCExpression eq = treeutils.makeUnary(dpos,JCTree.Tag.NOT,defaultCond); if (defaultAsm != null) { // There was a default case already made, but at the time we just // put in null for the case condition, since we did not know it // yet (and we wanted to process the statements in textual order). // So here we violate encapsulation a bit and poke it in. defaultAsm.expression = eq; } else { // There was no default - we need to construct an empty one // create a block for this case test T blockForTest = newBlock(CASEDEFAULT,pos); blocks.add(blockForTest); follows(switchStart,blockForTest); follows(blockForTest,blockAfter); addAssume(that.pos,Label.CASECONDITION,eq,blockForTest.statements); } processCurrentBlock(); // Complete the current block // Now process all of the blocks we created, in order for (T b: blocks) { processBlock(b); } } finally { breakStack.remove(0); breakBlocks.put(names.empty, previousBreakBlock); } // Should never actually be null, unless some exception happened if (blockAfter != null) processBlock(blockAfter); } // OK /** Should never call this because case statements are handled in switch. */ public void visitCase(JCCase that) { shouldNotBeCalled(that); } /** Stack to hold Blocks for catch clauses, when try statements are nested */ final protected java.util.List<T> catchStack = new java.util.LinkedList<T>(); /** Stack to hold Blocks for finally clauses, when try statements are nested */ final protected java.util.List<T> finallyStack = new java.util.LinkedList<T>(); // This sets up a complicated arrangement of blocks // // currentBlock: try statement // rest of statements // // becomes // // currentBlock: try statement block // throw - set exceptionVar, terminationVar; goto catchBlocks // return - set returnValue, terminationVar; goto finallyBlock // goto finallyBlock // // catchBlocks: assume condition on exception // reset terminationVar to 0, exceptionVar to null // catch block statements // goto finallyBlock // // finallyBlock: any finally block statements // if exceptionVar is not null, goto next outer catch block, else the corresponding finally block // if terminationVar is not 0, goto next outer finally block // goto afterTryBlock // // afterTryBlock: rest of statements // // // FIXME - unify the use of the termination var? @Override public void visitTry(JCTry that) { currentBlock.statements.add(comment(that.pos(),"try...")); // Create an (unprocessed) block for everything that follows the // try statement int pos = that.pos; T afterTry = newBlockWithRest(AFTERTRY,pos);// it gets all the followers of the current block // remainingStatements is now empty // Put the statements in the try block into the currentBlock remainingStatements.add(that.getBlock()); // We make an empty block that will contain // both catch clause statements and finally block statements. It serves // as the target for any return or throw statements and for exits from // the finally blocks of nested try statements. T targetBlock = newBlock(TRYTARGET,pos); T finallyBlock = newBlock(FINALLY,pos); follows(currentBlock,targetBlock); List<T> blocks = new LinkedList<T>(); blocks.add(targetBlock); // NOTE: A lot of structure sharing occurs in the following. // First create the non-exception path // Note: value for exceptionSym != null ==> an exception has been thrown // value for terminationSym != 0 ==> return or throw executed // value for terminationSym == 0 ==> no return or throw executed JCIdent ex = treeutils.makeIdent(pos, exceptionSym); JCExpression noex = treeutils.makeEqObject(pos,ex,treeutils.nullLit); T noexceptionBlock = newBlock(TRYNOEXCEPTION,pos); addAssume(pos,Label.IMPLICIT_ASSUME,noex,noexceptionBlock.statements); follows(targetBlock,noexceptionBlock); follows(noexceptionBlock,finallyBlock); blocks.add(noexceptionBlock); // Now create the paths for each catch List<JCStatement> assumptions = new LinkedList<JCStatement>(); addAssume(pos,Label.IMPLICIT_ASSUME,treeutils.makeNeqObject(pos,ex,treeutils.nullLit),assumptions); for (JCCatch catcher: that.catchers) { JCExpression ty = catcher.param != null ? catcher.param.vartype : treeutils.makeType(catcher.pos, syms.exceptionType); JCExpression tt = M.at(catcher.pos).TypeTest(ex,ty).setType(syms.booleanType); tt = M.at(catcher.pos).Parens(tt).setType(tt.type); T catchBlock = newBlock(CATCH,catcher.pos); follows(targetBlock,catchBlock); follows(catchBlock,finallyBlock); addAssumeCheck(catchBlock.statements, catchBlock.id().toString() + "-start"); catchBlock.statements.addAll(assumptions); addAssumeCheck(catchBlock.statements, catchBlock.id().toString() + "- +1"); addAssume(catcher.pos,Label.IMPLICIT_ASSUME,tt,catchBlock.statements); addAssumeCheck(catchBlock.statements, catchBlock.id().toString() + "- +2"); addAssume(catcher.pos,Label.IMPLICIT_ASSUME,treeutils.makeNot(catcher.pos,tt),assumptions); JCVariableDecl d = treeutils.makeVariableDecl(catcher.param.sym, ex); d.pos = catcher.param.pos; catchBlock.statements.add(d); JCIdent nex = treeutils.makeIdent(catcher.pos, exceptionSym); catchBlock.statements.add(treeutils.makeAssignStat(catcher.pos,nex,treeutils.nullLit)); // FIXME - seems to duplicate an item in catcher.body.stats JCIdent term = treeutils.makeIdent(catcher.pos, terminationSym); catchBlock.statements.add(treeutils.makeAssignStat(catcher.pos,term,treeutils.zero)); catchBlock.statements.addAll(catcher.body.stats); blocks.add(catchBlock); } // And the path if an exception is not caught by anything T noCatchBlock = newBlock(NOCATCH,pos); noCatchBlock.statements.addAll(assumptions); follows(targetBlock,noCatchBlock); follows(noCatchBlock,finallyBlock); blocks.add(noCatchBlock); // Need to save the current global exception and termination value Symbol owner = methodDecl != null ? methodDecl.sym : null; // FIXME ? classDecl.sym; JCVariableDecl savedException = treeutils.makeVarDef(syms.exceptionType, names.fromString("__JMLsavedException_" + that.pos), owner, that.pos); // FIXME - may need to have a non-null owner JCAssign saveAssignment = treeutils.makeAssign(that.pos, treeutils.makeIdent(that.pos, savedException.sym), treeutils.makeIdent(that.pos,exceptionSym)); finallyBlock.statements.add(M.at(that.pos).Exec(saveAssignment)); JCVariableDecl savedTermination = treeutils.makeVarDef(syms.intType, names.fromString("__JMLsavedTermination_" + that.pos), owner, that.pos); // FIXME - may need to have a non-null owner saveAssignment = treeutils.makeAssign(that.pos, treeutils.makeIdent(that.pos, savedTermination.sym), treeutils.makeIdent(that.pos,terminationSym)); finallyBlock.statements.add(M.at(that.pos).Exec(saveAssignment)); JCBlock finallyStat = that.getFinallyBlock(); if (finallyStat != null) finallyBlock.statements.addAll(finallyStat.getStatements()); // it gets the statements of the finally statement // Restore the global exception JCAssign restoreAssignment = treeutils.makeAssign(that.pos, treeutils.makeIdent(that.pos, exceptionSym), treeutils.makeIdent(that.pos,savedException.sym)); finallyBlock.statements.add(M.at(that.pos).Exec(restoreAssignment)); restoreAssignment = treeutils.makeAssign(that.pos, treeutils.makeIdent(that.pos, terminationSym), treeutils.makeIdent(that.pos,savedTermination.sym)); finallyBlock.statements.add(M.at(that.pos).Exec(restoreAssignment)); // And lastly, the blocks for out-of-band exits from the finally block // The normal ending block is for when there is no pending exception or return; // it takes all the finallyBlock followers and itself follows the finallyBlock. // The return/exception ending block follows the finallyBlock and precedes // the targetBlock for enclosing try-finally block. T finallyNormalBlock = newBlock(TRYFINALLYNORMAL,pos); follows(finallyBlock,finallyNormalBlock); follows(finallyNormalBlock,afterTry); JCIdent term = treeutils.makeIdent(pos, terminationSym); JCBinary bin = treeutils.makeBinary(pos,JCTree.Tag.EQ,treeutils.inteqSymbol,term,treeutils.zero); addAssume(pos,Label.IMPLICIT_ASSUME,bin,finallyNormalBlock.statements); T finallyExitBlock = newBlock(TRYFINALLYEXIT,pos); term = treeutils.makeIdent(pos, terminationSym); bin = treeutils.makeBinary(pos,JCTree.Tag.NE,treeutils.intneqSymbol,term,treeutils.zero); addAssume(pos,Label.IMPLICIT_ASSUME,bin,finallyExitBlock.statements); follows(finallyBlock,finallyExitBlock); if (!finallyStack.isEmpty()) follows(finallyExitBlock,finallyStack.get(0)); else follows(finallyExitBlock,afterTry); // Put targetBlock on the finallyStack - it is the destination for // any return or throw during the try statement body finallyStack.add(0,targetBlock); // Finish the processing of the current block; it might // refer to the finally block during processing processCurrentBlock(); finallyStack.remove(0); // Remove targetBlock // Now the finally block is the destination of any return or throw // during catch clauses finallyStack.add(0,finallyBlock); for (T b: blocks) { processBlock(b); } finallyStack.remove(0); processBlock(finallyBlock); processBlock(finallyNormalBlock); processBlock(finallyExitBlock); processBlock(afterTry); } /** Catch statements are handled in visitTry */ public void visitCatch(JCCatch that) { shouldNotBeCalled(that); } // OK public void visitIf(JCIf that) { int pos = that.pos; currentBlock.statements.add(comment(that.pos(),"if...")); // Now create an (unprocessed) block for everything that follows the // if statement T afterIf = newBlockWithRest(AFTERIF,pos);// it gets all the followers of the current block // Now make the then block T thenBlock = newBlock(THENSUFFIX,pos); addAssume(that.cond.pos, Label.BRANCHT, that.cond, thenBlock.statements); thenBlock.statements.add(that.thenpart); follows(thenBlock,afterIf); follows(currentBlock,thenBlock); // Now make the else block T elseBlock = newBlock(ELSESUFFIX,pos); addAssume(that.cond.pos, Label.BRANCHE, treeutils.makeNot(that.cond.pos,that.cond), elseBlock.statements); if (that.elsepart != null) elseBlock.statements.add(that.elsepart); follows(elseBlock,afterIf); follows(currentBlock,elseBlock); processCurrentBlock(); // complete current block processBlock(thenBlock); processBlock(elseBlock); processBlock(afterIf); } /** This is a stack of loops and switch statements - anything that can * contain a break statement */ final protected java.util.List<JCTree> breakStack = new java.util.LinkedList<JCTree>(); /** This is a map of label to Block, giving the block to which a labelled break * should jump - which is the Block after the labelled statement. */ final protected java.util.Map<Name,T> breakBlocks = new java.util.HashMap<Name,T>(); @Override // OK public void visitBreak(JCBreak that) { if (that.label != null) { // Note that a break with a label exits the labelled statement, even // if we are currently in a switch T breakBlock = breakBlocks.get(that.label); if (breakBlock == null) { log.error(that.pos(),"jml.internal","No target found for break label " + that.label); } else { replaceFollows(currentBlock,breakBlock); } } else if (breakStack.isEmpty()) { log.error(that.pos(),"jml.internal","Empty break stack"); } else { JCTree tree = breakStack.get(0); if (tree instanceof JCSwitch) { // Don't need to do anything. If the break is not at the end of a block, // the compiler would not have passed this. If it is at the end of a block // the logic in handling JCSwitch has taken care of everything. T b = breakBlocks.get(names.empty); if (b == null) { log.error(that.pos(),"jml.internal","Corresponding break target is not found"); //$NON-NLS-1$ //$NON-NLS-2$ } else { replaceFollows(currentBlock,b); } } else { T b = breakBlocks.get(names.empty); if (b == null) { log.error(that.pos(),"jml.internal","Corresponding break target is not found"); //$NON-NLS-1$//$NON-NLS-2$ } else { replaceFollows(currentBlock,b); } } } if (!remainingStatements.isEmpty()) { log.warning(remainingStatements.get(0).pos(),"jml.internal.notsobad","Statements after a break statement are unreachable and are ignored"); //$NON-NLS-1$ //$NON-NLS-2$ } } // FIXME - implement: intervening finally blocks public void visitContinue(JCContinue that) { currentBlock.statements.add(comment(that)); if (loopStack.isEmpty()) { log.error(that.pos(),"jml.internal","Empty loop stack for continue statement"); //$NON-NLS-1$ //$NON-NLS-2$ } else if (that.label == null) { JCTree t = loopStack.get(0); String blockName = blockNamePrefix(t.pos,LOOPCONTINUE); T b = blockLookup.get(blockName); if (b == null) log.error(that.pos(),"jml.internal","No continue block found to match this continue statement"); //$NON-NLS-1$//$NON-NLS-2$ else replaceFollows(currentBlock,b); } else { JCTree t = continueMap.get(that.label); String blockName = blockNamePrefix(t.pos,LOOPCONTINUE); T b = blockLookup.get(blockName); if (b == null) log.error(that.pos(),"jml.internal","No continue block found to match this continue statement, with label ", that.label); //$NON-NLS-1$ //$NON-NLS-2$ else replaceFollows(currentBlock,b); } if (!remainingStatements.isEmpty()) { log.warning(remainingStatements.get(0).pos(),"jml.internal.notsobad","Statements after a continue statement are unreachable and are ignored"); //$NON-NLS-1$ //$NON-NLS-2$ } } // OK - presumes that the program has already been modified to record // the return value and that the entire method body is enclosed in an // outer try-finally block public void visitReturn(JCReturn that) { if (!remainingStatements.isEmpty()) { JCStatement stat = remainingStatements.get(0); if (stat.toString().contains("JMLsaved")) remainingStatements.remove(0); if (remainingStatements.get(0).toString().contains("JMLsaved")) remainingStatements.remove(0); if (!remainingStatements.isEmpty()) { // Not fatal, but does indicate a problem with the original // program, which the compiler may have already identified log.warning(remainingStatements.get(0).pos, "esc.internal.error", //$NON-NLS-1$ "Unexpected statements following a return statement are ignored"); //$NON-NLS-1$ remainingStatements.clear(); } } // There are no remaining statements, so this new block is empty. // But we create it so there is a block that marks the return statement // and can be used to note the termination point in a trace // The format of the block name is relied upon in JmlEsc.reportInvalidAssertion T afterReturn = newBlockWithRest(RETURN,that.pos); follows(currentBlock,afterReturn); if (finallyStack.isEmpty()) { // We don't expect the finallyStack to ever be empty when there is // a return statement only because // JmlAssertionAdder wraps everything in a try-finally statement // TODO - generalize this log.warning(that.pos(),"esc.internal.error","finally stack is unexpectedly empty"); //$NON-NLS-1$//$NON-NLS-2$ } else { T finallyBlock = finallyStack.get(0); replaceFollows(afterReturn,finallyBlock); } processCurrentBlock(); processBlock(afterReturn); } // OK - presumes that the program has already been modified to record // the value of the exception being thrown public void visitThrow(JCThrow that) { if (!remainingStatements.isEmpty()) { JCStatement stat = remainingStatements.get(0); if (stat.toString().contains("JMLsaved")) remainingStatements.remove(0); if (remainingStatements.get(0).toString().contains("JMLsaved")) remainingStatements.remove(0); if (!remainingStatements.isEmpty()) { // Not fatal, but does indicate a problem with the original // program, which the compiler may have already identified log.warning(remainingStatements.get(0).pos, "esc.internal.error", "Unexpected statements following a throw statement"); remainingStatements.clear(); } } // There are no remaining statements, so this new block is empty. // But we create it so there is a block that marks the throw statement // and can be used to note the termination point in a trace T afterThrow = newBlockWithRest(THROW,that.pos); follows(currentBlock,afterThrow); if (finallyStack.isEmpty()) { // We don't expect the finallyStack to ever be empty when there is // a return statement only because // JmlAssertionAdder wraps everything in a try-finally statement // TODO - generalize this log.warning(that.pos(),"esc.internal.error","finally stack is unexpectedly empty"); } else { T targetBlock = finallyStack.get(0); replaceFollows(afterThrow,targetBlock); } processCurrentBlock(); processBlock(afterThrow); } // OK - FIXME - turn into a try-finally with lock operations? public void visitSynchronized(JCSynchronized that) { super.visitSynchronized(that); } // LOOPS @Override public void visitJmlWhileLoop(JmlWhileLoop that) { currentBlock.statements.add(comment(that.pos(),"while...")); loopHelper(that, null, that.cond, null, that.body); } @Override public void visitWhileLoop(JCWhileLoop that) { currentBlock.statements.add(comment(that.pos(),"while...")); loopHelper(that, null, that.cond, null, that.body); } @Override public void visitJmlForLoop(JmlForLoop that) { currentBlock.statements.add(comment(that.pos(),"for...")); loopHelper(that,that.init,that.cond,that.step,that.body); } @Override public void visitForLoop(JCForLoop that) { currentBlock.statements.add(comment(that.pos(),"for...")); loopHelper(that,that.init,that.cond,that.step,that.body); } /** A stack of the (nested) loops encountered */ final protected List<JCTree> loopStack = new LinkedList<JCTree>(); /** A map of labels to loops for continue statements */ final protected Map<Name,JCTree> continueMap = new HashMap<Name,JCTree>(); /* for (Init; Test; Update) S * becomes * LoopStart: - is actually the end of the current Block * Init; * IF UNROLLING: * compute loop condition * goto LoopUnroll0, LoopEnd * LoopUnroll0: * assume loop condition * S * Update * [ compute loop condition * goto LoopUnroll1, LoopEnd * LoopUnroll1: * assume loop condition * S * Update * ] * compute loop condition * goto LoopBody, LoopEnd * LoopBody: * assume loop condition value * S [ break -> LoopBreak; continue -> LoopContinue ] * goto LoopContinue * LoopContinue: <-- all continues go here * Update; * * LoopEnd: * assume !(loop condition value) * goto LoopBreak * LoopBreak: <-- all breaks go here * goto LoopAfter... */ // FIXME - allow for unrolling; review the above and the implementation // FIXME - check and document; add backedges protected void loopHelper(JCTree that, List<JCStatement> init, JCExpression test, List<JCExpressionStatement> update, JCStatement body) { loopStack.add(0,that); breakStack.add(0,that); int pos = that.pos; T bloopBody = newBlock(LOOPBODY,pos); T bloopContinue = newBlock(LOOPCONTINUE,pos); T bloopEnd = newBlock(LOOPEND,pos); // Now create a block for everything that follows the // loop statement T bloopAfter = newBlockWithRest(LOOPAFTER,pos);// it gets all the followers and statements of the current block T previousBreakBlock = breakBlocks.put(names.empty, bloopAfter); if (init != null) remainingStatements.addAll(init); scan(test); JCExpression ntest = result; T bloopStart = currentBlock; follows(bloopStart,bloopBody); follows(bloopStart,bloopEnd); // Create the loop body block addAssume(test.pos,Label.LOOP,ntest,bloopBody.statements); bloopBody.statements.add(body); follows(bloopBody,bloopContinue); // Create the loop continue block // It does the update if (update != null) bloopContinue.statements.addAll(update); // Create the loop end block addAssume(test.pos,Label.LOOP,treeutils.makeNot(test.pos, ntest),bloopEnd.statements); follows(bloopEnd,bloopAfter); // Now process all the blocks processCurrentBlock(); processBlock(bloopBody); processBlock(bloopContinue); processBlock(bloopEnd); loopStack.remove(0); breakStack.remove(0); breakBlocks.put(names.empty, previousBreakBlock); processBlock(bloopAfter); } @Override public void visitJmlEnhancedForLoop(JmlEnhancedForLoop that) { currentBlock.statements.add(comment(that.pos(),"for...")); loopHelperEFor(that); } @Override public void visitForeachLoop(JCEnhancedForLoop that) { currentBlock.statements.add(comment(that.pos(),"for...")); loopHelperEFor(that); } // FIXME - not implemented at all protected void loopHelperEFor(JCEnhancedForLoop that) { // if (that.expr.type.tag == TypeTags.ARRAY) { // // for (T v; arr) S // // becomes // // for (int $$index=0; $$index<arr.length; $$index++) { v = arr[$$index]; S } // // // make int $$index = 0; as the initialization //// Name name = names.fromString("$$index$"+that.pos); //// JCVariableDecl decl = makeVariableDecl(name,syms.intType,treeutils.makeIntLiteral(0,pos),pos); //// JCVariableDecl decl = ((JmlEnhancedForLoop)that).indexDecl; //// JCVariableDecl vdecl = ((JmlEnhancedForLoop)that).indexDecl; //// com.sun.tools.javac.util.List<JCStatement> initList = com.sun.tools.javac.util.List.<JCStatement>of(decl,vdecl); // // // make assume \values.size() == 0 // //// // make $$index < <expr>.length as the loop test //// JCIdent id = (JCIdent)factory.at(pos).Ident(decl); //// id.type = decl.type; //// JCExpression fa = factory.at(pos).Select(that.getExpression(),syms.lengthVar); //// fa.type = syms.intType; //// JCExpression test = treeutils.makeBinary(pos,JCTree.LT,id,fa); // //// // make $$index = $$index + 1 as the update list //// JCIdent idd = (JCIdent)factory.at(pos+1).Ident(decl); //// id.type = decl.type; //// JCAssign asg = factory.at(idd.pos).Assign(idd, //// treeutils.makeBinary(idd.pos,JCTree.PLUS,id,treeutils.makeIntLiteral(pos,1))); //// asg.type = syms.intType; //// JCExpressionStatement update = factory.at(idd.pos).Exec(asg); //// com.sun.tools.javac.util.List<JCExpressionStatement> updateList = com.sun.tools.javac.util.List.<JCExpressionStatement>of(update); // //// // make <var> = <expr>[$$index] as the initialization of the target and prepend it to the body //// JCArrayAccess aa = factory.at(pos).Indexed(that.getExpression(),id); //// aa.type = that.getVariable().type; //// JCIdent v = (JCIdent)factory.at(pos).Ident(that.getVariable()); //// v.type = aa.type; //// asg = factory.at(pos).Assign(v,aa); //// asg.type = v.type; // ListBuffer<JCStatement> newbody = new ListBuffer<JCStatement>(); //// newbody.append(factory.at(pos).Exec(asg)); // newbody.append(that.body); // // // add 0 <= $$index && $$index <= <expr>.length // // as an additional loop invariant //// JCExpression e1 = treeutils.makeBinary(pos,JCTree.LE,treeutils.makeIntLiteral(pos,0),id); //// JCExpression e2 = treeutils.makeBinary(pos,JCTree.LE,id,fa); //// JCExpression e3 = treeutils.makeBinary(pos,JCTree.AND,e1,e2); //// JmlStatementLoop inv =factory.at(pos).JmlStatementLoop(JmlToken.LOOP_INVARIANT,e3); //// if (loopSpecs == null) { //// loopSpecs = com.sun.tools.javac.util.List.<JmlStatementLoop>of(inv); //// } else { //// ListBuffer<JmlStatementLoop> buf = new ListBuffer<JmlStatementLoop>(); //// buf.appendList(loopSpecs); //// buf.append(inv); //// loopSpecs = buf.toList(); //// } // loopHelper(that,null,treeutils.trueLit,null,M.at(that.body.pos).Block(0,newbody.toList())); // // // } else { notImpl(that); // } } @Override public void visitDoLoop(JCDoWhileLoop that) { currentBlock.statements.add(comment(that.pos(),"do...")); loopHelperDoWhile(that); } @Override public void visitJmlDoWhileLoop(JmlDoWhileLoop that) { currentBlock.statements.add(comment(that.pos(),"do...")); loopHelperDoWhile(that); } /* FOR A DO-WHILE LOOP * do { S; } while (Test) becomes * * LoopBody: * S [ break -> LoopBreak; continue -> LoopContinue ] * goto LoopContinue * LoopContinue: <-- all continues go here * compute loop condition (which may have side effects creating other statements) * goto LoopEnd * LoopEnd: * assume !(loop condition value) * goto LoopBreak * LoopBreak: <-- all breaks go here * goto rest... */ // FIXME - allow for unrolling protected void loopHelperDoWhile(JCDoWhileLoop that) { loopStack.add(0,that); breakStack.add(0,that); int pos = that.pos; T bloopBody = newBlock(LOOPBODY,pos); T bloopContinue = newBlock(LOOPCONTINUE,pos); T bloopEnd = newBlock(LOOPEND,pos); T bloopBreak = newBlock(LOOPBREAK,pos); follows(currentBlock,bloopBody); follows(bloopBody,bloopContinue); follows(bloopContinue,bloopEnd); follows(bloopEnd,bloopBreak); // Create an (unprocessed) block for everything that follows the // loop statement T bloopAfter = newBlockWithRest(LOOPAFTERDO,pos);// it gets all the followers of the current block follows(bloopBreak,bloopAfter); T previousBreakBlock = breakBlocks.put(names.empty, bloopBreak); try { // do the loop body bloopBody.statements.add(that.body); processCurrentBlock(); processBlock(bloopBody); scan(that.cond); // TODO - fix for case that has side -effects - not currently used JCExpression ntest = result; addAssume(that.cond.pos,Label.LOOP,treeutils.makeNot(ntest.pos,ntest),bloopEnd.statements); processBlock(bloopContinue); } finally { loopStack.remove(0); breakStack.remove(0); breakBlocks.put(names.empty,previousBreakBlock); } processBlock(bloopEnd); processBlock(bloopBreak); processBlock(bloopAfter); } // JAVA OTHER STATEMENT AND EXPRESSION NODES // Just process all the statements in the block prior to any other // remaining statements // OK @Override public void visitBlock(JCBlock that) { List<JCStatement> s = that.getStatements(); if (s != null) { // Add the block statements BEFORE any remaining statements remainingStatements.addAll(0,s); } } @Override public void visitJmlBlock(JmlBlock that) { visitBlock(that); } /** Finds the statement that a labeled statement labels, stripping off all * nested labels. */ JCTree stripLabels(JCStatement st) { while (st instanceof JCLabeledStatement) st = ((JCLabeledStatement)st).getStatement(); return st; } // OK @Override public void visitLabelled(JCLabeledStatement that) { shouldNotBeCalled(that); } @Override public void visitJmlLabeledStatement(JmlLabeledStatement that) { T nextBlock = newBlockWithRest(AFTERLABEL,that.pos); follows(currentBlock,nextBlock); breakBlocks.put(that.label, nextBlock); continueMap.put(that.label, stripLabels(that)); try { remainingStatements.add(that.getStatement()); processCurrentBlock(); } finally { breakBlocks.remove(that.label); continueMap.remove(that.label); } processBlock(nextBlock); } @Override public void visitTopLevel(JCCompilationUnit that) { shouldNotBeCalled(that); } @Override public void visitImport(JCImport that) { shouldNotBeCalled(that); } @Override public void visitClassDef(JCClassDecl that) { shouldNotBeCalled(that); } // should always be JmlClassDecl objects @Override public void visitMethodDef(JCMethodDecl that) { shouldNotBeCalled(that); } @Override public void visitVarDef(JCVariableDecl that) { notImpl(that); } @Override public void visitTypeIdent(JCPrimitiveTypeTree that) { notImpl(that); } @Override public void visitTypeArray(JCArrayTypeTree that) { notImpl(that); } @Override public void visitTypeApply(JCTypeApply that) { notImpl(that); } @Override public void visitTypeParameter(JCTypeParameter that) { notImpl(that); } @Override public void visitWildcard(JCWildcard that) { notImpl(that); } @Override public void visitTypeBoundKind(TypeBoundKind that) { notImpl(that); } @Override public void visitAnnotation(JCAnnotation that) { notImpl(that); } @Override public void visitModifiers(JCModifiers that) { notImpl(that); } @Override public void visitErroneous(JCErroneous that) { notImpl(that); } @Override public void visitLetExpr(LetExpr that) { notImpl(that); } // OK @Override public void visitExec(JCExpressionStatement that) { // This includes assignments and stand-alone method invocations scan(that.expr); } // Do not need to override these methods // @Override public void visitSkip(JCSkip that) { super.visitSkip(that); } // No default implementation for these @Override public void visitApply(JCMethodInvocation that) { notImpl(that); } @Override public void visitAssert(JCAssert that) { notImpl(that); } @Override public void visitAssign(JCAssign that) { notImpl(that); } @Override public void visitAssignop(JCAssignOp that) { notImpl(that); } // Expressions just need to set the result field @Override public void visitBinary(JCBinary that) { scan(that.lhs); that.lhs = result; scan(that.rhs); that.rhs = result; result = that; } @Override public void visitConditional(JCConditional that) { scan(that.cond); that.cond = result; scan(that.truepart); that.truepart = result; scan(that.falsepart); that.falsepart = result; result = that; } // NOTE: Likely should be overridden @Override public void visitIdent(JCIdent that) { result = that; } @Override public void visitIndexed(JCArrayAccess that) { scan(that.indexed); that.indexed = result; scan(that.index); that.index = result; result = that; } @Override public void visitLiteral(JCLiteral that) { result = that; } @Override public void visitNewArray(JCNewArray that) { scan(that.elemtype); that.elemtype = result; scan(that.dims); scan(that.elems); result = that; } @Override public void visitNewClass(JCNewClass that) { scan(that.encl); that.encl = result; scan(that.typeargs); scan(that.clazz); that.clazz = result; scan(that.args); scan(that.def); result = that; } @Override public void visitParens(JCParens that) { scan(that.expr); that.expr = result; result = that; } @Override public void visitSelect(JCFieldAccess that) { scan(that.selected); that.selected = result; result = that; } @Override public void visitTypeCast(JCTypeCast that) { scan(that.clazz); that.clazz = result; scan(that.expr); that.expr = result; result = that; } @Override public void visitTypeTest(JCInstanceOf that) { scan(that.clazz); that.clazz = result; scan(that.expr); that.expr = result; result = that; } @Override public void visitUnary(JCUnary that) { scan(that.arg); that.arg = result; result = that; } // FIXME _ what about the BB nodes // JML NODES - BasicBlockers asssume that nearly all JML specs have been // translated into asserts and assume statements, or into uninterpreted // functions. // Needs implementation in derived classes @Override public void visitJmlMethodInvocation(JmlMethodInvocation that) { notImpl(that); } @Override public void visitJmlVariableDecl(JmlVariableDecl that) { notImpl(that); } @Override public void visitJmlSingleton(JmlSingleton that) { notImpl(that); } @Override public void visitJmlBinary(JmlBinary that) { shouldNotBeCalled(that); } @Override public void visitJmlChoose(JmlChoose that) { shouldNotBeCalled(that); } @Override public void visitJmlMethodSig(JmlMethodSig that) { shouldNotBeCalled(that); } @Override public void visitJmlGroupName(JmlGroupName that) { shouldNotBeCalled(that); } @Override public void visitJmlLblExpression(JmlLblExpression that) { shouldNotBeCalled(that); } @Override public void visitJmlMethodClauseCallable(JmlMethodClauseCallable that) { shouldNotBeCalled(that); } @Override public void visitJmlMethodClauseConditional(JmlMethodClauseConditional that) { shouldNotBeCalled(that); } @Override public void visitJmlMethodClauseDecl(JmlMethodClauseDecl that) { shouldNotBeCalled(that); } @Override public void visitJmlMethodClauseExpr(JmlMethodClauseExpr that) { shouldNotBeCalled(that); } @Override public void visitJmlMethodClauseGroup(JmlMethodClauseGroup that) { shouldNotBeCalled(that); } @Override public void visitJmlMethodClauseSignals(JmlMethodClauseSignals that) { shouldNotBeCalled(that); } @Override public void visitJmlMethodClauseSigOnly(JmlMethodClauseSignalsOnly that) { shouldNotBeCalled(that); } @Override public void visitJmlMethodClauseStoreRef(JmlMethodClauseStoreRef that) { shouldNotBeCalled(that); } @Override public void visitJmlMethodSpecs(JmlMethodSpecs that) { shouldNotBeCalled(that); } @Override public void visitJmlModelProgramStatement(JmlModelProgramStatement that) { shouldNotBeCalled(that); } @Override public void visitJmlPrimitiveTypeTree(JmlPrimitiveTypeTree that) { shouldNotBeCalled(that); } @Override public void visitJmlQuantifiedExpr(JmlQuantifiedExpr that) { shouldNotBeCalled(that); } @Override public void visitJmlSetComprehension(JmlSetComprehension that) { shouldNotBeCalled(that); } @Override public void visitJmlSpecificationCase(JmlSpecificationCase that) { shouldNotBeCalled(that); } @Override public void visitJmlStatement(JmlStatement that) { shouldNotBeCalled(that); } @Override public void visitJmlStatementDecls(JmlStatementDecls that) { shouldNotBeCalled(that); } @Override public void visitJmlStatementLoop(JmlStatementLoop that) { shouldNotBeCalled(that); } @Override public void visitJmlStoreRefArrayRange(JmlStoreRefArrayRange that){ shouldNotBeCalled(that); } @Override public void visitJmlStoreRefKeyword(JmlStoreRefKeyword that) { shouldNotBeCalled(that); } @Override public void visitJmlStoreRefListExpression(JmlStoreRefListExpression that) { shouldNotBeCalled(that); } @Override public void visitJmlTypeClauseIn(JmlTypeClauseIn that) { shouldNotBeCalled(that); } @Override public void visitJmlTypeClauseMaps(JmlTypeClauseMaps that) { shouldNotBeCalled(that); } @Override public void visitJmlTypeClauseExpr(JmlTypeClauseExpr that) { shouldNotBeCalled(that); } @Override public void visitJmlTypeClauseDecl(JmlTypeClauseDecl that) { shouldNotBeCalled(that); } @Override public void visitJmlTypeClauseInitializer(JmlTypeClauseInitializer that) { shouldNotBeCalled(that); } @Override public void visitJmlTypeClauseConstraint(JmlTypeClauseConstraint that) { shouldNotBeCalled(that); } @Override public void visitJmlTypeClauseRepresents(JmlTypeClauseRepresents that) { shouldNotBeCalled(that); } @Override public void visitJmlTypeClauseConditional(JmlTypeClauseConditional that) { shouldNotBeCalled(that); } @Override public void visitJmlTypeClauseMonitorsFor(JmlTypeClauseMonitorsFor that) { shouldNotBeCalled(that); } // These do not need to be implemented @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 visitJmlStatementSpec(JmlStatementSpec that) { shouldNotBeCalled(that); } /** 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 visitJmlClassDecl(JmlClassDecl that) { // Nested classes are found in JmlEsc. We get to this point if there is a local // class declaration within method body. JmlEsc.instance(context).visitClassDef(that); } // OK - e.g., assert, assume, comment statements @Override public void visitJmlStatementExpr(JmlStatementExpr that) { currentBlock.statements.add(that); } // OK @Override public void visitJmlStatementHavoc(JmlStatementHavoc that) { notImpl(that); } }