/* * This file is part of the OpenJML project. * Author: David R. Cok */ package org.jmlspecs.openjml.esc; import java.io.PrintWriter; import java.util.HashMap; import java.util.Map; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import org.jmlspecs.annotation.NonNull; import org.jmlspecs.openjml.IAPI; import org.jmlspecs.openjml.JmlOption; import org.jmlspecs.openjml.JmlPretty; import org.jmlspecs.openjml.JmlTree.JmlMethodDecl; import org.jmlspecs.openjml.JmlTreeScanner; import org.jmlspecs.openjml.Main; import org.jmlspecs.openjml.Strings; import org.jmlspecs.openjml.Utils; import org.jmlspecs.openjml.proverinterface.IProverResult; import org.jmlspecs.openjml.proverinterface.ProverResult; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.code.Symbol.MethodSymbol; import com.sun.tools.javac.code.Symtab; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCClassDecl; import com.sun.tools.javac.tree.JCTree.JCMethodDecl; 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.Options; import com.sun.tools.javac.util.PropagatedException; /** * This class is the main driver for executing ESC on a Java/JML AST. It * formulates the material to be proved, initiates the proofs, and obtains and * reports the results. The class is also a TreeScanner so that it can easily * walk the tree to find all class and method declarations. * <P> * To use, instantiate an instance of JmlEsc, and then call either visitClassDef * or visitMethodDef; various options from JmlOptions will be used. In particular, * the -boogie option affects which implementation of ESC is used, * and the -prover and -exec options (and the openjml.prover... properties) * determine which prover is used. * * @author David R. Cok */ public class JmlEsc extends JmlTreeScanner { /** The key used to register an instance of JmlEsc in the compilation context */ protected static final Context.Key<JmlEsc> escKey = new Context.Key<JmlEsc>(); /** The method used to obtain the singleton instance of JmlEsc for this compilation context */ public static JmlEsc instance(Context context) { JmlEsc instance = context.get(escKey); if (instance == null) { instance = new JmlEsc(context); context.put(escKey,instance); } return instance; } // public IAPI.IProofResultListener proofResultListener = null; /** The compilation context, needed to get common tools, but unique to this compilation run*/ @NonNull Context context; /** Used to obtain cached symbols, such as basic types */ @NonNull Symtab syms; /** The tool to log problem reports */ @NonNull Log log; /** The OpenJML utilities object */ @NonNull Utils utils; /** true if compiler options are set to a verbose mode */ boolean verbose; /** Just for debugging esc */ public static boolean escdebug = false; // May be set externally to enable debugging while testing /** The assertion adder instance used to translate */ public JmlAssertionAdder assertionAdder; /** The prover to use - initialized here and then used in visitMethods */ protected /*@NonNull*/ String proverToUse; /** The JmlEsc constructor, which initializes all the tools and other fields. */ public JmlEsc(Context context) { this.context = context; this.syms = Symtab.instance(context); this.log = Log.instance(context); this.utils = Utils.instance(context); } /** Initializes assertionAdder and proverToUse and translates the argument */ public void check(JCTree tree) { this.verbose = escdebug || JmlOption.isOption(context,"-verbose") // The Java verbose option || utils.jmlverbose >= Utils.JMLVERBOSE; this.assertionAdder = new JmlAssertionAdder(context, true, false); try { assertionAdder.convert(tree); // get at the converted tree through the map proverToUse = pickProver(); tree.accept(this); } catch (PropagatedException e) { // Canceled Main.instance(context).canceled = true; throw e; } catch (Exception e) { // No further error messages needed - FIXME - is this true? log.error("jml.internal","Should not be catching an exception in JmlEsc.check"); } } /** Returns the prover specified by the options. */ public String pickProver() { // Pick a prover to use String proverToUse = JmlOption.value(context,JmlOption.PROVER); if (proverToUse == null) proverToUse = Options.instance(context).get(Strings.defaultProverProperty); if (proverToUse == null) { proverToUse = "z3_4_4"; } return proverToUse; } /** Visit a class definition */ @Override public void visitClassDef(JCClassDecl node) { Main.instance(context).pushOptions(node.mods); if (node.sym.isInterface()) return; // Nothing to verify in an interface // TODO: not so - could check that specs are consistent // The super class takes care of visiting all the methods utils.progress(1,1,"Proving methods in " + utils.classQualifiedName(node.sym) ); //$NON-NLS-1$ super.visitClassDef(node); utils.progress(1,1,"Completed proving methods in " + utils.classQualifiedName(node.sym) ); //$NON-NLS-1$ Main.instance(context).popOptions(); } /** When we visit a method declaration, we translate and prove the method; * we do not walk into the method any further from this call, only through * the translation mechanism. */ @Override public void visitMethodDef(@NonNull JCMethodDecl decl) { Main.instance(context).pushOptions(decl.mods); IProverResult res = null; if (decl.body == null) return; // FIXME What could we do with model methods or interfaces, if they have specs - could check that the preconditions are consistent if (!(decl instanceof JmlMethodDecl)) { log.warning(decl.pos(),"jml.internal","Unexpected non-JmlMethodDecl in JmlEsc - not checking " + utils.qualifiedMethodSig(decl.sym)); //$NON-NLS-2$ res = new ProverResult(proverToUse,ProverResult.ERROR,decl.sym); return; } JmlMethodDecl methodDecl = (JmlMethodDecl)decl; if (skip(methodDecl)) { markMethodSkipped(methodDecl," (excluded by skipesc)"); //$NON-NLS-1$ return; } // Do any nested classes and methods first (which will recursively call // this method) super.visitMethodDef(methodDecl); if (!filter(methodDecl)) { markMethodSkipped(methodDecl," (excluded by -method)"); //$NON-NLS-1$ return; } try { res = doMethod(methodDecl); } catch (PropagatedException e) { IAPI.IProofResultListener proofResultListener = context.get(IAPI.IProofResultListener.class); if (proofResultListener != null) proofResultListener.reportProofResult(methodDecl.sym, new ProverResult(proverToUse,IProverResult.CANCELLED,methodDecl.sym)); throw e; } Main.instance(context).popOptions(); return; } public static boolean skip(JmlMethodDecl methodDecl) { if (methodDecl.mods != null) { for (JCTree.JCAnnotation a : methodDecl.mods.annotations) { if (a != null && a.type.toString().equals("org.jmlspecs.annotation.SkipEsc")) { // FIXME - do this without converting to string return true; } } } return false; } // FIXME - perhaps shoud not be in JmlEsc public static boolean skipRac(JmlMethodDecl methodDecl) { if (methodDecl.mods != null) { for (JCTree.JCAnnotation a : methodDecl.mods.annotations) { if (a != null && a.type.toString().equals("org.jmlspecs.annotation.SkipRac")) { // FIXME - do this without converting to string return true; } } } return false; } public IProverResult markMethodSkipped(JmlMethodDecl methodDecl, String reason) { utils.progress(2,1,"Skipping proof of " + utils.qualifiedMethodSig(methodDecl.sym) + reason); //$NON-NLS-1$ //$NON-NLS-2$ // FIXME - this is all a duplicate from MethodProverSMT IProverResult.IFactory 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; } }; IProverResult res = factory.makeProverResult(methodDecl.sym,proverToUse,IProverResult.SKIPPED,new java.util.Date()); IAPI.IProofResultListener proofResultListener = context.get(IAPI.IProofResultListener.class); if (proofResultListener != null) proofResultListener.reportProofResult(methodDecl.sym, res); return res; } /** Do the actual work of proving the method */ protected IProverResult doMethod(@NonNull JmlMethodDecl methodDecl) { boolean printPrograms = this.verbose || JmlOption.isOption(context, JmlOption.SHOW); if (skip(methodDecl)) { return markMethodSkipped(methodDecl," (because of SkipEsc annotation)"); } utils.progress(1,1,"Starting proof of " + utils.qualifiedMethodSig(methodDecl.sym) + " with prover " + (Utils.testingMode ? "!!!!" : proverToUse)); //$NON-NLS-1$ //$NON-NLS-2$ log.resetRecord(); // int prevErrors = log.nerrors; IAPI.IProofResultListener proofResultListener = context.get(IAPI.IProofResultListener.class); if (proofResultListener != null) proofResultListener.reportProofResult(methodDecl.sym, new ProverResult(proverToUse,IProverResult.RUNNING,methodDecl.sym)); // The code in this method decides whether to attempt a proof of this method. // If so, it sets some parameters and then calls proveMethod boolean isConstructor = methodDecl.sym.isConstructor(); boolean doEsc = ((methodDecl.mods.flags & (Flags.SYNTHETIC|Flags.ABSTRACT|Flags.NATIVE)) == 0); // TODO: Could check that abstract or native methods have consistent specs // Don't do ESC on the constructor of Object // FIXME - why? (we don't have the source anyway, so how would we get here?) if (methodDecl.sym.owner == syms.objectType.tsym && isConstructor) doEsc = false; if (!doEsc) return null; // FIXME - SKIPPED? // print the body of the method to be proved if (printPrograms) { PrintWriter noticeWriter = log.getWriter(WriterKind.NOTICE); noticeWriter.println(Strings.empty); noticeWriter.println("--------------------------------------"); //$NON-NLS-1$ noticeWriter.println(Strings.empty); noticeWriter.println("STARTING PROOF OF " + utils.qualifiedMethodSig(methodDecl.sym)); //$NON-NLS-1$ noticeWriter.println(JmlPretty.write(methodDecl.body)); } IProverResult res = null; try { if (JmlOption.isOption(context, JmlOption.BOOGIE)) { res = new MethodProverBoogie(this).prove(methodDecl); } else { res = new MethodProverSMT(this).prove(methodDecl,proverToUse); } utils.progress(1,1,"Completed proof of " + utils.qualifiedMethodSig(methodDecl.sym) //$NON-NLS-1$ + " with prover " + (Utils.testingMode ? "!!!!" : proverToUse) //$NON-NLS-1$ + " - " + ( res.isSat() ? "with warnings" : res.result() == IProverResult.UNSAT ? "no warnings" : res.result().toString()) ); // if (log.nerrors != prevErrors) { // res = new ProverResult(proverToUse,IProverResult.ERROR,methodDecl.sym); // } } catch (Main.JmlCanceledException | PropagatedException e) { res = new ProverResult(proverToUse,ProverResult.CANCELLED,methodDecl.sym); // FIXME - I think two ProverResult.CANCELLED are being reported // FIXME - the following will through an exception because progress checks whether the operation is cancelled utils.progress(1,1,"Proof ABORTED of " + utils.qualifiedMethodSig(methodDecl.sym) //$NON-NLS-1$ + " with prover " + (Utils.testingMode ? "!!!!" : proverToUse) //$NON-NLS-1$ + " - exception" ); throw e; } catch (Exception e) { res = new ProverResult(proverToUse,ProverResult.ERROR,methodDecl.sym); log.error("jml.internal","Prover aborted with exception: " + e.getMessage()); utils.progress(1,1,"Proof ABORTED of " + utils.qualifiedMethodSig(methodDecl.sym) //$NON-NLS-1$ + " with prover " + (Utils.testingMode ? "!!!!" : proverToUse) //$NON-NLS-1$ + " - exception" ); // FIXME - add a message? use a factory? } finally { if (proofResultListener != null) proofResultListener.reportProofResult(methodDecl.sym, res); if (proofResultListener != null) proofResultListener.reportProofResult(methodDecl.sym, new ProverResult(proverToUse,IProverResult.COMPLETED,methodDecl.sym)); } return res; } /** Return true if the method is to be checked, false if it is to be skipped. * A warning that the method is being skipped is issued if it is being skipped * and the verbosity is high enough. * */ public boolean filter(JCMethodDecl methodDecl) { String fullyQualifiedName = utils.qualifiedName(methodDecl.sym); String simpleName = methodDecl.name.toString(); if (methodDecl.sym.isConstructor()) { String constructorName = methodDecl.sym.owner.name.toString(); fullyQualifiedName = fullyQualifiedName.replace("<init>", constructorName); simpleName = simpleName.replace("<init>", constructorName); } String fullyQualifiedSig = utils.qualifiedMethodSig(methodDecl.sym); String excludes = JmlOption.value(context,JmlOption.EXCLUDE); if (excludes != null) { for (String exclude: excludes.split(";")) { //$NON-NLS-1$ if (fullyQualifiedName.equals(exclude) || fullyQualifiedSig.equals(exclude) || simpleName.equals(exclude)) { if (utils.jmlverbose > Utils.PROGRESS) log.getWriter(WriterKind.NOTICE).println("Skipping " + fullyQualifiedName + " because it is excluded by " + exclude); //$NON-NLS-1$ //$NON-NLS-2$ return false; } try { if (Pattern.matches(exclude,fullyQualifiedName)) { if (utils.jmlverbose > Utils.PROGRESS) log.getWriter(WriterKind.NOTICE).println("Skipping " + fullyQualifiedName + " because it is excluded by " + exclude); //$NON-NLS-1$ //$NON-NLS-2$ return false; } } catch(PatternSyntaxException e) { // The methodToDo can be a regular string and does not // need to be legal Pattern expression // skip } } } String methodsToDo = JmlOption.value(context,JmlOption.METHOD); if (methodsToDo != null) { match: { if (fullyQualifiedSig.equals(methodsToDo)) break match; // A hack to allow at least one signature-containing item in the methods list for (String methodToDo: methodsToDo.split(";")) { //$NON-NLS-1$ if (fullyQualifiedName.equals(methodToDo) || methodToDo.equals(simpleName) || fullyQualifiedSig.equals(methodToDo)) { break match; } try { if (Pattern.matches(methodToDo,fullyQualifiedName)) break match; } catch(PatternSyntaxException e) { // The methodToDo can be a regular string and does not // need to be legal Pattern expression // skip } } if (utils.jmlverbose > Utils.PROGRESS) { log.getWriter(WriterKind.NOTICE).println("Skipping " + fullyQualifiedName + " because it does not match " + methodsToDo); //$NON-NLS-1$//$NON-NLS-2$ } return false; } } return true; } // // FIXME - move these away from being globals // // static public IProverResult mostRecentProofResult = null; }