/* * This file is part of the OpenJML project. * Author: David R. Cok */ package org.jmlspecs.openjml; // TODO: // - document // - cleanup desugared specs // - clean up constructors and details of TypeSpecs // - clean up pretty printing and toString for debugging // - fix use of ZipArchive subdirectories import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Enumeration; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.zip.ZipFile; import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import org.eclipse.core.runtime.Platform; import org.jmlspecs.annotation.NonNull; import org.jmlspecs.openjml.JmlTree.JmlAnnotation; import org.jmlspecs.openjml.JmlTree.JmlClassDecl; import org.jmlspecs.openjml.JmlTree.JmlCompilationUnit; import org.jmlspecs.openjml.JmlTree.JmlMethodClause; import org.jmlspecs.openjml.JmlTree.JmlMethodClauseSignalsOnly; import org.jmlspecs.openjml.JmlTree.JmlMethodDecl; import org.jmlspecs.openjml.JmlTree.JmlMethodSpecs; import org.jmlspecs.openjml.JmlTree.JmlSpecificationCase; import org.jmlspecs.openjml.JmlTree.JmlTypeClause; import org.jmlspecs.openjml.JmlTree.JmlTypeClauseDecl; import org.jmlspecs.openjml.JmlTree.JmlTypeClauseInitializer; import org.jmlspecs.openjml.JmlTree.JmlVariableDecl; import org.osgi.framework.Bundle; import com.sun.tools.javac.code.Attribute; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Symbol.MethodSymbol; import com.sun.tools.javac.code.Symbol.VarSymbol; import com.sun.tools.javac.code.Symtab; import com.sun.tools.javac.code.Type; 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.file.RelativePath; import com.sun.tools.javac.file.ZipArchive; import com.sun.tools.javac.jvm.ClassReader; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.tree.JCTree.JCModifiers; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.ListBuffer; import com.sun.tools.javac.util.Log; import com.sun.tools.javac.util.Log.WriterKind; import com.sun.tools.javac.util.Names; import com.sun.tools.javac.util.Options; /** This class manages the specifications of various Java entities * during a compilation. There should be just one instance of JmlSpecs * per compilation Context, ensured by calling the preRegister * method. The class provides methods for finding specification files, and it * maintains a database of specifications of types, methods, and fields. * The specs are indexed by type * as well as by method, so that obsolete specs can be readily discarded. * <P> * <B>Specification Paths</B> * The specification path is a sequence of directories or analogous locations * of files. Just as the classpath * is used by Java to find the classes referenced in a program, the specs path * is used to find specification files. There are a few enhancements on this * simple concept: * <UL> * <LI>1) ordinarily the specsPath is specified on the command-line as the option * of -specspath</LI> * <LI>2) if there is no command-line argument, the value of the environment property * org.jmlspecs.specspath is used</LI> * <LI>3) if that is not defined, the value of the sourcepath is used (specified * by the -sourcepath command-line option or properties)</LI> * <LI>4) if that is not defined, the value of the classpath is used</LI> * is used (the -classpath option, if it is specified, or the java.class.path * environment property)</LI> * <LI>5) the string representing a directory may be a relative or absolute path</LI> * <LI>6) the string representing a directory may be a jar or zip file or a * directory within a jar or zip file, specified by using a ! to separate the * path to the jar file from the internal directory path, e.g. a/b/specs.jar!d/e * <LI>7) the string representing a directory may consist of a variable reference that * is replaced by a system dependent string. The following are defined: * <UL> * <LI> $CP - expanded to be the directories of the classpath</LI> * <LI> $ECP - expanded to be the content of java.class.path</LI> * <LI> $SP - expanded to be the directories of the sourcepath</LI> * <LI> $SY - expanded to be the directories containing specifications * of the java system files, either the value of the * environmental property org.jmlspecs.system.specs or, if * that is not set, an internal value corresponding to the * location of specs shipped with the tool </LI> * </UL> * </UL> * In addition, by default, the specs path has $SY appended to it (unless * disabled with -noInternalSpecs); the classpath has the internal runtime * library appended to it (unless disabled with -noInternalRuntime). * * <P> * <B>Mock files</B> * Especially for testing, but perhaps also in other contexts, it is useful to be * able to pretend that there is a file with some content at a given location, * without the file actually being present in the file system. This can be * accomplished using mock file objects. This tool handles files abstractly as * instances of JavaFileObject. One can create an instance of a derived class, * a MockJavaFileObject, which has a name and a String as content (which should * look like the text of a compilation unit), but which does not actually exist as a file. * The name does however have to look like a legitimate path name, with a * directory, a fully-qualified package directory path, and the class file name. * The initial directory must be on the specspath. For that purpose, mock * directory names are allowed on the specspath. These are arbitrary names, * beginning with $(==Utils.mockDirChar), which are then listed as directories * on the specspath and * used as the directory location of the package+classname representing the * specifications file. For example, the JUnit tests for this tool use two * mock directories, $A and $B (in that order). A test will specify the * specsPath for the test to include those two paths (e.g. setSpecsPath("$A;$B");), * and it will add mock files with names like "$A/t/A.java" for class t.A. * <P> * <B>The specifications database</B> * This class also handles the database of specifications. This database is * indexed by Symbol - either ClassSymbol, MethodSymbol or VarSymbol as * appropriate. Specifications for a Java element are also stored * as fields of the JmlClassDecl, JmlMethodDecl, and JmlVariableDecl AST nodes, * but these are only the specs that are declared in that compilation unit. * The database holds the combined specifications from all sources, e.g. all * files that contribute specifications (JML used to allow a sequence of files, * but it has been simplified to take specifications from just one file). * Also, Java classes which do not have source available to the compiler * do not have an AST in which to place references to specifications. Thus this * database is used as the general mechanism for retrieving specs for a Java element. * If there is a Java source file and accompanying ASTs, the fields * JmlClassDecl.typeSpecsCombined, JmlMethodDecl.methodSpecsCombined, and * JmlVariableDecl.fieldSpecsCombined should have the same entries as the database. * * @author David Cok * */ public class JmlSpecs { /** The prefix of the top-level directory within the JML release jar file * containing the specs for various versions of java (e.g. specs15 for * Java 1.5). */ private final static String prefix = "specs1"; /** The key to use to retrieve the instance of this class from the Context object. */ //@ non_null public static final Context.Key<JmlSpecs> specsKey = new Context.Key<JmlSpecs>(); /** A method that returns the unique instance of this class for the given Context * (creating it if it does not already exist). * * @param context the Context whose JmlSpecs instance is wanted * @return the singleton instance (per Context) of this class */ //@ non_null public static JmlSpecs instance(Context context) { JmlSpecs instance = context.get(specsKey); if (instance == null) { instance = new JmlSpecs(context); // registers itself } return instance; } /** Call this to register (a factory for) a singleton (per Context instance) object * of this class. * * @param context The compilation context in which to register a JmlSpecs instance */ // A factory is used so that the object is not created until needed. This // is important since various options are read (e.g. specspath) at // construction time. public static void preRegister(final Context context) { context.put(specsKey, new Context.Factory<JmlSpecs>() { public JmlSpecs make(Context context) { return new JmlSpecs(context); } }); } /** The Context in which this object was constructed */ //@ non_null final protected Context context; /** The Attr tool for this context */ final protected JmlAttr attr; /** The Log tool for this context */ final protected Log log; /** The Names tool for this context */ final protected Names names; /** The Utils tool for this context */ final protected Utils utils; /** The map giving the accumulated specifications for a given type */ final protected Map<ClassSymbol,TypeSpecs> specsmap = new HashMap<ClassSymbol,TypeSpecs>(); /** The specifications path, which is a sequence of directories in which to * find specification files; this is created by initializeSpecsPath(). * NOTE: This is lazily initialized, so call getSpecsPath() to obtain its value. */ protected LinkedList<Dir> specsDirs = null; /** Creates an instance in association with the given Context; * the specs path is initialized by an explicit call of initializeSpecsPath; * use JmlSpecs.instance to get instances - do not call the constructor * directly. * * @param context The compilation context */ protected JmlSpecs(Context context) { this.context = context; context.put(specsKey, this); // self-register attr = JmlAttr.instance(context); log = Log.instance(context); utils = Utils.instance(context); names = Names.instance(context); } /** Initializes the specs path given the current settings of options. */ public void initializeSpecsPath() { Options options = Options.instance(context); String s = JmlOption.value(context,JmlOption.SPECS); if (s == null) s = options.get(Strings.specsPathEnvironmentPropertyName); if (s == null) s = options.get(Strings.sourcepathOptionName); if (s == null) s = options.get(Strings.classpathOptionName); if (s == null) s = System.getProperty("java.class.path"); if (s == null) s = ""; setSpecsPath(s); } /** This method looks for the internal specification directories and, if * found, appends them to the argument. * @param dirs the list to which to append the internal spec directories * @return true if found, false if not */ public boolean appendInternalSpecs(boolean verbose, java.util.List<Dir> dirs) { PrintWriter noticeWriter = log.getWriter(WriterKind.NOTICE); String versionString = System.getProperty("java.version"); int version; if (versionString.startsWith("1.6")) version = 6; else if (versionString.startsWith("1.5")) version = 5; else if (versionString.startsWith("1.4")) version = 4; else if (versionString.startsWith("1.7")) version = 7; else if (versionString.startsWith("1.")) version = versionString.charAt(2) - '0'; else { noticeWriter.println("Unrecognized version: " + versionString); version = 8; // default, if the version string is in an unexpected format } if (verbose) noticeWriter.println("Java version " + version); // Look for a openjml.jar or jmlspecs.jar file on the classpath // If present, use it (and use the first one found). // This happens in command-line mode. String sp = System.getProperty("java.class.path"); String[] ss = sp.split(java.io.File.pathSeparator); Dir d; String libToUse = prefix+version; for (String s: ss) { if (s.endsWith(Strings.releaseJar)) { d = new JarDir(s,libToUse); if (d.exists()) { if (verbose) noticeWriter.println("Using internal specs " + d); dirs.add(d); return true; } } } for (String s: ss) { if (s.endsWith(Strings.specsJar)) { d = new JarDir(s,""); if (d.exists()) { if (verbose) noticeWriter.println("Using internal specs " + d); dirs.add(d); return true; } } } // Next see if there is jar file on the classpath that contains // specs16 or similar directories for (String s: ss) { if (s.endsWith(".jar")) { d = new JarDir(s,libToUse); if (d.exists()) { if (verbose) noticeWriter.println("Using internal specs " + d); dirs.add(d); return true; } d = new JarDir(s,prefix + (version-1)); if (d.exists()) { if (verbose) noticeWriter.println("Using internal specs " + d); dirs.add(d); return true; } } } // FIXME - clean this all up and try to get rid of the dependency on eclipseSpecsProjectLocation // (which is used in tests) - be careful though, the UI can be tricky and operates // differently in development vs. deployed mode // Finally, for working in the Eclipse environment, see if there // is an environment variable that is set. // Bundle specs = Platform.getBundle("org.jmlspecs.Specs"); // if (specs != null) { // Enumeration<String> en = specs.getEntryPaths("java" + version); // String p = en.nextElement(); // System.out.println(p); // String pp = specs.getLocation(); // System.out.println(pp); // java.net.URL ppp = specs.getEntry(""); // System.out.println(ppp); // } String sy = Options.instance(context).get(Strings.eclipseSpecsProjectLocation); // if (sy == null) { // Bundle specs = Platform.getBundle("org.jmlspecs.Specs"); // if (specs != null) { // String pp = specs.getLocation(); // System.out.println(pp); // int k = pp.indexOf("file:/") + "file:/".length(); // sy = pp.substring(k); // } // } // These are used in testing - sy should be the trunk directory of the Specs project if (sy != null) { boolean found = false; Dir dd; for (int v = version; v >= 4; --v) { dd = make(sy+"/java"+v); if (dd.exists()) { dirs.add(dd); found = true; } else { // We found some directories - the others ought to exist if (found) log.error("jml.internal.specs.dir.not.exist",dd); } } if (!found) log.error("jml.internal.specs.dir.not.exist",sy); return true; } else { log.error("jml.internal.specs.dir.not.defined"); } return false; } /** Returns the current list of specification directories in use. * @return The current list of specification directories, in order. */ public List<Dir> getSpecsPath() { if (specsDirs == null) initializeSpecsPath(); return specsDirs; } /** Returns the source path */ public String[] getSourcePath() { Options options = Options.instance(context); String s = options.get(Strings.sourcepathOptionName); if (s == null) s = options.get(Strings.classpathOptionName); if (s == null) s = System.getProperty("java.class.path"); if (s == null) s = ""; return s.split(java.io.File.pathSeparator); } /** Sets the specifications path according to the given string; the * string must be a sequence of directories, separated by the host * systems java.io.File.pathSeparator character. The directories are * given as absolute file system paths or as paths relative to the current * working directory. * @param specsPath the string holding the specifications path */ //@ modifies this.specsDirs; public void setSpecsPath(String specsPath) { setSpecsPath(specsPath.split(java.io.File.pathSeparator)); } /** Sets the specifications path according to the given array of strings; the * elements of the array are the directories of the specs path, either * absolute or relative to the working directory * @param specsPathArray the string holding the new specifications path */ //@ requires \nonnullelements(specsPathArray); //@ assignable this.specsDirs; public void setSpecsPath(String[] specsPathArray) { boolean verbose = utils.jmlverbose >= Utils.JMLVERBOSE || Options.instance(context).get("-verbose") != null; PrintWriter noticeWriter = log.getWriter(WriterKind.NOTICE); specsDirs = new LinkedList<Dir>(); List<String> todo = new LinkedList<String>(); for (int i = 0; i<specsPathArray.length; i++) { String s = specsPathArray[i]; if (s == null || s.length() == 0) continue; todo.add(s); } String dir; boolean checkDirectories = JmlOption.isOption(context,JmlOption.CHECKSPECSPATH); if (JmlOption.isOption(context,JmlOption.INTERNALSPECS)) { todo.add("$SY"); } boolean syIncluded = false; boolean spIncluded = false; boolean cpIncluded = false; boolean ecpIncluded = false; while (!todo.isEmpty()) { dir=todo.remove(0); if (dir.equals("$SY")) { if (syIncluded) { // If we are processing the last entry and it is a duplicate, just // ignore it. if (!todo.isEmpty()) log.warning("jml.bad.sp.var","$SY"); } else { syIncluded = true; String dirs = Options.instance(context).get(Strings.systemSpecsLocationEnvironmentPropertyName); if (dirs != null) pushback(dirs,todo); else { if (!appendInternalSpecs(verbose,getSpecsPath())) { log.warning("jml.no.internal.specs"); } } } } else if (dir.equals("$CP")) { if (cpIncluded) { log.warning("jml.bad.sp.var","$CP"); } else { cpIncluded = true; String dirs = Options.instance(context).get("-classpath"); if (dirs == null) dirs = System.getProperty("java.class.path"); if (dirs != null) pushback(dirs,todo); } } else if (dir.equals("$ECP")) { if (ecpIncluded) { log.warning("jml.bad.sp.var","$ECP"); } else { ecpIncluded = true; String dirs = System.getProperty("java.class.path"); if (dirs != null) pushback(dirs,todo); } } else if (dir.equals("$SP")) { if (spIncluded) { log.warning("jml.bad.sp.var","$SP"); } else { spIncluded = true; String dirs = Options.instance(context).get("-sourcepath"); if (dirs != null) pushback(dirs,todo); } } else if (dir.length()>0){ Dir d = make(dir); if (d != null) { if (checkDirectories && !d.exists()) { log.warning("jml.specs.dir.not.exist",d); } specsDirs.add(d); } else { // At present make always returns non-null log.error("jml.internal.notsobad","Failed to create a directory path entry from " + dir); } } } if (verbose) { noticeWriter.print("specspath:"); for (Dir s: specsDirs) { noticeWriter.print(" "); noticeWriter.print(s); } Options options = Options.instance(context); noticeWriter.println(""); noticeWriter.println("sourcepath: " + options.get("-sourcepath")); noticeWriter.println("classpath: " + options.get("-classpath")); noticeWriter.println("java.class.path: " + System.getProperty("java.class.path")); noticeWriter.flush(); } } /** This method is used internally to parse the directory path given in the * first argument and to push them (in reverse order) onto the front of the * queue that is the second argument; in this way the contents of the * directory path are handled as the next items in the order in which they * are listed in the directory path. * @param dirs the directory path to process * @param todo the list of directories yet to be processed */ protected void pushback(String dirs, List<String> todo) { String[] array = dirs.split(java.io.File.pathSeparator); for (int i=array.length-1; i>=0; --i) { todo.add(0,array[i]); } } /** A map of names to JavaFileObjects representing files. The JavaFileObjects * can function as files containing source or specifications but do not need * to be actually present in the file system. Thus they are handy in testing. */ final protected Map<String,JavaFileObject> mockFiles = new HashMap<String,JavaFileObject>(); /** Adds a name and associated mock file to the database of files (for this * context). * @param name the "absolute" filename, that is, the directory as it is on * the specs path followed by the package and filename and suffix, all * forward-slash separated * @param jfo the JavaFileObject associated with the name */ public void addMockFile(String name, JavaFileObject jfo) { mockFiles.put(name,jfo); } /** Creates an appropriate kind of Dir object given the String format of * the argument * @param dirName the directory as specified in the String format of the * specs path * @return an appropriate derived class of Dir representing this directory */ public Dir make(String dirName) { int n; if (dirName.charAt(0) == Strings.mockDirChar) { return new MockDir(dirName); } else if ((n=dirName.indexOf("!")) != -1) { return new JarDir(dirName.substring(0,n),dirName.substring(n+1)); } else if (dirName.endsWith(".jar") || dirName.endsWith(".zip")) { return new JarDir(dirName,""); } else { return new FileSystemDir(dirName); } } /** An abstract class representing a directory element of the specs path. */ abstract static public class Dir { /** The human-readable name of the directory */ protected String name; /** Returns the human-readable name of the directory * @return Returns the human-readable name of the directory */ public String name() { return name; } /** Returns the human-readable name of the directory * @return Returns the human-readable name of the directory */ public String toString() { return name; } /** Returns whether the directory actually exists * @return Returns whether the directory actually exists */ abstract boolean exists(); /** Finds a file with the given path (relative directory, name and * suffix) is present in this directory * @return a JavaFileObject for that file */ abstract public /*@Nullable*/JavaFileObject findFile(String filePath); /** Finds a file with the given path (relative directory, name but * no suffix) is present in this directory with any active JML suffix * (in the order of priority for suffixes). * @return a JavaFileObject for that file */ abstract public /*@Nullable*/JavaFileObject findAnySuffixFile(String filePath); } /** This class handles mock directories - data that appear to be files * within directories but do not actually exist in the file system. */ public class MockDir extends Dir { /** Constructs a mock directory object * @param dirName the path to use for the directory object */ public MockDir(String dirName) { this.name = dirName; } /** Mock directory objects always exist */ @Override public boolean exists() { return true; } @Override public /*@Nullable*/JavaFileObject findFile(String filePath) { String ss = name + "/" + filePath; JavaFileObject j = mockFiles.get(ss); return j; } @Override public /*@Nullable*/JavaFileObject findAnySuffixFile(String filePath) { String ss = name + "/" + filePath; for (String suffix : Strings.suffixes) { JavaFileObject j = mockFiles.get(ss + suffix); if (j != null) return j; } return null; } } /** This class represents conventional file system directories */ public class FileSystemDir extends Dir { /** The java.io.File object for the directory */ protected File dir; /** Creates a Dir object for the given directory; the existence of a * Dir object does not mean that the underlying directory actually * exists * @param dirName the relative or absolute path to the directory */ public FileSystemDir(String dirName) { this.name = dirName; this.dir = new File(dirName); } public FileSystemDir(File dir) { this.name = dir.getName(); this.dir = dir; } @Override public boolean exists() { return dir.exists() && dir.isDirectory(); } @Override public /*@Nullable*/JavaFileObject findFile(String filePath) { File f = new File(dir,filePath); if (f.exists()) { return ((JavacFileManager)context.get(JavaFileManager.class)).getRegularFile(f); } return null; } @Override public /*@Nullable*/JavaFileObject findAnySuffixFile(String filePath) { for (String suffix : Strings.suffixes) { File f = new File(dir,filePath + suffix); if (f.exists()) { return ((JavacFileManager)context.get(JavaFileManager.class)).getRegularFile(f); } } return null; } } /** This class represents .jar (and .zip) files and subdirectories within them */ public class JarDir extends Dir { /** An object holding the path to the archive file (which may not actually * exist) */ protected ZipArchive zipArchive; /** The subdirectory within the archive, with a trailing slash added to * the name, or an empty string if the directory desired is the top-level * of the archive. */ protected String internalDirSlash; /** The directory path within the jar file */ protected RelativePath.RelativeDirectory internalDir; /** Creates a Dir object representing the content or a subdirectory of * a Jar file. * @param zip the absolute or relative path to the jar file itself * @param name the subdirectory within the jar file (or an empty string * if the top-level is desired, not null) */ public JarDir(String zip, String name) { try { this.zipArchive = new ZipArchive(((JavacFileManager)context.get(JavaFileManager.class)),new ZipFile(zip)); } catch (IOException e) { this.zipArchive = null; } this.internalDir = new RelativePath.RelativeDirectory(name); this.internalDirSlash = name.length() == 0 ? name : (name + "/"); this.name = zip + (name.length() == 0 ? name : ("!" + name)); } @Override public boolean exists() { if (zipArchive == null) return false; for (RelativePath.RelativeDirectory f: zipArchive.getSubdirectories()) { if (name.length() == 0) return true; // TODO - check that this works correctly // use contains? if (f.getPath().startsWith(internalDir.getPath())) return true; } return false; } @Override public /*@Nullable*/JavaFileObject findFile(String filePath) { RelativePath file = new RelativePath.RelativeFile(internalDir,filePath); if (zipArchive == null) return null; if (!zipArchive.contains(file)) return null; return zipArchive.getFileObject(internalDir,filePath); } @Override public /*@Nullable*/JavaFileObject findAnySuffixFile(String filePath) { if (zipArchive == null) return null; for (String suffix : Strings.suffixes) { String ss = filePath + suffix; RelativePath file = new RelativePath.RelativeFile(internalDir,ss); if (!zipArchive.contains(file)) continue; JavaFileObject j = zipArchive.getFileObject(internalDir,ss); if (j != null) return j; } return null; } } /** Finds the first specification file (if any) for the given class. It * searches each directory on the specPath, in order, for a file with a * JML suffix (in order), returning the first one found. * * @param classSym The Symbol of the class whose specification file is to be found * @return The file found, or null if none found */ //@ nullable public JavaFileObject findSpecFile(ClassSymbol classSym) { return findAnySpecFile(classSym.fullname.toString()); } /** Finds the first specification file (if any) for the given class. It * searches for the defined suffixes in order, searching the whole specs * path for each one before checking for the next suffix. * [NOTE: In a previous design of JML, this method would search each * directory for any allowed suffix, before moving on to the next * directory - that behavior can be restored by switching the loops.] * * @param className The fully-qualified name of the file to be found, * without a suffix (or the dot before the suffix) either * dot or forward-slash separated * @return The file found, or null if none found */ //@ nullable public JavaFileObject findAnySpecFile(String className) { String s = className.replace('.','/'); for (String suffix : Strings.suffixes){ for (Dir dir: getSpecsPath()) { JavaFileObject j = dir.findFile(s + suffix); if (j != null) return j; } } return null; } /** Finds a specific specification file * * @param filename The fully qualified (package + file + suffix) name of * the specification file to be found, with / separation * @return The file found, or null if not found */ //@ nullable public JavaFileObject findSpecificSpecFile(String filename) { for (Dir dir: getSpecsPath()) { JavaFileObject j = dir.findFile(filename); if (j != null) return j; } return null; } /** Finds a specific file on the sourcepath * * @param filename The fully qualified (package + file + suffix) name of * the file to be found, with / separation * @return The file found, or null if not found */ //@ nullable public JavaFileObject findSpecificSourceFile(String filename) { for (String dir: getSourcePath()) { if (dir.isEmpty()) continue; JavaFileObject j = make(dir).findFile(filename); if (j != null) return j; } return null; } ///////////////////////////////////////////////////////////////////////// /** A debugging method that prints the content of the specs database */ public void printDatabase() { PrintWriter noticeWriter = log.getWriter(WriterKind.NOTICE); try { for (Map.Entry<ClassSymbol,TypeSpecs> e : specsmap.entrySet()) { String n = e.getKey().flatname.toString(); JavaFileObject f = e.getValue().file; noticeWriter.println(n + " " + (f==null?"<NOFILE>":f.getName())); ListBuffer<JmlTree.JmlTypeClause> clauses = e.getValue().clauses; noticeWriter.println(" " + clauses.size() + " CLAUSES"); for (JmlTree.JmlTypeClause j: clauses) { noticeWriter.println(" " + JmlPretty.write(j)); } noticeWriter.println(" " + e.getValue().methods.size() + " METHODS"); for (MethodSymbol m: e.getValue().methods.keySet()) { MethodSpecs sp = getSpecs(m); noticeWriter.println(" " + JmlPretty.write(sp.mods)); noticeWriter.println(" " + m.enclClass().toString() + " " + m.flatName()); noticeWriter.print(JmlPretty.write(sp.cases)); //log.noticeWriter.println(sp.toString(" ")); } noticeWriter.println(" " + e.getValue().fields.size() + " FIELDS"); for (VarSymbol m: e.getValue().fields.keySet()) { FieldSpecs sp = getSpecs(m); noticeWriter.print(" " + JmlPretty.write(sp.mods)); noticeWriter.println(" " + m.enclClass().toString() + " " + m.flatName()); for (JmlTypeClause t: sp.list) { noticeWriter.print(JmlPretty.write(t)); //noticeWriter.println(sp.toString(" ")); } } } noticeWriter.println("MOCK FILES"); for (String s: mockFiles.keySet()) { noticeWriter.println(s + " :: " + mockFiles.get(s)); } } catch (Exception e) { noticeWriter.println("Exception occurred in printing the database: " + e); } } /** Retrieves the specifications for a given type, or null if no specs are * present for this type * @param type the ClassSymbol of the type whose specs are wanted * @return the specifications, or null if there are none in the database */ //@ nullable public TypeSpecs get(ClassSymbol type) { return specsmap.get(type); } /** Retrieves the specifications for a given type, providing and registering * a default if one is not there * @param type the ClassSymbol of the type whose specs are wanted * @return the specifications */ public TypeSpecs getSpecs(ClassSymbol type) { TypeSpecs t = specsmap.get(type); if (t == null) { specsmap.put(type, t=new TypeSpecs(type)); } return t; } /** Deletes the specs for a given type, including all method and field * specs for that type. * @param type the type whose specs are to be deleted */ public void deleteSpecs(ClassSymbol type) { specsmap.put(type, null); } /** Adds the specs for a given type to the database, overwriting anything * already there * @param type the ClassSymbol of the type whose specs are provided * @param spec the specs to associate with the type */ public void putSpecs(ClassSymbol type, TypeSpecs spec) { spec.csymbol = type; specsmap.put(type,spec); if (utils.jmlverbose >= Utils.JMLDEBUG) log.getWriter(WriterKind.NOTICE).println("Saving class specs for " + type.flatname + (spec.decl == null ? " (null declaration)": " (non-null declaration)")); } /** Adds the specs for a given method to the database, overwriting anything * already there. There must already be a specs entry for the owning class * @param m the MethodSymbol of the method whose specs are provided * @param spec the specs to associate with the method */ public void putSpecs(MethodSymbol m, MethodSpecs spec) { if (m.toString().equals("JMLValueSequence()")) Utils.stop(); if (utils.jmlverbose >= Utils.JMLDEBUG) log.getWriter(WriterKind.NOTICE).println(" Saving method specs for " + m.enclClass() + " " + m); getSpecs(m.enclClass()).methods.put(m,spec); } /** Adds the specs for a given initialization block to the database, overwriting anything * already there. The type must already have a spec supplied, to which this * is added. * @param csym the class to which the initialilzation block belongs * @param m the Block whose specs are provided * @param spec the specs to associate with the block */ public void putSpecs(ClassSymbol csym, JCTree.JCBlock m, MethodSpecs spec) { if (utils.jmlverbose >= Utils.JMLDEBUG) log.getWriter(WriterKind.NOTICE).println(" Saving initializer block specs " ); specsmap.get(csym).blocks.put(m,spec); } /** Adds the specs for a given field to the database, overwriting anything * already there. The type must already have a class specs, to which * this is added. * @param m the VarSymbol of the method whose specs are provided * @param spec the specs to associate with the method */ public void putSpecs(VarSymbol m, FieldSpecs spec) { if (utils.jmlverbose >= Utils.JMLDEBUG) log.getWriter(WriterKind.NOTICE).println(" Saving field specs for " + m.enclClass() + " " + m); specsmap.get(m.enclClass()).fields.put(m,spec); } /** Retrieves the specs for a given method * @param m the MethodSymbol of the method whose specs are wanted * @return the specs of the method, or null if none are present */ //@ nullable public MethodSpecs getSpecs(MethodSymbol m) { TypeSpecs t = specsmap.get(m.enclClass()); return t == null ? null : t.methods.get(m); } // TODO - document public JmlMethodSpecs getDenestedSpecs(MethodSymbol m) { MethodSpecs s = getSpecs(m); if (s == null) return null; if (s.cases.deSugared == null) { attr.deSugarMethodSpecs(s.cases.decl,s); } return s.cases.deSugared; } // TODO - document // FIXME - this needs to be made consistent with the below public MethodSpecs defaultSpecs(JmlMethodDecl m) { // FIXME - should use a factory JmlTree.Maker M = JmlTree.Maker.instance(context); JmlMethodSpecs ms = new JmlMethodSpecs(); MethodSpecs mspecs = new MethodSpecs(null,ms); // FIXME - empty instead of null modifiers? ms.pos = m.pos; ms.decl = m; ms.deSugared = null; // FIXME- was ms? ListBuffer<JCExpression> list = new ListBuffer<JCExpression>(); // sym can be null if the method call is in a field initializer (and not in the body of a method) // Not sure when sym.type is null - but possibly when an initializer block is created to hold translated // material from a field initializer for (JCExpression t: m.thrown) { list.add(t); } list.add(JmlTreeUtils.instance(context).makeType(m.pos, Symtab.instance(context).runtimeExceptionType)); JmlMethodClauseSignalsOnly cl = new JmlMethodClauseSignalsOnly(m.pos,JmlTokenKind.SIGNALS_ONLY, list.toList()); JmlSpecificationCase cs = new JmlSpecificationCase(m.pos, M.Modifiers(0), false,null,null,com.sun.tools.javac.util.List.<JmlMethodClause>of(cl)); mspecs.cases.cases = com.sun.tools.javac.util.List.<JmlSpecificationCase>of(cs); return mspecs; } // TODO - document public static MethodSpecs defaultSpecs(Context context, MethodSymbol sym, int pos) { // FIXME - should use a factory JmlTree.Maker M = JmlTree.Maker.instance(context); JmlMethodSpecs ms = new JmlMethodSpecs(); MethodSpecs mspecs = new MethodSpecs(null,ms); // FIXME - empty instead of null modifiers? ms.pos = pos; ms.decl = null; // FIXME - this needs filling in? ms.deSugared = null; // FIXME- was ms? ListBuffer<JCExpression> list = new ListBuffer<JCExpression>(); // sym can be null if the method call is in a field initializer (and not in the body of a method) // Not sure when sym.type is null - but possibly when an initializer block is created to hold translated // material from a field initializer if (sym != null && sym.type != null) for (Type t: sym.getThrownTypes()) { JCExpression e = JmlTreeUtils.instance(context).makeType(pos, t); list.add(e); } list.add(JmlTreeUtils.instance(context).makeType(pos, Symtab.instance(context).runtimeExceptionType)); JmlMethodClauseSignalsOnly cl = new JmlMethodClauseSignalsOnly(pos,JmlTokenKind.SIGNALS_ONLY, list.toList()); JmlSpecificationCase cs = new JmlSpecificationCase(pos, M.Modifiers(0), false,null,null,com.sun.tools.javac.util.List.<JmlMethodClause>of(cl)); mspecs.cases.cases = com.sun.tools.javac.util.List.<JmlSpecificationCase>of(cs); return mspecs; } /** Retrieves the specs for a given field * @param m the VarSymbol of the field whose specs are wanted * @return the specs of the field, or null if none are present */ //@ nullable public FieldSpecs getSpecs(VarSymbol m) { // if (m.name.toString().equals("theString")) Utils.stop(); ClassSymbol c = m.enclClass(); if (c == null) return null; // This happens at least when m is the symbol for 'class' as in int.class TypeSpecs t = getSpecs(c); return t == null ? null : t.fields.get(m); } /** Retrieves the specs for a given initializer block * @param sym the class in which the block resides * @param m the JCBlock of the block whose specs are wanted * @return the specs of the block, or null if none are present */ //@ nullable public MethodSpecs getSpecs(ClassSymbol sym, JCTree.JCBlock m) { TypeSpecs t = specsmap.get(sym); return t == null ? null : t.blocks.get(m); } /** An ADT to hold the JML specifications for a Java class, including * clauses (e.g. invariant), JML modifiers of the class, model/ghost * declarations * @author David Cok * */ public static class TypeSpecs { /** The Symbol for the type these specs belong to*/ public ClassSymbol csymbol; /** The AST from specification files that provide the * specs for the class this TypeSpecs object documents. This is only * valid for a TypeSpecs object holding combined specifications. */ public JmlClassDecl refiningSpecDecls = null; // FIXME - not conssitently used /** The source file for the modifiers, not necessarily for the rest of the specs * if these are the combined specs */ //@ nullable // may be null if there are no specs public JavaFileObject file; /** The JmlClassDecl for the specification */ // FIXME - this is probably better used as the decl of the Java file, if any //@ nullable // may be null if there are no specs public JmlClassDecl decl; // FIXME - with a spec sequence the specs from more than one /** The JML modifiers of the class, as given in the COMBINED specification */ //@ nullable // may be null if there are no specs // FIXME - no, at minimum these are the Java modifiers public JCTree.JCModifiers modifiers; /** Caches the nullity for the associated class: if null, not yet determined; * otherwise, the result of considering explicit declarations, declarations * on containing classes, and the system default. */ //@ nullable private JmlTokenKind defaultNullity = null; /** All the specification clauses for the class (not method clauses or field clauses or block clauses) */ /*@ non_null */ public ListBuffer<JmlTree.JmlTypeClause> clauses; public ListBuffer<JmlTree.JmlTypeClauseDecl> decls; // FIXME - get rid of this - these are all incorporated into the class itself // /** All the model types directly declared in this type */ // @NonNull // public ListBuffer<JmlTree.JmlClassDecl> modelTypes = new ListBuffer<JmlTree.JmlClassDecl>(); // FIXME - get rid of this /** Synthetic methods for model fields (these are also included in the clauses list) */ /*@ non_null */ public ListBuffer<JmlTree.JmlTypeClauseDecl> modelFieldMethods = new ListBuffer<JmlTree.JmlTypeClauseDecl>(); // The following maps are part of the TypeSpecs so that everything associated with a given // type can be disposed of at once (by releasing references to the TypeSpecs instance) /** A map from methods of the class to the specifications for the method. */ /*@ non_null */ public Map<MethodSymbol,MethodSpecs> methods = new HashMap<MethodSymbol,MethodSpecs>(); /** A map from fields of the class to the specifications for the field. */ /*@ non_null */ public Map<VarSymbol,FieldSpecs> fields = new HashMap<VarSymbol,FieldSpecs>(); /** A map from initializer blocks of the class to the specifications for the initializers. */ /*@ non_null */ public Map<JCTree.JCBlock,MethodSpecs> blocks = new HashMap<JCTree.JCBlock,MethodSpecs>(); /** The spec associated with the initializer keyword, if any */ /*@ nullable */ // will be null if there is no initializer specification public JmlTypeClauseInitializer initializerSpec = null; /** The spec associated with the static_initializer keyword, if any */ /*@ nullable */ // will be null if there is no static_initializer specification public JmlTypeClauseInitializer staticInitializerSpec = null; // // FIXME - comment // public JmlMethodDecl checkInvariantDecl; // // FIXME - comment // public JmlMethodDecl checkStaticInvariantDecl; /** A quite empty and unfinished TypeSpecs object for a given class, * possibly but not necessarily one that has only specs and binary, * but no Java source. * @param symbol the class symbol for these specs */ public TypeSpecs(ClassSymbol symbol) { this.csymbol = symbol; this.file = symbol.sourcefile; this.decl = null; this.modifiers = null; this.clauses = new ListBuffer<JmlTree.JmlTypeClause>(); this.decls = new ListBuffer<JmlTree.JmlTypeClauseDecl>(); } // TODO - comment - only partially fills in the class - used for a binary file - I think everything is pretty much empty and null public TypeSpecs(ClassSymbol csymbol, JavaFileObject file, JCTree.JCModifiers mods, ListBuffer<JmlTree.JmlTypeClause> clauses) { this.file = file; this.csymbol = csymbol; this.decl = null; this.modifiers = mods; this.clauses = clauses != null ? clauses : new ListBuffer<JmlTypeClause>(); this.decls = decls != null ? decls : new ListBuffer<JmlTypeClauseDecl>(); } // TODO - comment - only partially fills in the class public TypeSpecs(JmlClassDecl decl) { this.file = decl.source(); this.decl = decl; this.modifiers = decl.mods; this.clauses = decl.typeSpecs != null ? decl.typeSpecs.clauses : new ListBuffer<JmlTree.JmlTypeClause>(); this.decls = decl.typeSpecs != null ? decl.typeSpecs.decls : new ListBuffer<JmlTypeClauseDecl>(); } // // Use when there is no spec for the type symbol (but records the fact // // that we looked and could not find one) // public TypeSpecs() { // this.file = null; // this.decl = null; // this.modifiers = null; // this.clauses = new ListBuffer<>(); // this.decls = new ListBuffer<>(); // } public String toString() { StringWriter s = new StringWriter(); JmlPretty p = new JmlPretty(s, false); for (JmlTypeClause c: clauses) { c.accept(p); try { p.println(); } catch (IOException e) {} // it can't throw up, and ignore if it does } for (JmlTree.JmlTypeClauseDecl c: decls) { c.accept(p); try { p.println(); } catch (IOException e) {} // it can't throw up, and ignore if it does } // if (modelTypes != null) for (JmlClassDecl c: modelTypes) { // c.accept(p); // try { p.println(); } catch (IOException e) {} // it can't throw up, and ignore if it does // } return s.toString(); } } /** Returns the default nullity for the given class - don't call this until * classes have been entered and annotations have been attributed. If the * argument is null, then the default nullity as set on the command-line is returned. * Note that the default nullity for the class is cached in the class specs once * computed, to avoid recompuation. * @param csymbol the class whose default nullity is to be determined; if * null the default system nullity setting (per the command-line) is returned * @return JmlToken.NULLABLE or JmlToken.NONNULL */ //@ ensures \result != null; public /*@non_null*/ JmlTokenKind defaultNullity(/*@ nullable*/ ClassSymbol csymbol) { if (csymbol == null) { // FIXME - this is no longer true // Note: NULLABLEBYDEFAULT turns off NONNULLBYDEFAULT and vice versa. // If neither one is present, then the logic here will give the // default as NONNULL. if (JmlOption.isOption(context,JmlOption.NULLABLEBYDEFAULT)) { return JmlTokenKind.NULLABLE; } else if (JmlOption.isOption(context,JmlOption.NONNULLBYDEFAULT)) { return JmlTokenKind.NONNULL; } else { return JmlTokenKind.NONNULL; // The default when nothing is specified } } { Env<AttrContext> env = JmlEnter.instance(context).getEnv(csymbol); if (env != null) { JCTree tree = env.tree; if (tree != null && tree instanceof JmlClassDecl) { JmlClassDecl decl = (JmlClassDecl)tree; return isNonNull(decl) ? JmlTokenKind.NONNULL : JmlTokenKind.NULLABLE; } } } TypeSpecs spec = get(csymbol); // spec is null if the TypeSpecs are in the process of being initialized if (spec == null || spec.defaultNullity == null) { JmlTokenKind t = null; if (spec.refiningSpecDecls == null) { if (csymbol.getAnnotationMirrors() != null) { if (csymbol.attribute(attr.nullablebydefaultAnnotationSymbol) != null) { t = JmlTokenKind.NULLABLE; } else if (csymbol.attribute(attr.nonnullbydefaultAnnotationSymbol) != null) { t = JmlTokenKind.NONNULL; } } } else { JCModifiers mods = spec.refiningSpecDecls.mods; if (spec.decl.specsDecl != null) mods = spec.decl.specsDecl.mods; if (utils.findMod(mods, attr.nullablebydefaultAnnotationSymbol) != null) { t = JmlTokenKind.NULLABLE; } else if (utils.findMod(mods, attr.nonnullbydefaultAnnotationSymbol) != null) { t = JmlTokenKind.NONNULL; } } if (t == null) { Symbol sym = csymbol.owner; // The owner might be a package - currently no annotations for packages if (sym instanceof ClassSymbol) { t = defaultNullity((ClassSymbol)sym); } else { t = defaultNullity(null); } } spec.defaultNullity = t; } return spec.defaultNullity; } // Not complete public /*@non_null*/ JCAnnotation defaultNullityAnnotation(/*@ nullable*/ ClassSymbol csymbol) { if (csymbol == null) { // FIXME - this is no longer true // Note: NULLABLEBYDEFAULT turns off NONNULLBYDEFAULT and vice versa. // If neither one is present, then the logic here will give the // default as NONNULL. JmlAnnotation a; // if (JmlOption.isOption(context,JmlOption.NULLABLEBYDEFAULT)) { // a = new JmlAnnotation(nullablebydefaultAnnotationSymbol.type, com.sun.tools.javac.util.List.<JCExpression>nil()); // } else if (JmlOption.isOption(context,JmlOption.NONNULLBYDEFAULT)) { // return JmlToken.NONNULL; // } else { // return JmlToken.NONNULL; // The default when nothing is specified // } return null; } JCModifiers mods = JmlSpecs.instance(context).getSpecs(csymbol).decl.mods; JCAnnotation a = utils.findMod(mods,attr.nullablebydefaultAnnotationSymbol); if (a != null) return a; a = utils.findMod(mods,attr.nonnullbydefaultAnnotationSymbol); if (a != null) return a; Symbol owner = csymbol.owner; if (owner instanceof Symbol.PackageSymbol) owner = null; return defaultNullityAnnotation((Symbol.ClassSymbol)owner); } /** Caches the symbol for the org.jmlspecs.annotation.NonNull */ ClassSymbol nonnullAnnotationSymbol = null; /** Caches the symbol for the org.jmlspecs.annotation.Nullable */ ClassSymbol nullableAnnotationSymbol = null; ClassSymbol nullablebydefaultAnnotationSymbol = null; ClassSymbol nonnullbydefaultAnnotationSymbol = null; /** Returns whether the given symbol is non-null (either explicitly or by * default); the second parameter is the enclosing class. * @param symbol the symbol whose nullity is being checked - either a VarDef (a * parameter declaration) or a MethodDef (for the return type) * @param csymbol the enclosing class, from which any default comes * @return true if the symbol is non-null explicitly or by default */ public boolean isNonNull(Symbol symbol) { return isNonNull(symbol,symbol.enclClass()); } public boolean isNonNull(JmlVariableDecl decl) { if (decl.type.isPrimitive()) return false; makeAnnotationSymbols(); if (decl.specsDecl != null) { // FIXME specsDecl should not be null - it should point back to decl at a minimum if (utils.findMod(decl.specsDecl.mods, nullableAnnotationSymbol) != null) return false; if (utils.findMod(decl.specsDecl.mods, nonnullAnnotationSymbol) != null) return true; } else { if (utils.findMod(decl.mods, nullableAnnotationSymbol) != null) return false; if (utils.findMod(decl.mods, nonnullAnnotationSymbol) != null) return true; } // Need the owning class - fields are owned by the class, but parameters and local variables are owned by the method Symbol owner = decl.sym.owner; if (owner instanceof MethodSymbol) owner = owner.owner; Env<AttrContext> env = JmlEnter.instance(context).getEnv((Symbol.TypeSymbol)owner); if (env != null) { JmlClassDecl cdecl = (JmlClassDecl)env.tree; return isNonNull(cdecl); } else { return defaultNullity((ClassSymbol)owner) == JmlTokenKind.NONNULL; } } public boolean isNonNull(JmlClassDecl decl) { // FIXM E- change this to return the token that defines the nullity makeAnnotationSymbols(); if (decl.specsDecl != null) { if (utils.findMod(decl.specsDecl.mods, nullablebydefaultAnnotationSymbol) != null) return false; if (utils.findMod(decl.specsDecl.mods, nonnullbydefaultAnnotationSymbol) != null) return true; } else { if (utils.findMod(decl.mods, nullablebydefaultAnnotationSymbol) != null) return false; if (utils.findMod(decl.mods, nonnullbydefaultAnnotationSymbol) != null) return true; } Symbol parent = decl.sym.owner; if (!(parent instanceof Symbol.ClassSymbol)) return defaultNullity(null) == JmlTokenKind.NONNULL; // FIXME - is this OK for interfaces if (Enter.instance(context).getEnv((Symbol.TypeSymbol)parent) == null) return defaultNullity(null) == JmlTokenKind.NONNULL; JmlClassDecl encl = (JmlClassDecl)Enter.instance(context).getEnv((Symbol.TypeSymbol)parent).tree; return isNonNull(encl); } public void makeAnnotationSymbols() { if (nonnullAnnotationSymbol == null) { nonnullAnnotationSymbol = ClassReader.instance(context).enterClass(Names.instance(context).fromString(Strings.nonnullAnnotation)); } if (nullableAnnotationSymbol == null) { nullableAnnotationSymbol = ClassReader.instance(context).enterClass(Names.instance(context).fromString(Strings.nullableAnnotation)); } if (nullablebydefaultAnnotationSymbol == null) { nullablebydefaultAnnotationSymbol = ClassReader.instance(context).enterClass(Names.instance(context).fromString(Strings.nullablebydefaultAnnotation)); } if (nonnullbydefaultAnnotationSymbol == null) { nonnullbydefaultAnnotationSymbol = ClassReader.instance(context).enterClass(Names.instance(context).fromString(Strings.nonnullbydefaultAnnotation)); } } public boolean isNonNull(Symbol symbol, ClassSymbol csymbol) { if (symbol.type.isPrimitive()) return false; // TODO - perhaps cache these when the JmlSpecs class is created? (watch for circular tool creation) makeAnnotationSymbols(); Attribute.Compound attr; if (symbol instanceof Symbol.VarSymbol && symbol.owner instanceof Symbol.ClassSymbol) { // Field FieldSpecs fspecs = getSpecs((Symbol.VarSymbol)symbol); if (fspecs == null) return false; // FIXME - we need private fields of binary classes that have no specs declared to be nullable if (fspecs != null && utils.findMod(fspecs.mods,nullableAnnotationSymbol) != null) return false; else if (fspecs != null && utils.findMod(fspecs.mods,nonnullAnnotationSymbol) != null) return true; else if (symbol.name == names._this) return true; else return defaultNullity((Symbol.ClassSymbol)symbol.owner) == JmlTokenKind.NONNULL; } else if (symbol instanceof Symbol.VarSymbol && (symbol.owner == null || symbol.owner instanceof Symbol.MethodSymbol)) { attr = symbol.attribute(nullableAnnotationSymbol); if (attr != null) return false; attr = symbol.attribute(nonnullAnnotationSymbol); if (attr != null) return true; // Method parameter or variable in body // MethodSpecs mspecs = getSpecs((Symbol.MethodSymbol)symbol.owner); // FIXME - not clear we are able to look up a particular parameter - which case do we use? don't want inherited specs? // specs.cases.decl // if (mspecs != null && utils.findMod(mspecs.mods,nullableAnnotationSymbol) != null) return false; // else if (mspecs != null && utils.findMod(mspecs.mods,nonnullAnnotationSymbol) != null) return true; // else return defaultNullity(csymbol) == JmlToken.NONNULL; // Need to distinguish the two cases. The following is correct for variables in the body return defaultNullity(csymbol) == JmlTokenKind.NONNULL; } else if (symbol instanceof Symbol.MethodSymbol) { // Method return value MethodSpecs mspecs = getSpecs((Symbol.MethodSymbol)symbol); if (mspecs != null && utils.findMod(mspecs.mods,nullableAnnotationSymbol) != null) return false; else if (mspecs != null && utils.findMod(mspecs.mods,nonnullAnnotationSymbol) != null) return true; else return defaultNullity(csymbol) == JmlTokenKind.NONNULL; } else { // What else? attr = symbol.attribute(nullableAnnotationSymbol); // FIXME - the symbol might be 'THIS' which should always be non_null if (attr != null) return false; attr = symbol.attribute(nonnullAnnotationSymbol); if (attr != null) return true; return defaultNullity(csymbol) == JmlTokenKind.NONNULL; } } /** Caches the symbol for a Pure annotation, which is computed on demand. */ private ClassSymbol pureAnnotationSymbol = null; private ClassSymbol functionAnnotationSymbol = null; /** Returns true if the given method symbol is annotated as Pure */ public boolean isPure(MethodSymbol symbol) { if (pureAnnotationSymbol == null) { pureAnnotationSymbol = utils.createClassSymbol(Strings.pureAnnotation); } if (functionAnnotationSymbol == null) { functionAnnotationSymbol = utils.createClassSymbol(Strings.functionAnnotation); } MethodSpecs mspecs = getSpecs((Symbol.MethodSymbol)symbol); if (mspecs != null && utils.findMod(mspecs.mods,pureAnnotationSymbol) != null) return true; if (mspecs != null && utils.findMod(mspecs.mods,functionAnnotationSymbol) != null) return true; TypeSpecs tspecs = getSpecs((Symbol.ClassSymbol)symbol.owner); // FIXME - the following will not find a pure annotation on the class in a .jml file. if (tspecs != null && utils.findMod(tspecs.modifiers,pureAnnotationSymbol) != null) return true; return false; } public JCAnnotation fieldSpecHasAnnotation(VarSymbol sym, JmlTokenKind token) { FieldSpecs fspecs = getSpecs(sym); if (fspecs == null) return null; Symbol annotationSymbol = attr.tokenToAnnotationSymbol.get(token); return utils.findMod(fspecs.mods,annotationSymbol); } public JCAnnotation methodSpecHasAnnotation(MethodSymbol sym, JmlTokenKind token) { MethodSpecs mspecs = getSpecs(sym); if (mspecs == null) return null; Symbol annotationSymbol = attr.tokenToAnnotationSymbol.get(token); return utils.findMod(mspecs.mods,annotationSymbol); } /** Adds the specs in the second argument to the stored specs for the * given class symbol. Presumes there is already at least an empty * stored specs structure. */ public JmlSpecs.TypeSpecs combineSpecs(ClassSymbol sym, /*@ nullable */ JmlClassDecl javaClassDecl, /*@ nullable */ JmlClassDecl specClassDecl) { JmlSpecs.TypeSpecs tspecs = new TypeSpecs(sym); putSpecs(sym, tspecs); tspecs.csymbol = sym; tspecs.decl = specClassDecl; tspecs.refiningSpecDecls = specClassDecl; if (specClassDecl != null) { tspecs.modifiers = specClassDecl.mods; tspecs.file = specClassDecl.source(); } else { tspecs.modifiers = null; if (javaClassDecl != null) tspecs.file = javaClassDecl.source(); } tspecs.defaultNullity = defaultNullity(sym); // tspecs is to be the combinedSpecs // It already has: // csymbol, // refiningSpecDecls // file // Also, if tspecs.decl is non-null, it already has tspecs.decl.typeSpecs == tspecs; // Not set here: // modelFieldMethods // checkInvariantDecl, checkStaticInvariantDecl (RAC related) if (tspecs.decl != null && specClassDecl != tspecs.decl ) { log.getWriter(WriterKind.NOTICE).println("PRECONDITION FALSE IN COMBINESPECS " + sym + " " + (specClassDecl != null) + " " + (tspecs.decl != null)); } // FIXME - do not bother copying if there is only one file // modelFieldMethods, checkInvariantDecl, checkStaticInvariantDecl not relevant yet ListBuffer<JCTree> newlist = new ListBuffer<JCTree>(); if (specClassDecl != null) { for (JCTree t: specClassDecl.defs) { JCTree tt = t; if (t instanceof JCTree.JCBlock) { JCTree.JCBlock b = (JCTree.JCBlock)t; tspecs.blocks.put(b, null); } else if (t instanceof JmlTypeClauseInitializer) { JmlTypeClauseInitializer init = (JmlTypeClauseInitializer)t; //if (!utils.isJMLStatic(init.modifiers, sym)) { if (init.token == JmlTokenKind.INITIALIZER) { if (tspecs.initializerSpec != null) { log.error(init, "jml.one.initializer.spec.only"); tt = null; } else { tspecs.initializerSpec = init; tspecs.clauses.add((JmlTypeClause)t); tt = null; } } else { if (tspecs.staticInitializerSpec != null) { log.error(init, "jml.one.initializer.spec.only"); tt = null; } else { tspecs.staticInitializerSpec = init; tspecs.clauses.add((JmlTypeClause)t); tt = null; } } } else if (t instanceof JmlMethodDecl) { JmlMethodDecl md = (JmlMethodDecl)t; tspecs.methods.put(md.sym, md.methodSpecsCombined ); } else if (t instanceof JmlVariableDecl) { JmlVariableDecl md = (JmlVariableDecl)t; tspecs.fields.put(md.sym, md.fieldSpecsCombined ); } else if (t instanceof JmlTypeClauseDecl) { tspecs.decls.add((JmlTypeClauseDecl)t); } else if (t instanceof JmlTypeClause) { tspecs.clauses.add((JmlTypeClause)t); tt = null; } if (tt != null) newlist.add(tt); } specClassDecl.defs = newlist.toList(); } return tspecs; } /** An ADT to hold the specs for a method or block */ public static class MethodSpecs { public JCTree.JCModifiers mods; public VarSymbol queryDatagroup; public VarSymbol secretDatagroup; public JmlMethodSpecs cases; public MethodSpecs(JmlMethodDecl m) { this.mods = m.mods; cases = m.cases != null ? m.cases : new JmlMethodSpecs(); cases.decl = m; } public MethodSpecs(JCTree.JCModifiers mods, JmlMethodSpecs m) { this.mods = mods; cases = m != null ? m : new JmlMethodSpecs(); } public String toString() { return JmlPretty.write(mods) + Strings.eol + JmlPretty.write(cases); } } /** An ADT to hold the specs for a field declaration */ public static class FieldSpecs { /** Modifiers pertinent to this fields specifications */ public JCTree.JCModifiers mods; /** A list of the clauses pertinent to this field (e.g. in, maps) */ public ListBuffer<JmlTree.JmlTypeClause> list = new ListBuffer<JmlTree.JmlTypeClause>(); public JavaFileObject source() { return decl.source(); } public JmlVariableDecl decl; /** Creates a FieldSpecs object initialized with only the given modifiers */ public FieldSpecs(JmlVariableDecl decl) { this.decl = decl; this.mods = decl.mods; decl.fieldSpecsCombined = this; } @Override public String toString() { StringWriter s = new StringWriter(); JmlPretty p = new JmlPretty(s, false); mods.accept(p); try { p.println(); } catch (IOException e) {} // it can't throw up, and ignore if it does // FIXME - println only if there are mods? for (JmlTypeClause c: list) { c.accept(p); try { p.println(); } catch (IOException e) {} // it can't throw up, and ignore if it does } return s.toString(); } } /** Finds the specs file for a given compilation unit. * @param jmlcu The compilation unit of the Java source, if any * @param specs if true, looks for any specs file; if false, looks for Java file * @return the source object of the specifications */ /*@ nullable */ public JavaFileObject findSpecs(JmlCompilationUnit jmlcu, boolean specs) { JCTree.JCExpression pkgName = jmlcu.getPackageName(); String pack = pkgName == null ? null : pkgName.toString(); String filepath = jmlcu.getSourceFile().getName(); // In the following, we need a name as the prefix to look for the specs. // That is supposed to be the same as the name of the public class within // the file, and thus the same as the name of the file itself. // However, a file may have no public classes within it - so // the best indication of the spec file name is the name of the // java file just parsed. // (TODO) Unfortunately, there is no guarantee as to what getName() // will return. It would be safer, but a pain, to dismember the // associated URI. (getName is even deprecated within some subclasses) int i = filepath.lastIndexOf('/'); int ii = filepath.lastIndexOf('\\'); if (i < ii) i = ii; int k = filepath.lastIndexOf("."); String rootname = k >= 0 ? filepath.substring(i+1,k) : filepath.substring(i+1); JavaFileObject f; if (specs) { f = JmlSpecs.instance(context).findAnySpecFile(pack == null ? rootname : (pack + "." + rootname)); } else { rootname = rootname + Strings.javaSuffix; f = JmlSpecs.instance(context).findSpecificSourceFile(pack == null ? rootname : (pack + "." + rootname)); } return f; } }