/* * This file is part of the OpenJML project. * Author: David R. Cok */ package org.jmlspecs.openjml; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.Map; import javax.tools.DiagnosticListener; import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import javax.tools.SimpleJavaFileObject; import org.jmlspecs.annotation.NonNull; import org.jmlspecs.annotation.Nullable; import org.jmlspecs.annotation.Pure; import org.jmlspecs.openjml.JmlSpecs.FieldSpecs; import org.jmlspecs.openjml.JmlSpecs.TypeSpecs; import org.jmlspecs.openjml.JmlTree.JmlClassDecl; import org.jmlspecs.openjml.JmlTree.JmlCompilationUnit; import org.jmlspecs.openjml.JmlTree.JmlMethodDecl; import org.jmlspecs.openjml.JmlTree.JmlMethodInvocation; import org.jmlspecs.openjml.JmlTree.JmlMethodSpecs; import org.jmlspecs.openjml.JmlTree.JmlVariableDecl; import org.jmlspecs.openjml.esc.BasicBlocker2; import org.jmlspecs.openjml.esc.BasicProgram; import org.jmlspecs.openjml.esc.JmlEsc; import org.jmlspecs.openjml.proverinterface.IProverResult; import com.sun.tools.javac.code.*; import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Symbol.MethodSymbol; import com.sun.tools.javac.code.Symbol.PackageSymbol; import com.sun.tools.javac.code.Symbol.TypeSymbol; import com.sun.tools.javac.code.Symbol.VarSymbol; import com.sun.tools.javac.comp.Attr; import com.sun.tools.javac.comp.AttrContext; import com.sun.tools.javac.comp.Enter; import com.sun.tools.javac.comp.Env; import com.sun.tools.javac.comp.JmlAttr; import com.sun.tools.javac.comp.JmlEnter; import com.sun.tools.javac.file.JavacFileManager; import com.sun.tools.javac.main.JavaCompiler; import com.sun.tools.javac.comp.CompileStates; import com.sun.tools.javac.comp.CompileStates.CompileState; import com.sun.tools.javac.parser.JmlParser; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.tree.JCTree.JCStatement; import com.sun.tools.javac.tree.Pretty; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.ListBuffer; 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.Options; import com.sun.tools.javac.util.Position; /** This class is a wrapper and publicly published API for the OpenJML tool * functionality. In principle, any external programmatic interaction with * openjml would go through methods in this class. [In practice some internal * classes are exposed as well.] * <P> * The class is used as follows. The user creates a new API object and makes * calls to it; each distinct API object encapsulates a completely separate * compilation context. What would ordinarily be command-line options are * specified on creation of the API object; for the most part they are not * changeable after the compilation context has been created (where change is * allowed, a method call is provided). * <P> * There are also static methods, named execute, that are public entry points to the * various tools (jml checker, jmldoc, ...) that openjml provides. It performs * a one-time processing of files, without making the classes and ASTs available, * just like a command-line execution would do. * <P> * The public API consists of the methods with public visibility in this * compilation unit. * * @author David Cok */ public class API implements IAPI { /** The encapsulated org.jmlspecs.openjml.Main object */ protected Main main = null; //@ initially main != null; /** The listener for diagnostic messages */ protected DiagnosticListener<? extends JavaFileObject> diagListener = null; /** The listener for proof results */ protected IProofResultListener proofResultListener; /** Creates a new compilation context, initialized with given command-line options; * use Factory.makeAPI to create new API objects. * Output is sent to System.out; no diagnostic listener is used (so errors * and warnings are sent to System.out). * @param args the command-line options and initial set of files with which * to load the compilation environment */ //@ ensures isOpen; protected API(@NonNull String ... args) throws IOException { this(null,null,null,args); } /** Creates an API that will send informational output to the * given PrintWriter and diagnostic output to the given listener; no * operations are initiated however; * use Factory.makeAPI to create new API objects - don't call the constructor directly. * @param writer destination of non-diagnostic output (null means System.out) * @param listener receiver for diagnostic output * @param options the set of options to use, or null to mean default initialization * @param args files and additional options (as command-line arguments) */ //@ ensures isOpen; protected API(@Nullable PrintWriter writer, @Nullable DiagnosticListener<? extends JavaFileObject> listener, @Nullable Options options, @NonNull String... args) throws java.io.IOException { if (writer == null) { writer = new PrintWriter(System.out); } main = new Main(Strings.applicationName,writer,listener,options,args); this.diagListener = listener; Log.instance(context()).multipleErrors = true; } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#context() */ @Override //@ ensures \result == main.context; @Pure public @Nullable Context context() { return main == null ? null : main.context; } /** Returns the compiler object for this context. */ @Override @Pure @Nullable public Main main() { return main; } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#setProgressReporter(org.jmlspecs.openjml.Main.IProgressReporter) */ @Override public void setProgressListener(@Nullable Main.IProgressListener p) { if (main.progressDelegate != null) { p.setContext(context()); main.progressDelegate.setDelegate(p); } } @Override public void setProofResultListener(@Nullable IProofResultListener p) { proofResultListener = p; } /** Returns the string describing the version of OpenJML that is this * set of classes. * @return the version of this instance of OpenJML */ @Override public @NonNull String version() { return JavaCompiler.version(); } // See comment in parent interface @Override public void initOptions(@NonNull Options options, @NonNull String ... args) { main.initializeOptions(options == null && context() != null ? Options.instance(context()) : options, args); } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#setOption(java.lang.String, java.lang.String) */ //@ requires isOpen; //@ ensures isOpen; @Override public void addOptions(String... args) { main.addOptions(args); } @Override public void addUncheckedOption(String arg) { main.addUncheckedOption(arg); } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#getOption(java.lang.String) */ @Override public @Nullable String getOption(String name) { return Options.instance(context()).get(name); } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#execute(Options, String[]) */ @Override public int execute(@Nullable Options options, @NonNull String ... args) { int ret = main.executeNS(main.out(), diagListener, proofResultListener, options, args); return ret; } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#execute(PrintWriter, DiagnosticListener<JavaFileObject>, Options, String[]) */ @Override public int execute(@NonNull PrintWriter writer, @Nullable DiagnosticListener<JavaFileObject> diagListener, @Nullable Options options, @NonNull String ... args) { int ret = main.executeNS(writer,diagListener, proofResultListener, options,args); return ret; } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#jmldoc(String[]) */ public int jmldoc(@NonNull String... args) { return 4; // FIXME - org.jmlspecs.openjml.jmldoc.Main.execute(args); } @Override public boolean isTypechecked(ClassSymbol csym) { Env<AttrContext> env = Enter.instance(context()).getEnv(csym); if (env == null) return false; return env.tree.type != null; } @Override public boolean isTypechecked(String qualifiedName) { ClassSymbol csym = Symtab.instance(context()).classes.get(Names.instance(context()).fromString(qualifiedName)); if (csym == null) return false; return isTypechecked(csym); } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#enterAndCheck(org.jmlspecs.openjml.JmlTree.JmlCompilationUnit[]) */ @Override public int typecheck(@NonNull JmlCompilationUnit... trees) throws IOException { if (context() == null) { throw new NullPointerException("There is no valid compilation context"); } if (trees == null) { throw new IllegalArgumentException("Argument 'trees' of API.enterAndCheck is null"); } ListBuffer<JCCompilationUnit> list = new ListBuffer<JCCompilationUnit>(); list.appendArray(trees); return typecheck(list.toList()); } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#typecheck(java.util.Collection<? extends JmlCompilationUnit>) */ @Override public int typecheck(@NonNull Collection<? extends JmlCompilationUnit> trees) throws java.io.IOException { if (context() == null) { throw new NullPointerException("There is no valid compilation context"); } if (trees == null) { throw new IllegalArgumentException("Argument 'trees' of API.enterAndCheck is null"); } ListBuffer<JCCompilationUnit> list = new ListBuffer<JCCompilationUnit>(); list.addAll(trees); return typecheck(list.toList()); } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#typecheck(java.util.List<? extends JmlCompilationUnit>) */ @Override public int typecheck(@NonNull List<? extends JCCompilationUnit> list) throws IOException { Context context = context(); JmlCompiler jcomp = (JmlCompiler)JmlCompiler.instance(context); JmlTree.Maker maker = JmlTree.Maker.instance(context); // for (JCCompilationUnit jcu: list) { // for (JCTree t: jcu.defs) { // if (t instanceof JmlClassDecl && ((JmlClassDecl)t).typeSpecs == null) JmlParser.filterTypeBodyDeclarations((JmlClassDecl)t,context,maker); // } //// for (JmlClassDecl t: ((JmlCompilationUnit)jcu).parsedTopLevelModelTypes) { //// if (t.typeSpecs == null) JmlParser.filterTypeBodyDeclarations(t,context, maker); //// } // } ListBuffer<JCCompilationUnit> jlist = new ListBuffer<JCCompilationUnit>(); jlist.addAll(list); // The following statements are a subset of the compilation tool chain from JavaCompiler JavaCompiler dc = jcomp.processAnnotations( jcomp.enterTrees(jcomp.stopIfError(CompileState.PARSE, jlist.toList())), com.sun.tools.javac.util.List.<String>nil()); // TODO - someday incorporate annotation processors dc.flow(dc.attribute(dc.todo)); int errs = Log.instance(context()).nerrors; Log.instance(context()).nerrors = 0; return errs; } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#parseFiles(java.io.File[]) */ @Override public @NonNull java.util.List<JmlCompilationUnit> parseFiles(@NonNull File... files) { JmlCompiler c = (JmlCompiler)JmlCompiler.instance(context()); Log log = Log.instance(context()); c.inSequence = false; Iterable<? extends JavaFileObject> fobjects = ((JavacFileManager)context().get(JavaFileManager.class)).getJavaFileObjects(files); ArrayList<JmlCompilationUnit> trees = new ArrayList<JmlCompilationUnit>(); for (JavaFileObject fileObject : fobjects) { if (log.getSource(fileObject).getEndPosTable() != null) continue; // File object already parsed trees.add((JmlCompilationUnit)c.parse(fileObject)); } return trees; } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#parseFiles(String[]) */ @Override public @NonNull java.util.List<JmlCompilationUnit> parseFiles(@NonNull String... filenames) { File[] files = new File[filenames.length]; for (int i=0; i<filenames.length; i++) { files[i] = new File(filenames[i]); } return parseFiles(files); } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#parseFiles(javax.tools.JavaFileObject[]) */ @Override public @NonNull java.util.List<JmlCompilationUnit> parseFiles(@NonNull JavaFileObject... inputs) { JmlCompiler c = (JmlCompiler)JmlCompiler.instance(context()); c.inSequence = false; ArrayList<JmlCompilationUnit> trees = new ArrayList<JmlCompilationUnit>(); for (JavaFileObject fileObject : inputs) trees.add((JmlCompilationUnit)c.parse(fileObject)); return trees; } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#parseFiles(java.util.Collection) */ @Override public @NonNull java.util.List<JmlCompilationUnit> parseFiles(@NonNull Collection<? extends JavaFileObject> inputs) { JmlCompiler c = (JmlCompiler)JmlCompiler.instance(context()); c.inSequence = false; ArrayList<JmlCompilationUnit> trees = new ArrayList<JmlCompilationUnit>(); for (JavaFileObject fileObject : inputs) trees.add((JmlCompilationUnit)c.parse(fileObject)); return trees; } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#parseSingleFile(java.io.File) */ @Override public @NonNull JmlCompilationUnit parseSingleFile(@NonNull String filename) { return parseSingleFile(makeJFOfromFilename(filename)); } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#parseSingleFile(java.io.File) */ @Override public @NonNull JmlCompilationUnit parseSingleFile(@NonNull JavaFileObject jfo) { JmlCompiler c = (JmlCompiler)JmlCompiler.instance(context()); c.inSequence = true; // Don't look for specs JmlCompilationUnit specscu = (JmlCompilationUnit)c.parse(jfo); c.inSequence = false; return specscu; } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#parseString(java.lang.String, java.lang.String) */ @Override public @NonNull JmlCompilationUnit parseString(@NonNull String name, @NonNull String content) throws Exception { if (name == null || name.length() == 0) throw new IllegalArgumentException(); JmlCompiler c = (JmlCompiler)JmlCompiler.instance(context()); JavaFileObject file = makeJFOfromString(name,content); c.inSequence = true; // true so that no searching for spec files happens Iterable<? extends JavaFileObject> fobjects = List.<JavaFileObject>of(file); JmlCompilationUnit jcu = ((JmlCompilationUnit)c.parse(fobjects.iterator().next())); if (name.endsWith(".java")) jcu.specsCompilationUnit = jcu; return jcu; } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#parseExpression(java.lang.CharSequence, boolean) */ @Override public JCExpression parseExpression(CharSequence text,boolean isJML) { JmlCompiler.instance(context()); JmlParser p = ((com.sun.tools.javac.parser.JmlFactory)com.sun.tools.javac.parser.JmlFactory.instance(context())).newParser(text,true,true,true,isJML); return p.parseExpression(); } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#parseStatement(java.lang.CharSequence, boolean) */ @Override public JCStatement parseStatement(CharSequence text,boolean isJML) { JmlCompiler.instance(context()); JmlParser p = ((com.sun.tools.javac.parser.JmlFactory)com.sun.tools.javac.parser.JmlFactory.instance(context())).newParser(text,true,true,true,isJML); return p.parseStatement(); } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#parseAndCheck(java.io.File) */ @Override public void parseAndCheck(File... files) throws java.io.IOException { typecheck(parseFiles(files)); } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#findSpecs(JmlCompilationUnit) */ @Override public @Nullable JavaFileObject findSpecs(JmlCompilationUnit jmlcu) { return JmlSpecs.instance(context()).findSpecs(jmlcu,true); } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#attachSpecs(JmlCompilationUnit,JmlCompilationUnit) */ @Override public void attachSpecs(JmlCompilationUnit javaSource, JmlCompilationUnit specsSource) { javaSource.specsCompilationUnit = specsSource == null ? javaSource: specsSource; } /** Creates a JavaFileObject instance from a pseudo filename and given content * @param name the name to give the 'file' * @param content the content to give the file * @return the resulting JavaFileObject */ @Override public JavaFileObject makeJFOfromString(String name, String content) throws Exception { return new StringJavaFileObject(name,content); } /** Creates a JavaFileObject instance from a real file, by name * @param filepath the path to the file, either absolute or relative to the current working directory * @return the resulting JavaFileObject */ @Override public JavaFileObject makeJFOfromFilename(String filepath) { JavacFileManager dfm = (JavacFileManager)context().get(JavaFileManager.class); return dfm.getFileForInput(filepath); } /** Creates a JavaFileObject instance from a File object * @param file the file to wrap * @return the resulting JavaFileObject */ @Override public JavaFileObject makeJFOfromFile(File file) { JavacFileManager dfm = (JavacFileManager)context().get(JavaFileManager.class); return dfm.getRegularFile(file); } // TODO: need an easier way to find out if there are errors from parseAndCheck or enterAndCheck /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#getPackageSymbol(java.lang.String) */ @Override public @Nullable PackageSymbol getPackageSymbol(@NonNull String qualifiedName) { Name n = Names.instance(context()).fromString(qualifiedName); return Symtab.instance(context()).packages.get(n); } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#getClassSymbol(java.lang.String) */ //@ requires isOpen; //@ ensures isOpen; @Override public @Nullable ClassSymbol getClassSymbol(@NonNull String qualifiedName) { Name n = Names.instance(context()).fromString(qualifiedName); return Symtab.instance(context()).classes.get(n); } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#getClassSymbol(com.sun.tools.javac.code.Symbol.ClassSymbol, java.lang.String) */ @Override public @Nullable ClassSymbol getClassSymbol(@NonNull ClassSymbol csym, @NonNull String name) { Scope.Entry e = csym.members().lookup(Names.instance(context()).fromString(name)); if (e == null || e.sym == null) return null; while (e.sym != null && e.sym.owner == csym) { if (e.sym instanceof ClassSymbol) return (ClassSymbol)e.sym; e = e.next(); } return null; } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#getMethodSymbol(com.sun.tools.javac.code.Symbol.ClassSymbol, java.lang.String) */ @Override public @Nullable MethodSymbol getMethodSymbol(@NonNull ClassSymbol csym, @NonNull String name) { Scope.Entry e = csym.members().lookup(Names.instance(context()).fromString(name)); if (e == null || e.sym == null) return null; while (e.sym != null && e.sym.owner == csym) { if (e.sym instanceof MethodSymbol) return (MethodSymbol)e.sym; e = e.next(); } return null; } // FIXME - need a way to handle multiple methods with the same name /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#getVarSymbol(com.sun.tools.javac.code.Symbol.ClassSymbol, java.lang.String) */ @Override public @Nullable VarSymbol getVarSymbol(@NonNull ClassSymbol csym, @NonNull String name) { Scope.Entry e = csym.members().lookup(Names.instance(context()).fromString(name)); if (e == null || e.sym == null) return null; while (e.sym != null && e.sym.owner == csym) { if (e.sym instanceof VarSymbol) return (VarSymbol)e.sym; e = e.next(); } return null; } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#getSymbol(org.jmlspecs.openjml.JmlTree.JmlClassDecl) */ @Override public @Nullable ClassSymbol getSymbol(@NonNull JmlClassDecl decl) { return decl.sym; } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#getSymbol(org.jmlspecs.openjml.JmlTree.JmlMethodDecl) */ @Override public @Nullable MethodSymbol getSymbol(@NonNull JmlMethodDecl decl) { return decl.sym; } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#getSymbol(org.jmlspecs.openjml.JmlTree.JmlVariableDecl) */ @Override public @Nullable VarSymbol getSymbol(@NonNull JmlVariableDecl decl) { return decl.sym; } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#getClassDecl(java.lang.String) */ //@ requires isOpen; //@ ensures isOpen; @Override public @NonNull JmlClassDecl getClassDecl(@NonNull String qualifiedName) { return getClassDecl(getClassSymbol(qualifiedName)); } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#getClassDecl(com.sun.tools.javac.code.Symbol.ClassSymbol) */ //@ requires isOpen; //@ ensures isOpen; @Override public JmlClassDecl getClassDecl(ClassSymbol csym) { JCTree tree = JmlEnter.instance(context()).getClassEnv(csym).tree; return (JmlClassDecl)tree; } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#getMethodDecl(com.sun.tools.javac.code.Symbol.MethodSymbol) */ //@ requires isOpen; //@ ensures isOpen; @Override public @Nullable JmlMethodDecl getMethodDecl(MethodSymbol msym) { JmlClassDecl cdecl = getClassDecl((ClassSymbol)msym.owner); for (JCTree t: cdecl.defs) { if (t instanceof JmlMethodDecl && ((JmlMethodDecl)t).sym == msym) { return (JmlMethodDecl)t; } } return null; } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#getVarDecl(com.sun.tools.javac.code.Symbol.VarSymbol) */ //@ requires isOpen; //@ ensures isOpen; @Override public @Nullable JmlVariableDecl getVarDecl(VarSymbol vsym) { JmlClassDecl cdecl = getClassDecl((ClassSymbol)vsym.owner); for (JCTree t: cdecl.defs) { if (t instanceof JmlVariableDecl && ((JmlVariableDecl)t).sym == vsym) { return (JmlVariableDecl)t; } } return null; } /** A cached object to search parse trees (not thread safe since it is static) */ static protected Finder finder = new Finder(); /** A class that searches parse trees for nodes with a given position range. * This class presumes that the AST nodes have start and end position information * for which (a) each node has an end position at or after the start position, * (b) sibling nodes have disjoint position ranges, ordered in the order the * nodes are visited, and (c) a node's range includes the ranges of its * children. This condition is true of a AST produced by parsing, but is * not necessarily true of an AST produced by translation or other AST edits. */ public static class Finder extends JmlTreeScanner { /** Find the node within the given tree that encompasses the given * start and end position. * @param tree the root of the tree * @param startpos the starting char position (from begining of file) * @param endpos the ending position * @return the best matching node */ public JCTree find(JmlCompilationUnit tree, int startpos, int endpos) { this.startpos = startpos; this.endpos = endpos; this.tree = tree; this.scanMode = AST_JML_MODE; scan(tree); return found; } int startpos; int endpos; JmlCompilationUnit tree; public JCTree found = null; public JmlMethodDecl parentMethod = null; /** Called for each node as the tree is visited - do not call directly - use find() */ public void scan(JCTree node) { if (node == null) return; int sp = node.getStartPosition(); if (sp == Position.NOPOS && node instanceof JmlMethodInvocation) sp = ((JmlMethodInvocation)node).pos; int ep = node.getEndPosition(tree.endPositions); // Do this specially because the range of a MethodDecl node does not include the specs if (node instanceof JmlMethodDecl) { JCTree ftree = found; super.scan(node); if (found != ftree) parentMethod = (JmlMethodDecl)node; } else if (node instanceof JmlVariableDecl) { JCTree ftree = found; super.scan(node); if (found == ftree && sp <= startpos && endpos <= ep) { found = node; } } else if (sp <= startpos && endpos <= ep) { found = node; //System.out.println(startpos + " " + endpos + " " + sp + " " + ep + " " + node.getClass()); super.scan(node); // Call this to scan child nodes } // If the desired range is not within the node's range, we // don't even process the children } } /** Finds the minimal (lowest) node in the AST that includes the given character position range * @param tree the compilation unit to search (must have endPositions set) * @param startpos the starting character position * @param endpos the ending character position * @return the node identified */ protected JCTree findNode(JmlCompilationUnit tree, int startpos, int endpos) { return finder.find(tree,startpos,endpos); } // /** The method on which ESC was run most recently */ // protected MethodSymbol mostRecentProofMethod = null; // // protected BasicProgram mostRecentProgram = null; // FIXME - document; perhaps get rid of, and mostRecentProofMethod /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#getCEValue(int, int, java.lang.String, java.lang.String) */ @Override // TODO: REVIEW THIS - get the info through the IProverResult public String getCEValue(int pos, int end, String text, String fileLocation) { // fileLocation = fileLocation.replace('\\','/'); //// if (mostRecentProofMethod == null) { //// return "No proof in which to evaluate the selection"; //// } // JmlCompilationUnit tree = null; //(JmlCompilationUnit)Enter.instance(context()).getEnv((TypeSymbol)mostRecentProofMethod.owner).toplevel; // if (!tree.sourcefile.getName().replace('\\','/').equals(fileLocation)) { // //System.out.println("Did not match " + tree.sourcefile.toString()); // boolean found = false; // { // JmlCompilationUnit stree = tree.specsCompilationUnit; // if (stree.sourcefile.getName().replace('\\','/').equals(fileLocation)) { // tree = stree; // found = true; // } // //System.out.println("Did not match " + stree.sourcefile.toString()); // } // if (!found) { // // TODO _ make a proper error to the right destination // System.out.println("No Match for " + tree.specsCompilationUnit.sourcefile.getName()); // } // } // JCTree node = findNode(tree,pos,end); // JmlMethodDecl parentMethod = finder.parentMethod; //// if (parentMethod.sym != mostRecentProofMethod) { //// return "Selected text is not within the method of the most recent proof (which is " + mostRecentProofMethod + ")"; //// } // String out; // if (node instanceof JmlVariableDecl) { // // This happens when we have selected a method parameter or the variable within a declaration // // continue // out = text == null ? null : ("Found declaration: " + ((JmlVariableDecl)node).name.toString() + "\n"); // } else if (!(node instanceof JCTree.JCExpression)) { // return text == null ? null : ("Selected text is not an expression (" + node.getClass() + "): " + text); // } else { // if (text == null) out = node.toString().replace("<", "<") + " <B>is</B> "; // else out = "Found expression node: " + node.toString() + "\n"; // } // // if (JmlEsc.mostRecentProofResult != null) { // String value = JmlEsc.mostRecentProofResult.counterexample().get(node); // if (value != null) { // if (text == null) out = out + value; // else out = out + "Value " + node.type + " : " + value; // if (node.type.tag == TypeTags.CHAR) { // try { // out = out + " ('" + (char)Integer.parseInt(value) + "')"; // } catch (NumberFormatException e) { // // ignore // } // } // } // else out = text == null ? null : (out + "Value is unknown (type " + node.type + ")"); // return out; // } return "No counterexample information available"; } @Override // TODO: REVIEW THIS - get the info through the IProverResult public Finder findMethod(JmlCompilationUnit tree, int pos, int end, String text, String fileLocation) { fileLocation = fileLocation.replace('\\','/'); // if (mostRecentProofMethod == null) { // return "No proof in which to evaluate the selection"; // } if (!tree.sourcefile.getName().replace('\\','/').equals(fileLocation)) { //System.out.println("Did not match " + tree.sourcefile.toString()); boolean found = false; { JmlCompilationUnit stree = tree.specsCompilationUnit; if (stree.sourcefile.getName().replace('\\','/').equals(fileLocation)) { tree = stree; found = true; } //System.out.println("Did not match " + stree.sourcefile.toString()); } if (!found) { // TODO _ make a proper error to the right destination System.out.println("No Match for " + tree.specsCompilationUnit.sourcefile.getName()); } } JCTree node = findNode(tree,pos,end); return finder; } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#doESC(com.sun.tools.javac.code.Symbol.MethodSymbol) */ @Override public IProverResult doESC(MethodSymbol msym) { JmlMethodDecl decl = getMethodDecl(msym); JmlEsc esc = JmlEsc.instance(context()); class L implements IProofResultListener { public L(IProofResultListener chained) { this.chained = chained; } public IProofResultListener chained; public IProverResult result; public void reportProofResult(MethodSymbol msym, IProverResult result) { this.result = result; if (chained != null) chained.reportProofResult(msym, result); } }; IProofResultListener p = proofResultListener; L l = new L(p); setProofResultListener(l); // L l; // if (!(p instanceof L)) { // main().proofResultListener = l = new L(p); // main().context().put(IAPI.IProofResultListener.class, l); // } else { // l = (L)p; // } esc.check(decl); setProofResultListener(p); // main().context().put(IAPI.IProofResultListener.class, p); // main().proofResultListener = p; return l.result; } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#doESC(com.sun.tools.javac.code.Symbol.ClassSymbol) */ @Override public void doESC(ClassSymbol csym) { //if (!isTypechecked(csym)) typecheck(csym); // mostRecentProofMethod = null; // mostRecentProgram = null; JmlClassDecl decl = getClassDecl(csym); main().proofResultListener = proofResultListener; JmlEsc.instance(context()).check(decl); } // /* (non-Javadoc) // * @see org.jmlspecs.openjml.IAPI#getProofResult(com.sun.tools.javac.code.Symbol.MethodSymbol) // */ // //@ requires isOpen; // //@ ensures isOpen; // @Override // public @Nullable IProverResult getProofResult(MethodSymbol msym) { // return JmlEsc.instance(context()).proverResults.get(msym); // } // // @Override // public @Nullable Map<MethodSymbol,IProverResult> getProofResults() { // return JmlEsc.instance(context()).proverResults; // } // TODO - move these two methods to Utils? /** Adds the given class and all its supertypes (recursively) to the given list, * ordered with parent classes first. * @param csym the class or interface in question * @param list the list to add to */ //@ requires isOpen; //@ ensures isOpen; protected void collectSuperTypes(@NonNull ClassSymbol csym, java.util.List<ClassSymbol> list) { Type tt = csym.getSuperclass(); if (tt != null && tt != Type.noType) { ClassSymbol s = (ClassSymbol)tt.tsym; // super classes are always ClassSymbols collectSuperTypes(s,list); } for (Type t: csym.getInterfaces()) { ClassSymbol c = (ClassSymbol)t.tsym; // interfaces are ClassSymbols if (!list.contains(c)) { collectSuperTypes(c,list); // c and any super interfaces are added here } } list.add(csym); } /** Adds the method and all the methods it overrides (in classes or interfaces) * to the given list * @param msym the method in question * @param list the list to add the methods to */ //@ requires isOpen; //@ ensures isOpen; protected void collectSuperMethods(@NonNull MethodSymbol msym, java.util.List<MethodSymbol> list) { java.util.List<ClassSymbol> clist = new ArrayList<ClassSymbol>(); collectSuperTypes(msym.enclClass(),clist); for (ClassSymbol c: clist) { // find a method matching msym in c Scope.Entry e = c.members().lookup(msym.getSimpleName()); while (e != null) { Symbol sym = e.sym; e = e.sibling; if (!(sym instanceof MethodSymbol)) continue; MethodSymbol mmsym = (MethodSymbol)sym; if (!msym.overrides(mmsym,msym.enclClass(),Types.instance(context()),false)) continue; list.add(mmsym); break; } // FIXME - there must be a utility somewhere that does this already } } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#getSpecs(com.sun.tools.javac.code.Symbol.ClassSymbol) */ @Override public @NonNull TypeSpecs getSpecs(@NonNull ClassSymbol sym) { return JmlSpecs.instance(context()).get(sym); } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#getAllSpecs(com.sun.tools.javac.code.Symbol.ClassSymbol) */ @Override public java.util.List<TypeSpecs> getAllSpecs(@NonNull ClassSymbol sym) { java.util.List<ClassSymbol> list = new ArrayList<ClassSymbol>(); collectSuperTypes(sym,list); JmlSpecs specs = JmlSpecs.instance(context()); java.util.List<TypeSpecs> tslist = new ArrayList<TypeSpecs>(list.size()); for (ClassSymbol c: list) tslist.add(specs.get(c)); return tslist; } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#getSpecs(com.sun.tools.javac.code.Symbol.MethodSymbol) */ @Override public @NonNull JmlSpecs.MethodSpecs getSpecs(@NonNull MethodSymbol sym) { return JmlSpecs.instance(context()).getSpecs(sym); } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#getAllSpecs(com.sun.tools.javac.code.Symbol.MethodSymbol) */ @Override public java.util.List<JmlSpecs.MethodSpecs> getAllSpecs(@NonNull MethodSymbol msym) { java.util.ArrayList<JmlSpecs.MethodSpecs> tslist = new ArrayList<JmlSpecs.MethodSpecs>(); if (msym.isStatic() || msym.isConstructor()) { tslist.add(getSpecs(msym)); return tslist; } java.util.List<MethodSymbol> list = new ArrayList<MethodSymbol>(); collectSuperMethods(msym,list); tslist.ensureCapacity(list.size()); JmlSpecs specs = JmlSpecs.instance(context()); for (MethodSymbol c: list) tslist.add(specs.getSpecs(c)); return tslist; } // FIXME - should this be inherited specs; what about parameter name renaming? /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#getDenestedSpecs(com.sun.tools.javac.code.Symbol.MethodSymbol) */ @Override public @NonNull JmlMethodSpecs getDenestedSpecs(@NonNull MethodSymbol sym) { return JmlSpecs.instance(context()).getDenestedSpecs(sym); } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#getSpecs(com.sun.tools.javac.code.Symbol.VarSymbol) */ @Override public @NonNull FieldSpecs getSpecs(@NonNull VarSymbol sym) { return JmlSpecs.instance(context()).getSpecs(sym); } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#nodeFactory() */ //@ requires isOpen; //@ ensures isOpen; @Override public @NonNull JmlTree.Maker nodeFactory() { JmlAttr.instance(context()); // Avoids circular tool registration problems return JmlTree.Maker.instance(context()); } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#prettyPrint(com.sun.tools.javac.tree.JCTree) */ // FIXME - allow the option of showing composite specs? @Override public @NonNull String prettyPrint(@NonNull JCTree ast) throws java.io.IOException { StringWriter s = new StringWriter(); Pretty p = JmlPretty.instance(s,true); if (ast instanceof JCTree.JCExpressionStatement) p.printStat(ast); else ast.accept(p); return s.toString(); } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#prettyPrintJML(com.sun.tools.javac.tree.JCTree) */ @Override public @NonNull String prettyPrintJML(@NonNull JCTree ast) throws java.io.IOException { StringWriter s = new StringWriter(); Pretty p = JmlPretty.instance(s,false); if (ast instanceof JCTree.JCExpressionStatement) p.printStat(ast); else ast.accept(p); return s.toString(); } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#prettyPrint(java.util.List, boolean, java.lang.String) */ //@ requires isOpen; //@ ensures isOpen; @Override public @NonNull String prettyPrint(@NonNull java.util.List<? extends JCTree> astlist, @NonNull String sep) throws java.io.IOException { StringWriter s = new StringWriter(); boolean isFirst = true; for (JCTree ast: astlist) { if (!isFirst) { s.append(sep); } else { isFirst = false; } JmlPretty.instance(s,true).print(ast); } return s.toString(); } /* (non-Javadoc) * @see org.jmlspecs.openjml.IAPI#close() */ //@ requires isOpen; //@ assignable isOpen; //@ ensures !isOpen; @Override public void close() { JmlCompiler.instance(context()).close(); main.context = null; main = null; } /** This class encapsulates a String as a JavaFileObject, making it a pseudo-file */ protected static class StringJavaFileObject extends SimpleJavaFileObject { /** The content of the mock file */ //@ non_null protected String content; /** A fake file name, used when the user does not want to be bothered * supplying one. We have to make and cache this because it is a pain to * deal with exceptions in constructors. */ //@ non_null static final protected URI uritest = makeURI(); /** A utility method to make the URI, so it can handle the exceptions. * We don't try to recover gracefully if the exception occurs - this is * just used in testing anyway. */ private static URI makeURI() { try { return new URI("file:///TEST.java"); } catch (Exception e) { System.err.println("CATASTROPHIC EXIT - FAILED TO CONSTRUCT A MOCK URI"); System.exit(3); return null; } } /** Constructs a new JavaFileObject of kind SOURCE or OTHER depending on the * filename extension * @param filename the filename to use (no leading slash) (null indicates to * use the internal fabricated filename) * @param content the content of the pseudo file * @throws Exception if a URI cannot be created */ // FIXME - sort out the package part of the path public StringJavaFileObject(/*@ nullable */String filename, /*@ non_null */String content) throws Exception { // This takes three slashes because the filename is supposed to be absolute. // In our case this is not a real file anyway, so we pretend it is absolute. super(filename == null ? uritest : new URI("file:///" + filename), filename == null || filename.endsWith(Strings.javaSuffix) ? Kind.SOURCE : Kind.OTHER); this.content = content; } /** Overrides the parent to provide the content directly from the String * supplied at construction, rather than reading the file. This is called * by the system. */ @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) { return content; } /** Overrides the parent method to allow name compatibility between * pseudo files of different kinds. */ // Don't worry about whether the kinds match, just the file extension @Override public boolean isNameCompatible(String simpleName, Kind kind) { String s = uri.getPath(); if (kind == Kind.OTHER) { int i = s.lastIndexOf('/'); s = s.substring(i+1); return s.startsWith(simpleName); } else { String baseName = simpleName + kind.extension; return s.endsWith("/" + baseName); } } /** Returns true if the receiver and argument are the same object */ @Override public boolean equals(Object o) { return o == this; } /** A definition of hashCode, since we have a definition of equals */ @Override public int hashCode() { return super.hashCode(); } } }