package org.jmlspecs.openjml.esc; import java.io.IOException; import java.io.PrintWriter; import java.nio.file.FileSystems; import java.nio.file.Path; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import javax.tools.JavaFileObject; import org.jmlspecs.openjml.*; import org.jmlspecs.openjml.JmlTree.JmlBinary; import org.jmlspecs.openjml.JmlTree.JmlClassDecl; import org.jmlspecs.openjml.JmlTree.JmlDoWhileLoop; import org.jmlspecs.openjml.JmlTree.JmlForLoop; import org.jmlspecs.openjml.JmlTree.JmlMethodDecl; import org.jmlspecs.openjml.JmlTree.JmlMethodInvocation; import org.jmlspecs.openjml.JmlTree.JmlMethodSpecs; import org.jmlspecs.openjml.JmlTree.JmlQuantifiedExpr; import org.jmlspecs.openjml.JmlTree.JmlStatement; import org.jmlspecs.openjml.JmlTree.JmlStatementExpr; import org.jmlspecs.openjml.JmlTree.JmlVariableDecl; import org.jmlspecs.openjml.JmlTree.JmlWhileLoop; import org.jmlspecs.openjml.esc.BasicProgram.BasicBlock; import org.jmlspecs.openjml.proverinterface.IProverResult; import org.jmlspecs.openjml.proverinterface.IProverResult.Span; import org.jmlspecs.openjml.proverinterface.ProverResult; import org.smtlib.ICommand; import org.smtlib.IExpr; import org.smtlib.IPrinter; import org.smtlib.IResponse; import org.smtlib.IResponse.IError; import org.smtlib.IResponse.IPair; import org.smtlib.ISolver; import org.smtlib.IVisitor.VisitorException; import org.smtlib.SMT; import org.smtlib.command.C_assert; import org.smtlib.command.C_check_sat; import org.smtlib.command.C_push; import org.smtlib.sexpr.ISexpr; import com.sun.tools.javac.code.Symbol.MethodSymbol; import com.sun.tools.javac.code.Symbol.VarSymbol; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.TypeTag; import com.sun.tools.javac.tree.*; import com.sun.tools.javac.tree.JCTree.JCArrayAccess; 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.JCCase; import com.sun.tools.javac.tree.JCTree.JCConditional; import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.tree.JCTree.JCFieldAccess; import com.sun.tools.javac.tree.JCTree.JCIdent; import com.sun.tools.javac.tree.JCTree.JCIf; 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.JCStatement; import com.sun.tools.javac.tree.JCTree.JCSwitch; import com.sun.tools.javac.tree.JCTree.JCSynchronized; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import com.sun.tools.javac.util.Context; 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.Position; public class MethodProverSMT { final public static String separator = "--------------------------------------"; // OPTIONS SET WHEN prover() IS CALLED // /** true if counterexample information is desired - set when prove() is called */ // protected boolean showCounterexample; // // /** true if counterexample trace information is desired - set when prove() is called */ // protected boolean showTrace; /** true if subexpression trace information is desired * - set when prove() is called */ protected boolean showSubexpressions; /** The compilation context - cached at construction time */ final protected Context context; /** The compiler log for this context - cached at construction time */ final protected Log log; /** The Utils instance for this context - cached at construction time */ final protected Utils utils; /** The JmlEsc instance that is calling this method prover - cached at construction time */ final protected JmlEsc jmlesc; /** The JmlTreeUtils tool for this compilation context - cached at construction time*/ final protected JmlTreeUtils treeutils; /** The factory to produce IProverResult objects. This is initialized to use * ProverResult in the constructor, but the client can set a different * factory before calls to prove() */ public IProverResult.IFactory factory; /** The interface for new ITracer factories */ public interface ITracerFactory { public ITracer makeTracer(Context context, SMT smt, ISolver solver, Map<JCTree,String> cemap, BiMap<JCTree,JCExpression> jmap); } /** The factory to use to create Tracer objects, initialized to the one kind of tracer we currently have. */ public ITracerFactory tracerFactory = new ITracerFactory() { public ITracer makeTracer(Context context, SMT smt, ISolver solver, Map<JCTree,String> cemap, BiMap<JCTree,JCExpression> jmap) { return new Tracer(context,smt,solver,cemap,jmap); } }; /** A map filled in by running the tracer - it maps String versions of * expressions to a user-friendly String for display in the tracing output. * Examples are that null and this would typically be represented by the * solver as some internal reference to a solver constant. */ public Map<String,String> constantTraceMap = new HashMap<String,String>(); /** The object to use to trace counterexamples. */ public ITracer tracer; // DEBUGGING SETTINGS // /** local field used to enable verbose output for this object */ // protected boolean verbose; /** Just for debugging esc */ public static boolean escdebug = false; // May be set externally to enable debugging while testing /** true if trace information with respect to the basic block program is to be output * (for debugging only) - set when prove() is called */ protected boolean showBBTrace; /** Constructs an instance of this class to do static checking on a method. * The instance can be reused for multiple methods and classes * within the same compilation context. It does not retain any state of its * own, beyond the cached values of other tools in the compilation context. */ public MethodProverSMT(JmlEsc jmlesc) { this.jmlesc = jmlesc; this.context = jmlesc.context; this.log = Log.instance(context); this.utils = Utils.instance(context); this.treeutils = JmlTreeUtils.instance(context); this.factory = new IProverResult.IFactory() { @Override public IProverResult makeProverResult(MethodSymbol msym, String prover, IProverResult.Kind kind, java.util.Date start) { ProverResult pr = new ProverResult(prover,kind,msym); pr.methodSymbol = msym; if (start != null) { pr.setDuration((pr.timestamp().getTime()-start.getTime())/1000.); pr.setTimestamp(start); } return pr; } }; } /** Returns the prover exec specified by the options */ public /*@ nullable */ String pickProverExec(String proverToUse) { String exec = JmlOption.value(context, JmlOption.PROVEREXEC); if (exec == null) exec = JmlOption.value(context, Strings.proverPropertyPrefix + proverToUse); return exec; } /** The entry point to initiate proving a method. In the current implementation * the methodDecl is a method of the original AST and the original AST must * already be translated using the JmlAssertionAdder instance that is in * jmlesc.assertionAdder. Various information is printed about the proof attempt, and * the result of the proof attempt is also returned in an IProverResult object. * <P> * The amount and kind of printing depends on various options, such as * -trace, -subexpressions, -show as well as debugging and verbosity flags. */ public IProverResult prove(JmlMethodDecl methodDecl, String proverToUse) { escdebug = escdebug || utils.jmlverbose >= Utils.JMLDEBUG; boolean verbose = escdebug || JmlOption.isOption(context,"-verbose") // The Java verbose option || utils.jmlverbose >= Utils.JMLVERBOSE; this.showSubexpressions = verbose || JmlOption.isOption(context,JmlOption.SUBEXPRESSIONS); boolean showTrace = this.showSubexpressions || JmlOption.isOption(context,JmlOption.TRACE); boolean showCounterexample = JmlOption.isOption(context,JmlOption.COUNTEREXAMPLE); this.showBBTrace = escdebug; log.useSource(methodDecl.sourcefile); int prevErrors = log.nerrors; boolean print = jmlesc.verbose; boolean printPrograms = jmlesc.verbose || JmlOption.isOption(context, JmlOption.SHOW); JmlClassDecl currentClassDecl = utils.getOwner(methodDecl); // FIXME - when might methodDecl.sym be null? JmlMethodSpecs denestedSpecs = methodDecl.sym == null ? null : JmlSpecs.instance(context).getDenestedSpecs(methodDecl.sym); // newblock is the translated version of the method body JmlMethodDecl translatedMethod = jmlesc.assertionAdder.methodBiMap.getf(methodDecl); if (translatedMethod == null) { log.warning("jml.internal","No translated method for " + utils.qualifiedMethodSig(methodDecl.sym)); return factory.makeProverResult(methodDecl.sym,proverToUse,IProverResult.SKIPPED,null); } JCBlock newblock = translatedMethod.getBody(); if (newblock == null) { log.error("esc.no.typechecking",methodDecl.name.toString()); //$NON-NLS-1$ return factory.makeProverResult(methodDecl.sym,proverToUse,IProverResult.ERROR,null); } if (printPrograms) { log.getWriter(WriterKind.NOTICE).println(Strings.empty); log.getWriter(WriterKind.NOTICE).println(separator); log.getWriter(WriterKind.NOTICE).println(Strings.empty); log.getWriter(WriterKind.NOTICE).println("TRANSFORMATION OF " + utils.qualifiedMethodSig(methodDecl.sym)); //$NON-NLS-1$ log.getWriter(WriterKind.NOTICE).println(JmlPretty.write(newblock)); } // determine the executable String exec = pickProverExec(proverToUse); if (exec == null || exec.trim().isEmpty()) { log.error("esc.no.exec",proverToUse); //$NON-NLS-1$ return factory.makeProverResult(methodDecl.sym,proverToUse,IProverResult.ERROR,null); } // create an SMT object, adding any options SMT smt = new SMT(); smt.processCommandLine(new String[]{}, smt.smtConfig); Object o = JmlOption.value(context, JmlOption.TIMEOUT); if (o != null && !o.toString().isEmpty()) { try { smt.smtConfig.timeout = Double.parseDouble(o.toString()); } catch (NumberFormatException e) { // FIXME - issue a warning } } // Add a listener for errors and start the solver. // The listener is set to use the defaultPrinter for printing // SMT abstractions and forwards all informational and error messages // to the OpenJML log mechanism smt.smtConfig.log.addListener(new SMTListener(log,smt.smtConfig.defaultPrinter)); SMTTranslator smttrans = new SMTTranslator(context, methodDecl.sym.toString()); ISolver solver; IResponse solverResponse = null; BasicBlocker2 basicBlocker; BasicProgram program; Date start; ICommand.IScript script; boolean usePushPop = true; // FIXME - false is not working yet { // now convert to basic block form basicBlocker = new BasicBlocker2(context); program = basicBlocker.convertMethodBody(newblock, methodDecl, denestedSpecs, currentClassDecl, jmlesc.assertionAdder); if (printPrograms) { log.getWriter(WriterKind.NOTICE).println(Strings.empty); log.getWriter(WriterKind.NOTICE).println(separator); log.getWriter(WriterKind.NOTICE).println(Strings.empty); log.getWriter(WriterKind.NOTICE).println("BasicBlock2 FORM of " + utils.qualifiedMethodSig(methodDecl.sym)); log.getWriter(WriterKind.NOTICE).println(program.toString()); } // convert the basic block form to SMT try { script = smttrans.convert(program,smt); if (printPrograms) { try { log.getWriter(WriterKind.NOTICE).println(Strings.empty); log.getWriter(WriterKind.NOTICE).println(separator); log.getWriter(WriterKind.NOTICE).println(Strings.empty); log.getWriter(WriterKind.NOTICE).println("SMT TRANSLATION OF " + utils.qualifiedMethodSig(methodDecl.sym)); org.smtlib.sexpr.Printer.WithLines.write(new PrintWriter(log.getWriter(WriterKind.NOTICE)),script); log.getWriter(WriterKind.NOTICE).println(); log.getWriter(WriterKind.NOTICE).println(); } catch (VisitorException e) { log.getWriter(WriterKind.NOTICE).print("Exception while printing SMT script: " + e); //$NON-NLS-1$ } } } catch (Exception e) { log.error("jml.internal", "Failed to convert to SMT: " + e); return factory.makeProverResult(methodDecl.sym,proverToUse,IProverResult.ERROR,new Date()); } // Starts the solver (and it waits for input) start = new Date(); setBenchmark(proverToUse,methodDecl.name.toString(),smt.smtConfig); solver = smt.startSolver(smt.smtConfig,proverToUse,exec); if (solver == null) { log.error("jml.solver.failed.to.start",exec); return factory.makeProverResult(methodDecl.sym,proverToUse,IProverResult.ERROR,start); } else { // Try the prover if (verbose) log.getWriter(WriterKind.NOTICE).println("EXECUTION"); //$NON-NLS-1$ try { solverResponse = script.execute(solver); // Note - the solver knows the smt configuration } catch (Exception e) { // Not sure there is anything to worry about, but just in case log.error("jml.esc.badscript", methodDecl.getName(), e.toString()); //$NON-NLS-1$ return factory.makeProverResult(methodDecl.sym,proverToUse,IProverResult.ERROR,start); } } } // Now assemble and report the result if (verbose) { log.getWriter(WriterKind.NOTICE).println("Proof result is " + smt.smtConfig.defaultPrinter.toString(solverResponse)); } IProverResult proofResult = null; { IResponse unsatResponse = smt.smtConfig.responseFactory.unsat(); if (solverResponse.isError()) { solver.exit(); log.error("jml.esc.badscript", methodDecl.getName(), smt.smtConfig.defaultPrinter.toString(solverResponse)); //$NON-NLS-1$ return factory.makeProverResult(methodDecl.sym,proverToUse,IProverResult.ERROR,start); } if (solverResponse.equals(unsatResponse)) { if (verbose) log.getWriter(WriterKind.NOTICE).println("Method checked OK"); proofResult = factory.makeProverResult(methodDecl.sym,proverToUse,IProverResult.UNSAT,start); if (!Strings.feasibilityContains(Strings.feas_none,context)) { boolean allFeasibilities = Strings.feasibilityContains(Strings.feas_all,context) || Strings.feasibilityContains(Strings.feas_debug,context); if (usePushPop) { solver.pop(1); // Pop off previous check_sat } else { solver.exit(); } java.util.List<JmlStatementExpr> checks = jmlesc.assertionAdder.assumeChecks.get(methodDecl); int k = 0; if (checks != null) for (JmlStatementExpr stat: checks) { ++k; // if (k < 290) continue; // if (k > 100) break; if (prevErrors != log.nerrors) break; // Only do the feasibility check if called for by the feasibility option if (!allFeasibilities && !Strings.feasibilityContains(stat.description,context)) continue; if (!usePushPop) { ISolver solver2 = smt.startSolver(smt.smtConfig,proverToUse,exec); if (JmlAssertionAdder.useAssertCount) { List<ICommand> commands = script.commands(); commands.remove(commands.size()-1); ICommand c = commands.remove(commands.size()-1); if (c instanceof C_push) { commands.remove(commands.size()-1); commands.remove(commands.size()-1); } JCExpression bin = treeutils.makeBinary(Position.NOPOS,JCTree.Tag.EQ,treeutils.inteqSymbol, treeutils.makeIdent(Position.NOPOS,jmlesc.assertionAdder.assumeCheckSym), treeutils.makeIntLiteral(Position.NOPOS, k)); commands.add(new C_assert(smttrans.convertExpr(bin))); commands.add(new C_check_sat()); } else { script = smttrans.convert(program,smt); } // if (printPrograms) { // try { // log.getWriter(WriterKind.NOTICE).println(Strings.empty); // log.getWriter(WriterKind.NOTICE).println(separator); // log.getWriter(WriterKind.NOTICE).println(Strings.empty); // log.getWriter(WriterKind.NOTICE).println("SMT TRANSLATION OF " + utils.qualifiedMethodSig(methodDecl.sym)); // org.smtlib.sexpr.Printer.WithLines.write(new PrintWriter(log.getWriter(WriterKind.NOTICE)),script); // log.getWriter(WriterKind.NOTICE).println(); // log.getWriter(WriterKind.NOTICE).println(); // } catch (VisitorException e) { // log.getWriter(WriterKind.NOTICE).print("Exception while printing SMT script: " + e); //$NON-NLS-1$ // } // } try { solverResponse = script.execute(solver2); // Note - the solver knows the smt configuration solver2.exit(); } catch (Exception e) { // Not sure there is anything to worry about, but just in case log.error("jml.esc.badscript", methodDecl.getName(), e.toString()); //$NON-NLS-1$ return factory.makeProverResult(methodDecl.sym,proverToUse,IProverResult.ERROR,start); } } if (usePushPop) { solver.pop(1); // Pop off previous setting of assumeCheck solver.push(1); // Mark the top JCExpression bin = treeutils.makeBinary(Position.NOPOS,JCTree.Tag.EQ,treeutils.inteqSymbol, treeutils.makeIdent(Position.NOPOS,jmlesc.assertionAdder.assumeCheckSym), treeutils.makeIntLiteral(Position.NOPOS, k)); solver.assertExpr(smttrans.convertExpr(bin)); solverResponse = solver.check_sat(); } String description = stat.description; // + " " + stat; String loc = utils.qualifiedName(methodDecl.sym); // FIXME - get rid of the next line some time when we can change the test results if (Utils.testingMode) loc = ""; else loc = loc + " "; String msg = (utils.jmlverbose >= Utils.PROGRESS) ? ("Feasibility check #" + k + " - " + description + " : ") :("Feasibility check - " + description + " : "); boolean infeasible = solverResponse.equals(unsatResponse); utils.progress(0,1,loc + msg + (infeasible ? "infeasible": "OK")); if (infeasible) { if (Strings.preconditionAssumeCheckDescription.equals(description)) { log.warning(stat.pos(), "esc.infeasible.preconditions", utils.qualifiedMethodSig(methodDecl.sym)); proofResult = factory.makeProverResult(methodDecl.sym,proverToUse,IProverResult.INFEASIBLE,start); // If the preconditions are inconsistent, all paths will be infeasible break; } else { log.warning(stat.pos(), "esc.infeasible.assumption", description, utils.qualifiedMethodSig(methodDecl.sym)); proofResult = factory.makeProverResult(methodDecl.sym,proverToUse,IProverResult.INFEASIBLE,start); } } else if (solverResponse.isError()) { if (usePushPop) solver.exit(); log.error("jml.esc.badscript", methodDecl.getName(), smt.smtConfig.defaultPrinter.toString(solverResponse)); //$NON-NLS-1$ return factory.makeProverResult(methodDecl.sym,proverToUse,IProverResult.ERROR,start); } } } } else b: { // Proof was not UNSAT, so there may be a counterexample int count = Utils.instance(context).maxWarnings; boolean byPath = JmlOption.isOption(context, JmlOption.MAXWARNINGSPATH); ProverResult pr = (ProverResult)factory.makeProverResult(methodDecl.sym,proverToUse, solverResponse.toString().equals("sat") ? IProverResult.SAT : IProverResult.POSSIBLY_SAT,start); proofResult = pr; while (prevErrors == log.nerrors) { if (solverResponse.isError()) { solver.exit(); log.error("jml.esc.badscript", methodDecl.getName(), smt.smtConfig.defaultPrinter.toString(solverResponse)); //$NON-NLS-1$ return factory.makeProverResult(methodDecl.sym,proverToUse,IProverResult.ERROR,start); } if (solverResponse.equals(smt.smtConfig.responseFactory.unknown())) { IResponse unknownReason = solver.get_info(smt.smtConfig.exprFactory.keyword(":reason-unknown")); // Not widely supported if (unknownReason.equals(smt.smtConfig.responseFactory.unsupported())) { // continue } else if (unknownReason instanceof IResponse.IAttributeList) { IResponse.IAttributeList attrList = (IResponse.IAttributeList)unknownReason; IExpr.IAttributeValue value = attrList.attributes().get(0).attrValue(); if (value.toString().contains("incomplete")) { // FIXME - this might be only CVC4 // continue on } else if (value.toString().equals("ok")) { // FIXME - this might be only Z3 // continue on } else { String msg = "Aborted proof: " + smt.smtConfig.defaultPrinter.toString(value); unknownReason = smt.smtConfig.responseFactory.error(msg); boolean timeout = msg.contains("timeout"); if (timeout) { log.warning(methodDecl,"esc.resourceout",": " + msg); proofResult = factory.makeProverResult(methodDecl.sym,proverToUse,IProverResult.TIMEOUT,start); break b; } } } else { // Unexpected result log.error("jml.internal.notsobad","Unexpected result when querying SMT solver for reason for an unknown result: " + unknownReason); break b; } // Instead, try to get a simple value and see if there is a model IResponse r = solver.get_value(smt.smtConfig.exprFactory.symbol("NULL")); if (r.isError()) { String msg = ": "; if (JmlOption.value(context,JmlOption.TIMEOUT) != null) msg = " (possible timeout): "; log.warning(methodDecl,"esc.nomodel","method " + utils.qualifiedName(methodDecl.sym) + " - " + msg + r); proofResult = factory.makeProverResult(methodDecl.sym,proverToUse,IProverResult.UNKNOWN,start); break b; } } // If we don't clearly know the prover failed, we try to get a simple value and see if there is a model IResponse r = solver.get_value(smt.smtConfig.exprFactory.symbol("NULL")); if (r.isError()) { String msg = ": "; if (JmlOption.value(context,JmlOption.TIMEOUT) != null) msg = " (possible timeout): "; log.warning(methodDecl,"esc.nomodel",msg + r); proofResult = factory.makeProverResult(methodDecl.sym,proverToUse,IProverResult.UNKNOWN,start); break b; } if (print) log.getWriter(WriterKind.NOTICE).println("Some assertion is not valid"); // FIXME - decide how to show counterexamples when there is no tracing Map<JCTree,String> cemap = constructCounterexample(jmlesc.assertionAdder,basicBlocker,smttrans,smt,solver); BiMap<JCTree,JCExpression> jmap = jmlesc.assertionAdder.exprBiMap.compose(basicBlocker.bimap); tracer = tracerFactory.makeTracer(context,smt,solver,cemap,jmap); // Report JML-labeled values and the path to the failed invariant { tracer.appendln(JmlTree.eol + "TRACE of " + utils.qualifiedMethodSig(methodDecl.sym)); if (utils.jmlverbose >= Utils.JMLVERBOSE) tracer.appendln("Constants"); populateConstantMap(smt, solver, cemap, smttrans); } path = new ArrayList<IProverResult.Span>(); JCExpression pathCondition = reportInvalidAssertion( program,smt,solver,methodDecl,cemap,jmap, jmlesc.assertionAdder.pathMap, basicBlocker.pathmap); if (showTrace) log.getWriter(WriterKind.NOTICE).println(tracer.text()); // FIXME - decide how to show counterexamples when there is no tracing if (showCounterexample) { log.getWriter(WriterKind.NOTICE).println("\nCOUNTEREXAMPLE"); for (VarSymbol v: basicBlocker.premap.keySet()) { Name n = basicBlocker.premap.getName(v); String ns = n.toString(); if (ns.equals("this")) continue; // FIXME - use symbols for these if (ns.equals("length")) continue; if (ns.equals("_alloc__")) continue; if (ns.equals("_heap__")) continue; String s = getValue(n.toString(),smt,solver); log.getWriter(WriterKind.NOTICE).println(n.toString() + " = " + s); } log.getWriter(WriterKind.NOTICE).println(Strings.empty); } if (pathCondition != null) { Counterexample ce = new Counterexample(tracer.text(),cemap,path); pr.add(ce); // TODO - make more abstract } if (pathCondition == null) { break; } if (--count <= 0) break; solver.pop(1); // pops off all of the previous check_sat solver.assertExpr(smttrans.convertExpr(pathCondition)); solver.push(1); // mark the top again solverResponse = solver.check_sat(); if (solverResponse.isError()) { log.error("jml.esc.badscript", methodDecl.getName(), smt.smtConfig.defaultPrinter.toString(solverResponse)); //$NON-NLS-1$ return factory.makeProverResult(methodDecl.sym,proverToUse,IProverResult.ERROR,start); } if (solverResponse.equals(unsatResponse)) break; // TODO - checking each assertion separately } pr.setDuration((new Date().getTime() - pr.timestamp().getTime())/1000.); } } if (usePushPop) solver.exit(); smt.smtConfig.logfile = null; // saveBenchmark(proverToUse,methodDecl.name.toString()); // jmlesc.mostRecentProgram = program; if (prevErrors != log.nerrors) return factory.makeProverResult(methodDecl.sym,proverToUse,IProverResult.ERROR,start); return proofResult; } protected List<IProverResult.Span> path; public void populateConstantMap(SMT smt, ISolver solver, Map<JCTree,String> cemap, SMTTranslator smttrans) { addToConstantMap(smttrans.NULL,smt,solver,cemap); addToConstantMap(smttrans.thisSym.toString(),smt,solver,cemap); // for (Type t : smttrans.javaTypes) { // String s = smttrans.javaTypeSymbol(t).toString(); // FIXME - need official printer // addToConstantMap(s,smt,solver,cemap); // s = smttrans.jmlTypeSymbol(t).toString(); // FIXME - need official printer // addToConstantMap(s,smt,solver,cemap); // } } public void addToConstantMap(String id, SMT smt, ISolver solver, Map<JCTree,String> cemap) { String result = getValue(id,smt,solver); if (result != null) constantTraceMap.put(result,id); if (utils.jmlverbose >= Utils.JMLVERBOSE) { tracer.appendln("\t\t\tVALUE: " + id + "\t === " + result); } } public void addToConstantMap(JCExpression e, Map<JCTree,String> cemap, SMTTranslator smttrans) { String result = cemap.get(e); String expr = smttrans.convertExpr(e).toString();// TODO - use the pretty printer? if (result != null) constantTraceMap.put(result,e.toString()); if (e.type.getTag() == TypeTag.CHAR) result = showChar(result); if (utils.jmlverbose >= Utils.JMLVERBOSE) tracer.appendln("\t\t\tVALUE: " + expr + "\t === " + result); } Set<String> blocks = new java.util.HashSet<String>(); /** Iterates through the basic blocks to find and report the invalid assertion * that caused the SAT result from the prover. */ public JCExpression reportInvalidAssertion(BasicProgram program, SMT smt, ISolver solver, JCMethodDecl decl, Map<JCTree,String> cemap, BiMap<JCTree,JCExpression> jmap, BiMap<JCTree,JCTree> aaPathMap, BiMap<JCTree,JCTree> bbPathMap) { exprValues = new HashMap<JCTree,String>(); blocks.clear(); JCExpression pathCondition = reportInvalidAssertion(program.startBlock(),smt,solver,decl,0, JmlTreeUtils.instance(context).falseLit,cemap,jmap,aaPathMap,bbPathMap); if (pathCondition == null) { log.warning("jml.internal.notsobad","Could not find an invalid assertion even though the proof result was satisfiable: " + decl.sym); //$NON-NLS-1$ //$NON-NLS-2$ return null; } return pathCondition; } Map<JCTree,String> exprValues = new HashMap<JCTree,String>(); // These strings must mirror the strings used in JmlAsssertionAdder.visitJmlLblExpression private final static String prefix_lblpos = Strings.labelVarString + JmlTokenKind.BSLBLPOS.internedName().substring(1) + "_"; private final static String prefix_lblneg = Strings.labelVarString + JmlTokenKind.BSLBLNEG.internedName().substring(1) + "_"; private final static String prefix_lbl = Strings.labelVarString + JmlTokenKind.BSLBLANY.internedName().substring(1) + "_"; public int checkTerminationPosition(String id, int terminationPos) { // The BasicBlocker2 implementation creates special RETURN and // THROWS blocks that just hold those statements. Thus we can // identify terminating statements. This is relevant in the situations // in which the invalid assertion is a postcondition - then we want // to know which path through the method (ending at a return or // throws statement) causes the bad postcondition. Note also that // a return or throws statement might be overridden by a subsequent // return or throws statement in a later finally block. // The extraction of the terminationPos from the block id depends // on the format of the block id as generated in BasicBlocker2 int k = id.indexOf(BasicBlockerParent.RETURN); if (k < 0) k = id.indexOf(BasicBlockerParent.THROW); if (k > 0) { int kk = BasicBlockerParent.blockPrefix.length(); terminationPos = Integer.parseInt(id.substring(kk,k)); } return terminationPos; } /** Helper method for iterating through the basic blocks to find and report the invalid assertion * that caused the SAT result from the prover; * Returns true if an invalid assertion was found and reported */ // How this works: If there is an invalid assertion, then the value of the blockid // of the bloc containing that assertion is false; recursively, the blockid // is false for any block that has a following block with a false blockid. // Thus if there is an invalid assertion the start block is false and there is // a path of false blocks to the invalid assertion. There could possibly be // other blocks with false ids as well. public JCExpression reportInvalidAssertion(BasicProgram.BasicBlock block, SMT smt, ISolver solver, JCMethodDecl decl, int terminationPos, JCExpression pathCondition, Map<JCTree,String> cemap, BiMap<JCTree,JCExpression> jmap, BiMap<JCTree,JCTree> aaPathMap, BiMap<JCTree,JCTree> bbPathMap) { String id = block.id.name.toString(); Boolean value = getBoolValue(id,smt,solver); if (value == null) { // FIXME - error and what to do ? return null; } if (utils.jmlverbose >= Utils.JMLVERBOSE && (JmlOption.isOption(context,JmlOption.COUNTEREXAMPLE) || JmlOption.isOption(context,JmlOption.SUBEXPRESSIONS))) { tracer.appendln("Block " + id + " is " + value); //$NON-NLS-1$//$NON-NLS-2$ } if (value) { // The value of the block id is true, so we don't pursue it return null; } terminationPos = checkTerminationPosition(id,terminationPos); pathCondition = JmlTreeUtils.instance(context).makeOr(Position.NOPOS,pathCondition,block.id); //showTrace = true; // FIXME - would like to have a range, not just a single position point, // for the terminationPos for (JCStatement stat: block.statements()) { // Report any statements that are JML-labeled if (stat instanceof JCVariableDecl) { Name n = ((JCVariableDecl)stat).name; String ns = n.toString(); if (ns.startsWith(Strings.labelVarString)) { int k = ns.lastIndexOf(Strings.underscore); if (ns.startsWith(prefix_lblpos)) { Boolean b = getBoolValue(ns,smt,solver); String label = ns.substring(prefix_lblpos.length(),k); if (b == null) log.warning(stat.pos,"esc.label.value",label,"is unknown"); //$NON-NLS-1$ else if (b) log.warning(stat.pos,"esc.label.value",label,b); //$NON-NLS-1$ } else if (ns.startsWith(prefix_lblneg)) { Boolean b = getBoolValue(ns,smt,solver); String label = ns.substring(prefix_lblneg.length(),k); if (b == null) log.warning(stat.pos,"esc.label.value",label,"is unknown"); //$NON-NLS-1$ else if (!b) log.warning(stat.pos,"esc.label.value",label,b); //$NON-NLS-1$ } else if (ns.startsWith(prefix_lbl)) { String b = getValue(ns,smt,solver); String label = ns.substring(prefix_lbl.length(),k); if (b == null) log.warning(stat.pos,"esc.label.value",label,"is unknown"); //$NON-NLS-1$ else log.warning(stat.pos,"esc.label.value",label,b); //$NON-NLS-1$ } else { log.warning(stat.pos,"jml.internal.notsobad","Unknown label: " + ns); //$NON-NLS-1$ } } } { JCStatement bbstat = stat; JCTree origStat = aaPathMap.getr(bbstat); String comment = bbstat instanceof JmlStatementExpr && ((JmlStatementExpr)bbstat).expression instanceof JCLiteral ? ((JCLiteral)((JmlStatementExpr)bbstat).expression).value.toString() : null; ifstat: if (origStat != null) { String loc = utils.locationString(origStat.getStartPosition()); //String comment = ((JCLiteral)((JmlStatementExpr)bbstat).expression).value.toString(); int sp=-2,ep=-2; // The -2 is different from NOPOS and (presumably) any other value that might be generated below int spanType = Span.NORMAL; JCTree toTrace = null; String val = null; if (origStat instanceof JCIf) { toTrace = ((JCIf)origStat).getCondition(); } else if (origStat instanceof JCSwitch) { toTrace = ((JCSwitch)origStat).getExpression(); sp = toTrace.getStartPosition(); ep = toTrace.getEndPosition(log.currentSource().getEndPosTable()); } else if (origStat instanceof JCCase) { toTrace = ((JCCase)origStat).getExpression(); sp = origStat.getStartPosition(); // 8 is the length of "default:" ep = toTrace == null ? sp + 8 : toTrace.getEndPosition(log.currentSource().getEndPosTable()); // } else if (origStat instanceof JCCatch) { // catches come as JmlVariableDecls // JCVariableDecl d = ((JCCatch)origStat).param; // sp = origStat.getStartPosition(); // ep = d.getEndPosition(log.currentSource().getEndPosTable()); // toTrace = null ; // FIXME - not working correctly yet } else if (origStat instanceof JCSynchronized) { toTrace = ((JCSynchronized)origStat).getExpression(); } else if (origStat instanceof JmlForLoop) { JmlForLoop s = (JmlForLoop)origStat; sp = s.getStartPosition(); ep = s.getStatement().getStartPosition(); // FIXME - what about the initalizer etc. } else if (origStat instanceof JmlWhileLoop) { JmlWhileLoop s = (JmlWhileLoop)origStat; sp = s.getStartPosition(); ep = s.getStatement().getStartPosition(); // FIXME - what about the initalizer etc. } else if (origStat instanceof JmlDoWhileLoop) { JmlDoWhileLoop s = (JmlDoWhileLoop)origStat; sp = s.getCondition().getStartPosition(); ep = s.getCondition().getEndPosition(log.currentSource().getEndPosTable()); // } else if (origStat instanceof JmlEnhancedForLoop) { // JmlEnhancedForLoop s = (JmlEnhancedForLoop)origStat; // sp = s.getCondition().getStartPosition(); // ep = s.getCondition().getEndPosition(log.currentSource().getEndPosTable()); } else if (origStat instanceof JmlVariableDecl) { JmlVariableDecl s = (JmlVariableDecl)origStat; sp = s.getStartPosition(); ep = s.getEndPosition(log.currentSource().getEndPosTable()); toTrace = s.ident; tracer.appendln(loc + " \t" + comment); if (toTrace != null && showSubexpressions) tracer.trace(s.init); if (toTrace != null && showSubexpressions) tracer.trace(s.ident); break ifstat; // } else if (stat instanceof JmlStatementExpr && ((JmlStatementExpr)stat).token == JmlTokenKind.COMMENT && ((JmlStatementExpr)stat).expression.toString().contains("ImplicitAssume")) { // break ifstat; } else if (stat instanceof JmlStatementExpr && ((JmlStatementExpr)stat).token == JmlTokenKind.ASSUME) { toTrace = ((JmlStatementExpr)stat).expression; // } else if (comment.startsWith("AssumeCheck assertion")) { // break ifstat; } else { toTrace = origStat; } if (sp == -2) { sp = toTrace.getStartPosition(); ep = toTrace.getEndPosition(log.currentSource().getEndPosTable()); val = cemap.get(toTrace); spanType = val == null ? Span.NORMAL : val.equals("true") ? Span.TRUE : Span.FALSE; } //log.getWriter(WriterKind.NOTICE).println("SPAN " + sp + " " + ep + " " + spanType); if (sp != Position.NOPOS) { if (ep >= sp) path.add(new Span(sp,ep,spanType)); // else log.warning(Position.NOPOS,"jml.internal.notsobad","Incomplete position information (" + sp + " " + ep + ") for " + origStat); } else { // log.warning(Position.NOPOS,"jml.internal.notsobad","Incomplete position information (" + sp + " " + ep + ") for " + origStat); } if (comment.startsWith("AssumeCheck assertion")) break ifstat; tracer.appendln(loc + " \t" + comment); if (toTrace != null && showSubexpressions) tracer.trace(toTrace); String s = ((JmlStatementExpr)bbstat).id; if (toTrace != null && s != null) { tracer.appendln("\t\t\t\t" + s + " = " + cemap.get(toTrace)); } } else if (aaPathMap.reverse.keySet().contains(bbstat)) { String loc = utils.locationString(bbstat.getStartPosition()); //String comment = ((JCLiteral)((JmlStatementExpr)bbstat).expression).value.toString(); tracer.appendln(loc + " \t" + comment); } else if (comment != null) { tracer.appendln(" \t//" + comment); } } if (showBBTrace) { log.getWriter(WriterKind.NOTICE).println("STATEMENT: " + stat); if (stat instanceof JmlStatementExpr) { JmlStatementExpr x = (JmlStatementExpr)stat; traceSubExpr(x.expression); log.getWriter(WriterKind.NOTICE).println(tracer.text()); tracer.clear(); } else if (stat instanceof JCVariableDecl) { JCVariableDecl vd = (JCVariableDecl)stat; Name n = vd.name; if (vd.init != null) traceSubExpr(vd.init); log.getWriter(WriterKind.NOTICE).println("DECL: " + n + " === " + getValue(n.toString(),smt,solver)); } } if (stat instanceof JmlStatementExpr && ((JmlStatementExpr)stat).token == JmlTokenKind.COMMENT) { JmlStatementExpr s = (JmlStatementExpr)stat; if (s.id == null || !s.id.startsWith("ACHECK")) continue; if (s.optionalExpression != null) { log.getWriter(WriterKind.NOTICE).println("FOUND " + s.id); return pathCondition; } } if (stat instanceof JmlStatementExpr && ((JmlStatementExpr)stat).token == JmlTokenKind.ASSERT) { JmlStatementExpr assertStat = (JmlStatementExpr)stat; JCExpression e = assertStat.expression; Label label = assertStat.label; if (e instanceof JCTree.JCLiteral) { value = ((JCTree.JCLiteral)e).value.equals(1); // Boolean literals have 0 and 1 value } else if (e instanceof JCTree.JCParens) { value = ((JCTree.JCLiteral)((JCTree.JCParens)e).expr).value.equals(1); // Boolean literals have 0 and 1 value } else if (e instanceof JCIdent){ id = e.toString(); // Relies on all assert statements being reduced to identifiers value = getBoolValue(id,smt,solver); } else { return pathCondition; // For when assert statements are not identifiers } if (!value) { boolean byPath = JmlOption.isOption(context, JmlOption.MAXWARNINGSPATH); if (byPath) pathCondition = JmlTreeUtils.instance(context).makeOr(Position.NOPOS, pathCondition, e); else pathCondition = e; if (terminationPos == 0) terminationPos = decl.pos; JavaFileObject prev = null; int pos = assertStat.pos; if (pos == Position.NOPOS || pos == decl.pos) { pos = terminationPos; prev = log.useSource(((JmlMethodDecl)decl).sourcefile); } else { if (assertStat.source != null) prev = log.useSource(assertStat.source); } String associatedLocation = Strings.empty; if (assertStat.associatedPos != Position.NOPOS && !Utils.testingMode) { associatedLocation = ": " + utils.locationString(assertStat.associatedPos,assertStat.associatedSource); } String extra = Strings.empty; JCExpression optional = assertStat.optionalExpression; if (optional != null) { if (optional instanceof JCTree.JCLiteral) extra = ": " + ((JCTree.JCLiteral)optional).getValue().toString(); //$NON-NLS-1$ } if (assertStat.description != null) { extra = ": " + assertStat.description; } if (JmlOption.isOption(context, JmlOption.SHOW)) log.getWriter(WriterKind.NOTICE).println("Failed assert: " + e.toString()); int epos = assertStat.getEndPosition(log.currentSource().getEndPosTable()); String loc; if (epos == Position.NOPOS || pos != assertStat.pos) { log.warning(pos,"esc.assertion.invalid",label,associatedLocation,utils.methodName(decl.sym),extra); //$NON-NLS-1$ loc = utils.locationString(pos); tracer.appendln(loc + " Invalid assertion (" + label + ")"); } else { // FIXME - migrate to using pos() for terminationPos as well log.warning(assertStat.getPreferredPosition(),"esc.assertion.invalid",label,associatedLocation,utils.methodName(decl.sym),extra); //$NON-NLS-1$ loc = utils.locationString(assertStat.getPreferredPosition()); tracer.appendln(loc + " Invalid assertion (" + label + ")"); } // TODO - above we include the optionalExpression as part of the error message // however, it is an expression, and not evaluated for ESC. Even if it is // a literal string, it is printed with quotes around it. if (assertStat.source != null) log.useSource(prev); if (assertStat.associatedPos != Position.NOPOS) { if (assertStat.associatedSource != null) prev = log.useSource(assertStat.associatedSource); log.warning(assertStat.associatedPos, Utils.testingMode ? "jml.associated.decl" : "jml.associated.decl.cf", loc); tracer.appendln(associatedLocation + " Associated location"); if (assertStat.associatedSource != null) log.useSource(prev); } // Found an invalid assertion, so we can terminate // Negate the path condition return pathCondition; } } } // Since we have not found an invalid assertion in this block, we // inspect each follower. Since the blocks form a DAG, this will // terminate. for (BasicBlock b: block.followers()) { JCExpression p = reportInvalidAssertion(b,smt,solver,decl,terminationPos,pathCondition,cemap,jmap,aaPathMap,bbPathMap); if (p != null) return p; } return null; // Did not find anything in this block or its followers } /** Query the solver for the (boolean) value of an id in the current model */ public Boolean getBoolValue(String id, SMT smt, ISolver solver) { String v = getValue(id,smt,solver); if (v == null) return null; return !v.contains("false"); } /** Query the solver for the (int) value of an id in the current model */ public int getIntValue(String id, SMT smt, ISolver solver) { return Integer.parseInt(getValue(id,smt,solver,true)); } public String getValue(String id, SMT smt, ISolver solver) { return getValue(id,smt,solver,true); } /** Query the solver for any type of value of an id in the current model */ public String getValue(String id, SMT smt, ISolver solver, boolean report) { org.smtlib.IExpr.ISymbol s = smt.smtConfig.exprFactory.symbol(id); IResponse resp = solver.get_value(s); String out; if (resp instanceof IResponse.IError) { if (report) log.error("jml.internal.notsobad", ((IResponse.IError)resp).errorMsg()); //$NON-NLS-1$ return null; } else if (resp == null) { if (report) log.error("jml.internal.notsobad", "Could not find value of assertion: " + id); //$NON-NLS-1$ return null; } else if (resp instanceof org.smtlib.sexpr.ISexpr.ISeq){ org.smtlib.sexpr.ISexpr se = ((org.smtlib.sexpr.ISexpr.ISeq)resp).sexprs().get(0); if (se instanceof org.smtlib.sexpr.ISexpr.ISeq) se = ((org.smtlib.sexpr.ISexpr.ISeq)se).sexprs().get(1); out = se.toString(); } else if (resp instanceof IResponse.IValueResponse) { out = ((IResponse.IValueResponse)resp).values().get(0).second().toString(); //FIXME use a printer instead of toString() } else { log.error("jml.internal.notsobad", "Unexpected response on requesting value of assertion: " + smt.smtConfig.defaultPrinter.toString(resp)); //$NON-NLS-1$ return null; } return ((Tracer)tracer).normalizeConstant(out); // FIXME - fix interface } /** Write out (through log.getWriter(WriterKind.NOTICE)) the values of the given expression * and, recursively, of any subexpressions. */ public void traceSubExpr(JCExpression e) { tracer.trace(e); } protected String showChar(String userString) { try { int i = Integer.parseInt(userString); userString = "'" + (char)i + "' (" + userString + ')'; } catch (Exception e) { // do nothing } return userString; } /** The interface for tracer objects */ public interface ITracer { void trace(JCTree that); void append(String s); void appendln(String s); String text(); void clear(); } /** This class walks the expression subtrees, printing out the value of each * subexpression. It is used by creating an instance of the Tracer (using * the constructor), and then calling scan() on an AST. scan() is called * recursively to find and print all expressions. Statements are not printed * but are scanned for any subexpressions. */ // Not static so we have access to getValue public class Tracer extends JmlTreeScanner implements ITracer { SMT smt; ISolver solver; Map<JCTree,String> cemap; Log log; String result; StringBuffer traceText = new StringBuffer(); public void append(String s) { traceText.append(s); } public void appendln(String s) { traceText.append(s); traceText.append(Strings.eol); } public void trace(JCTree that) { scan(that); } public String text() { return traceText.toString(); } public void clear() { traceText.setLength(0); } /** Not to be used by callers - this is set false by some visit methods * to prevent scan() from printing the value of the expression under * scrutiny. */ protected boolean print = true; protected boolean subexpressions = true; public Tracer(Context context, SMT smt, ISolver solver, Map<JCTree,String> cemap, BiMap<JCTree,JCExpression> jmap) { this.smt = smt; this.solver = solver; this.cemap = cemap; this.log = Log.instance(context); } public void scan(JCTree that) { try { if (subexpressions) super.scan(that); if (that instanceof JCExpression && !treeutils.isATypeTree((JCExpression)that)) { if (print) { String expr = that.toString(); String sv = cemap.get(that); if (sv == null && that instanceof JCIdent) { sv = getValue(expr,smt,solver,false); // Fail softly } String userString = normalizeConstant(sv); if (that.type.getTag() == TypeTag.BOOLEAN) { userString = userString.replaceAll("\\( _ bv0 1 \\)", "false"); userString = userString.replaceAll("\\( _ bv1 1 \\)", "true"); userString = userString.replaceAll("\\( not true \\)", "false"); userString = userString.replaceAll("\\( not false \\)", "true"); } if (that.type.getTag() == TypeTag.CHAR) userString = showChar(userString); traceText.append("\t\t\tVALUE: " + expr + "\t === " + userString); traceText.append(Strings.eol); } else print = true; } } catch (Exception e) { traceText.append("\t\t\tVALUE: " + that + "\t === " + "<Internal Exception>"); traceText.append(Strings.eol); } } public String normalizeConstant(String sv) { String userString = sv == null ? "???" : constantTraceMap.get(sv); if (userString == null) userString = sv; return userString; } /** Declarations are not expressions, but we want to print the final * value of the newly declared variable anyway. */ @Override public void visitJmlVariableDecl(JmlVariableDecl e) { scan(e.init); Name n = e.name; String sv = cemap.get(e); if (sv == null) { sv = getValue(n.toString(),smt,solver); cemap.put(e, sv); } if (e.type.getTag() == TypeTag.CHAR) sv = showChar(sv); traceText.append("\t\t\tVALUE: " + e.sym + " = " + (sv == null ? "???" : sv)); traceText.append(Strings.eol); } /** We only scan and print one branch of the conditional, that selected by the * condition. */ @Override public void visitConditional(JCConditional e) { scan(e.cond); String sv = cemap.get(e.cond); if (sv == null) { } else if (sv.equals("true")) { scan(e.truepart); } else { scan(e.falsepart); } } /** Overridden to handle short-circuit cases appropriately */ @Override public void visitBinary(JCBinary tree) { // Special handling of short-circuit cases if (tree.getTag() == JCTree.Tag.OR) { scan(tree.lhs); String v = cemap.get(tree.lhs); if ("false".equals(v)) scan(tree.rhs); } else if (tree.getTag() == JCTree.Tag.AND) { scan(tree.lhs); String v = cemap.get(tree.lhs); if ("true".equals(v)) scan(tree.rhs); } else { super.visitBinary(tree); } } /** Overridden to handle short-circuit cases appropriately */ @Override public void visitJmlBinary(JmlBinary tree) { // Special handling of short-circuit cases if (tree.op == JmlTokenKind.IMPLIES) { scan(tree.lhs); String v = cemap.get(tree.lhs); if ("true".equals(v)) scan(tree.rhs); } else if (tree.op == JmlTokenKind.REVERSE_IMPLIES) { scan(tree.lhs); String v = cemap.get(tree.lhs); if ("false".equals(v)) scan(tree.rhs); } else { super.visitJmlBinary(tree); } } /** Scans only subexpressions, so that the tracing of assignments is * more intuitive. */ public void scanLHS(JCTree that) { if (that == null || that instanceof JCIdent) { // skip } else if (that instanceof JCFieldAccess) { JCFieldAccess fa = (JCFieldAccess)that; scan(fa.selected); } else if (that instanceof JCArrayAccess) { JCArrayAccess aa = (JCArrayAccess)that; scan(aa.indexed); scan(aa.index); } else { log.warning(that.pos(), "jml.internal.notsobad", "Unexpected kind of AST in Tracer.scanLHS: " + that.getClass()); } } /** Left-hand sides take special handling; the LHS is evaluated before * the RHS, so we can it first. However, we want the final value of the * full LHS expression to be printed after all subexpressions. */ @Override public void visitAssign(JCAssign tree) { scanLHS(tree.lhs); scan(tree.rhs); } @Override public void visitAssignop(JCAssignOp tree) { // TODO: AssignOp statements have the annoyance that only the final // value of the LHS is reported. scanLHS(tree.lhs); scan(tree.rhs); } @Override public void visitApply(JCMethodInvocation tree) { scanLHS(tree.meth); // FIXME - typeArags? for (JCExpression a: tree.args) { scan(a); } if (tree.type.getTag() == TypeTag.VOID) print = false; } @Override public void visitJmlMethodInvocation(JmlMethodInvocation tree) { if (tree.token != JmlTokenKind.BSTYPELC) { for (JCExpression a: tree.args) { scan(a); } } } @Override public void visitJmlQuantifiedExpr(JmlQuantifiedExpr tree) { // We don't scan the interior of quantified expressions - // they don't have concrete values. The value of the // expression itself is reported by scan() } } static public String benchmarkName = null; static private int benchmarkCount = 0; // public void saveBenchmark(String solverName, String methodname) { // String benchmarkDir = JmlOption.value(context,JmlOption.BENCHMARKS); // if (benchmarkDir == null) return; // String n; // if (benchmarkName != null) { // if ("<init>".equals(methodname)) methodname = "INIT"; // int count = 0; // String root = benchmarkDir + "/" + benchmarkName + "." + methodname; // n = root + ".smt2"; // while (true) { // Path p = FileSystems.getDefault().getPath(n); // if (!java.nio.file.Files.exists(p)) break; // count++; // n = root + (-count) + ".smt2"; // } // } else { // benchmarkCount++; // n = String.format("%s/bench-%05d.smt2",benchmarkDir,benchmarkCount); // } // try { // Path p = FileSystems.getDefault().getPath(n); // java.nio.file.Files.deleteIfExists(p); // java.nio.file.Files.move(FileSystems.getDefault().getPath("solver.out.z3"),p); // } catch (IOException e) { // System.out.println(e); // } // } public void setBenchmark(String solverName, String methodname, SMT.Configuration config) { String benchmarkDir = JmlOption.value(context,JmlOption.BENCHMARKS); if (benchmarkDir == null) benchmarkDir = "benchmarks"; if (benchmarkDir == null) return; new java.io.File(benchmarkDir).mkdirs(); String n; if (benchmarkName != null) { if ("<init>".equals(methodname)) methodname = "INIT"; int count = 0; String root = benchmarkDir + "/" + benchmarkName + "." + methodname; n = root + ".smt2"; while (true) { Path p = FileSystems.getDefault().getPath(n); if (!java.nio.file.Files.exists(p)) break; count++; n = root + (-count) + ".smt2"; } } else { benchmarkCount++; n = String.format("%s/bench-%05d.smt2",benchmarkDir,benchmarkCount); } // try { config.logfile = n; // Path p = FileSystems.getDefault().getPath(n); // java.nio.file.Files.deleteIfExists(p); // java.nio.file.Files.move(FileSystems.getDefault().getPath("solver.out.z3"),p); // } catch (IOException e) { // System.out.println(e); // } } /** Construct the mapping from original source subexpressions to values in the current solver model. */ public Map<JCTree,String> constructCounterexample(JmlAssertionAdder assertionAdder, BasicBlocker2 basicBlocker, SMTTranslator smttrans, SMT smt, ISolver solver) { boolean verbose = false; if (verbose) { log.getWriter(WriterKind.NOTICE).println("ORIGINAL <==> TRANSLATED"); for (JCTree e: assertionAdder.exprBiMap.forward.keySet()) { if (!(e instanceof JCExpression) && !(e instanceof JCVariableDecl)) continue; JCTree v = assertionAdder.exprBiMap.getf(e); if (v != null && assertionAdder.exprBiMap.getr(v) == e) { log.getWriter(WriterKind.NOTICE).println(e.toString() + " <==> " + v); } else { log.getWriter(WriterKind.NOTICE).println(e.toString() + " ===> " + v); } } log.getWriter(WriterKind.NOTICE).println("\nTRANSLATED <==> BB"); for (JCTree e: basicBlocker.bimap.forward.keySet()) { JCExpression v = basicBlocker.bimap.getf(e); if (v != null && basicBlocker.bimap.getr(v) == e) { log.getWriter(WriterKind.NOTICE).println(e.toString() + " <==> " + v); } else { log.getWriter(WriterKind.NOTICE).println(e.toString() + " ===> " + v); } } log.getWriter(WriterKind.NOTICE).println("\nBB <==> SMT"); for (JCExpression e: smttrans.bimap.forward.keySet()) { IExpr v = smttrans.bimap.getf(e); if (v != null && smttrans.bimap.getr(v) == e) { log.getWriter(WriterKind.NOTICE).println(e.toString() + " <==> " + v); } else { log.getWriter(WriterKind.NOTICE).println(e.toString() + " ===> " + v); } } log.getWriter(WriterKind.NOTICE).println("\nORIGINAL <==> SMT"); } IExpr[] ee = new IExpr[1]; IPrinter p = smt.smtConfig.defaultPrinter; // Map<String,String> ce = constructSMTCounterexample(smttrans,solver); Map<JCTree,String> values = new HashMap<JCTree,String>(); for (JCTree t : assertionAdder.exprBiMap.forward.keySet() ) { if (t instanceof JmlVariableDecl) t = ((JmlVariableDecl)t).ident; if (!(t instanceof JCExpression)) continue; // t is the original source expression JCTree t1 = assertionAdder.exprBiMap.getf(t); // t1 is the result of JmlAssertionAdder, which should be a new AST JCExpression t2 = basicBlocker.bimap.getf(t1); // t2 is the result of BasicBlocker2, which may have changed AST nodes in place if (t2 == null && t1 instanceof JCIdent) t2 = (JCIdent)t1; // this can happen if the Ident ends up being declared in a declaration (such as wtih field or array assignments) IExpr smtexpr = smttrans.bimap.getf(t2); if (smtexpr == null) continue; ee[0] = smtexpr; String value = null; IResponse resp = solver.get_value(ee); // FIXME - need to get a single kind of response if (resp instanceof ISexpr.ISeq) { ISexpr pair = ((ISexpr.ISeq)resp).sexprs().get(0); value = p.toString(((ISexpr.ISeq)pair).sexprs().get(1)); // ce.put(key, value.toString()); } if (resp instanceof IResponse.IValueResponse) { IPair<IExpr,IExpr> pair = ((IResponse.IValueResponse)resp).values().get(0); value = p.toString(pair.second()); // ce.put(key, value.toString()); } if (resp instanceof IError) { value = p.toString(resp); } String t3 = value; // String t3 = t2 == null ? null : ce.get(t2.toString()); values.put(t, t3); if (verbose) log.getWriter(WriterKind.NOTICE).println(t + " >>>> " + t1 + " >>>> " + t2 + " >>>> " + smt.smtConfig.defaultPrinter.toString(smtexpr) + " >>>> "+ t3); } return values; } /** This is a listener for SMT log and error messages */ public static class SMTListener implements org.smtlib.Log.IListener { org.smtlib.IPrinter printer; com.sun.tools.javac.util.Log log; SMTListener(Log log, org.smtlib.IPrinter printer) { this.log = log; this.printer = printer; } @Override public void logOut(String msg) { log.getWriter(WriterKind.NOTICE).println(msg); } @Override public void logOut(IResponse result) { log.getWriter(WriterKind.NOTICE).println(printer.toString(result)); } @Override public void logError(String msg) { log.error("jml.smt.error",msg); //$NON-NLS-1$ } @Override public void logError(IError result) { log.error("jml.smt.error",printer.toString(result)); //$NON-NLS-1$ } @Override public void logDiag(String msg) { log.getWriter(WriterKind.NOTICE).println(msg); } @Override public void indent(String chars) { // TODO - not sure how to enforce the indent } } /** This class stores variable - value pairs that constitute a counterexample * for a given proof attempt. For flexibility, both variable and value are * stored as Strings, though this makes it depend on the format of the * rendering to String. It's OK because this information is not persisted. */ public class Counterexample implements IProverResult.ICounterexample { protected SortedMap<String,String> map = new TreeMap<String,String>(); protected Map<JCTree,String> mapv = new HashMap<JCTree,String>(); private List<IProverResult.Span> path; public String traceText; public Counterexample(String trace, Map<JCTree,String> cemap, List<IProverResult.Span> path) { mapv = cemap; this.path = path; this.traceText = trace; } // FIXME _ Cleanup // @Override // public void put(String key,String value) { // map.put(key,value); // } // // @Override // public void putMap(Map<String,String> map) { // this.map.putAll(map); // } // // @Override // public Map<String,String> getMap() { // return this.map; // } // // @Override // public void put(JCTree expr,String value) { // mapv.put(expr,value); // } // // @Override // public String get(String key) { // return map.get(key); // } @Override public String get(JCTree expr) { return mapv.get(expr); } @Override public Set<Map.Entry<String,String>> sortedEntries() { return map.entrySet(); } @Override public void putPath(List<IProverResult.Span> path) { this.path = path; } @Override public List<IProverResult.Span> getPath() { return path; } } }