/* * This file is part of the OpenJML project. * Author: David R. Cok */ package org.jmlspecs.openjml; import static com.sun.tools.javac.code.Flags.STATIC; import static com.sun.tools.javac.code.Flags.UNATTRIBUTED; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import javax.tools.JavaFileObject; import org.jmlspecs.annotation.NonNull; import org.jmlspecs.openjml.JmlSpecs.MethodSpecs; import org.jmlspecs.openjml.JmlTree.IInJML; import org.jmlspecs.openjml.JmlTree.JmlClassDecl; import org.jmlspecs.openjml.JmlTree.JmlMethodDecl; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.code.Kinds; import com.sun.tools.javac.code.Scope; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Symbol.MethodSymbol; import com.sun.tools.javac.code.Symbol.TypeSymbol; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Types; import com.sun.tools.javac.comp.AttrContext; 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.jvm.ClassReader; import com.sun.tools.javac.parser.JmlScanner; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCClassDecl; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; 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.JCDiagnostic; 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.Name; import com.sun.tools.javac.util.Names; import com.sun.tools.javac.util.Options; import com.sun.tools.javac.util.PropagatedException; import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; import com.sun.tools.javac.util.JCDiagnostic.SimpleDiagnosticPosition; /** This class holds a number of utility methods. They could often be * static, but we make this a registerable tool, so that it can be * overridden by some enterprising future person. */ public class Utils { /** This field is used to restrict output during testing so as to * make test results more deterministic (or to match old test results). */ static public boolean testingMode = false; /////////////////////////////////////////////////////////////////////////////////////// /** The context applicable for this instance of the Utils class. */ protected Context context; /** The Log object - do not use this directly - use log() instead */ private Log log; /** The key to use to retrieve the instance of this class from the Context object. */ //@ non_null public static final Context.Key<Utils> utilsKey = new Context.Key<Utils>(); /** A method that returns the unique instance of this class for the given Context * * @param context the Context whose JmlSpecs instance is wanted * @return the singleton instance (per Context) of this class */ //@ non_null public static Utils instance(Context context) { Utils instance = context.get(utilsKey); if (instance == null) { instance = new Utils(context); // registers itself } return instance; } /** Creates an instance in association with the given Context; * @param context The compilation context */ protected Utils(Context context) { this.context = context; context.put(utilsKey, this); } /** The error and warning log. It is crucial that the log be obtained * lazily, and not before options are read; otherwise the Log object * is not properly intialized from the Java options. */ public final Log log() { if (log == null) log = Log.instance(context); return log; } /** Global utility value that enables printing of debugging or trace information. */ public int jmlverbose = 0; static public final int QUIET = 0; static public final int NORMAL = 1; static public final int PROGRESS = 2; static public final int JMLVERBOSE = 3; static public final int JMLDEBUG = 4; /** Do ESC - set by Main.setupOptions */ public boolean esc = false; /** Do RAC - set by Main.setupOptions */ public boolean rac = false; /** Do JML check only - set by Main.setupOptions */ public boolean check = false; /** Do Java compilation - set by Main.setupOptions */ public boolean compile = false; /** Do Jmldoc */ public boolean doc = false; /** Max number of ESC warnings per method (set from an option) */ public int maxWarnings = 1; /** The set of keys that control the use of optional comments, set from options */ public Set<String> commentKeys = new HashSet<String>(); /** A bit that indicates that a declaration was declared within a JML annotation (so that it should not be visible to Java) */ final public static long JMLBIT = 1L << 60; // Any bit that does not conflict with bits in com.sun.tools.javac.code.Flags. /** A bit that indicates that a declaration was declared somewhere within a JML annotation, but not nested within a class or method body that is also in the JML annotation */ final public static long JMLBITTOP = 1L << 59; // Any bit that does not conflict with bits in com.sun.tools.javac.code.Flags. /** A bit that indicates that JML instrumentation has been added .... FIXME */ final public static long JMLINSTRUMENTED = 1L << 61; // Any bit that does not conflict with bits in com.sun.tools.javac.code.Flags. /** A bit that indicates that a variable is local to an expression */ final public static long JMLEXPRLOCAL = 1L << 62; // Any bit that does not conflict with bits in com.sun.tools.javac.code.Flags. // FIXME - describe - used to be the DEFAULT flag final public static long JMLADDED = 1L << 58; /** Tests whether the JML flag is set in the given modifiers object * @param mods the instance of JCModifiers to test * @return true if JML is set */ public boolean isJML(/*@ nullable */ JCModifiers mods) { return mods != null && (mods.flags & JMLBIT) != 0; } public boolean isJMLTop(/*@ nullable */ JCModifiers mods) { return mods != null && (mods.flags & JMLBITTOP) != 0; } /** Tests whether the given tree was directly parsed as part of JML annotation; * nested declarations that are not themselves directly in a JML comment will return false, * even if they are nested in a class that itself is directly in a JML comment. */ public boolean isJML(JCTree t) { return (t instanceof IInJML) && ((IInJML)t).isJML(); } // public boolean isJMLTop(JCTree t) { // return (t instanceof IInJML) && ((IInJML)t).isJMLTop(); // } /** Tests whether the JML flag is set in the given bit-vector * @param flags the bit-array to test * @return true if JML is set */ public boolean isJML(long flags) { return (flags & JMLBIT) != 0; } public boolean isJMLTop(long flags) { return (flags & JMLBITTOP) != 0; } /** Sets the JML flag in the given modifiers. * * @param mods The modifiers in which to set the JML flag */ public void setJML(/*@ non_null */ JCModifiers mods) { mods.flags |= JMLBIT; } public void setJMLTop(/*@ non_null */ JCModifiers mods) { mods.flags |= JMLBITTOP; } /** Unsets the JML flag in the given modifiers. * * @param mods The modifiers in which to set the JML flag */ public void unsetJML(/*@ non_null */ JCModifiers mods) { mods.flags &= ~JMLBIT; } // FIXME - document public boolean isInstrumented(long flags) { return (flags & JMLINSTRUMENTED) != 0; } // FIXME - document public void setInstrumented(/*@ non_null */JCModifiers mods) { mods.flags |= JMLINSTRUMENTED; } // IS this flag used for anything? FIXME /** Returns true if the modifiers is marked as local to a JML expression */ public boolean isExprLocal(/*@ non_null */ JCModifiers mods) { return (mods.flags & JMLEXPRLOCAL) != 0; } /** Returns true if the modifiers is marked as local to a JML expression */ public boolean isExprLocal(long flags) { return (flags & JMLEXPRLOCAL) != 0; } /** Sets the modifiers as local to a JML expression */ public void setExprLocal(/*@ non_null */ JCModifiers mods) { mods.flags |= JMLEXPRLOCAL; } /** Creates an annotation symbol from the fully qualified name for the * annotation; generally the result is cached. * @param fullyQualifiedName the fully qualified name * @return the annotation symbol */ public ClassSymbol createClassSymbol(String fullyQualifiedName) { return ClassReader.instance(context). enterClass(Names.instance(context).fromString(fullyQualifiedName)); } /** A cache for the symbol */ private ClassSymbol helperAnnotationSymbol = null; /** Returns true if the given symbol has a helper annotation * * @param symbol the symbol to check * @return true if there is a helper annotation */ public boolean isHelper(@NonNull Symbol symbol) { if (helperAnnotationSymbol == null) { helperAnnotationSymbol = createClassSymbol(Strings.helperAnnotation); } return symbol.attribute(helperAnnotationSymbol)!=null; } /** Returns true if the given symbol is marked static or is a member of a JML interface * that is not marked as 'instance' */ public boolean isJMLStatic(Symbol sym) { // non-static Simple identifier is OK // If the owner of the field is an interface, it // is by default static. However, it might be a // JML field marked as instance. if (sym.owner == null) { if ((sym.flags() & STATIC) == 0) return false; } else { if (!sym.isStatic()) return false; } if (isJML(sym.flags())) { Symbol csym = sym.owner; if (csym != null && (csym.flags() & Flags.INTERFACE) != 0) { // TODO - should cleanup this reference to JmlAttr from Utils if (JmlAttr.instance(context).hasAnnotation(sym,JmlTokenKind.INSTANCE)) return false; } } return true; } public boolean isJMLStatic(JCModifiers mods, ClassSymbol csym) { // non-static Simple identifier is OK // If the owner of the field is an interface, it // is by default static. However, it might be a // JML field marked as instance. if ((csym.flags() & Flags.INTERFACE) != 0) { // TODO - should cleanup this reference to JmlAttr from Utils if (JmlAttr.instance(context).findMod(mods,JmlTokenKind.INSTANCE) != null) return false; } return ((mods.flags & Flags.STATIC) != 0); } // FIXME - document public Object envString(/*@ non_null */Env<AttrContext> env) { return (env.tree instanceof JCCompilationUnit ? ((JCCompilationUnit)env.tree).sourcefile : env.tree instanceof JCClassDecl ? ((JCClassDecl)env.tree).sym.flatName() : env.tree.getClass()); } /** Returns true if no standard modifiers or annotations have been set * @param mods the modifiers structure to check * @return true if any standard flags or annotations are set */ // FIXME - should this check for just JML annotations? public boolean hasNone(/*@ nullable */JCModifiers mods) { return mods == null || ((mods.flags&Flags.StandardFlags) == 0 && (mods.annotations == null || mods.annotations.isEmpty())); } /** Returns true if any of the specified Java modifiers is set * @param mods the modifiers structure to check * @return true if any of the given flags are set */ public boolean hasAny(/*@ nullable */JCModifiers mods, long flags) { return mods != null && ((mods.flags&flags) != 0); } /** Returns non-zero if any Java modifiers other than those specified are set * @param mods the modifiers structure to check * @return bit-vector of the offending flags */ public long hasOnly(/*@ nullable */JCModifiers mods, long flags) { if (mods == null) return 0; return mods.flags & ~flags & Flags.StandardFlags; } /** Finds whether a specified annotation is present in the given modifiers, * returning it if it is; this method requires that the annotations have * already been attributed. * @param mods the modifiers to search * @param m the Name (fully qualified) of the annotation type to find * @return the annotation AST if present, null if not */ //@ nullable public JmlTree.JmlAnnotation findMod(/*@ nullable */ JCModifiers mods, /*@ non_null */Name m) { if (mods == null) return null; for (JCTree.JCAnnotation a: mods.annotations) { Type t = a.annotationType.type; if (t != null) { // FIXME - can this be done by comparing symbols rather than strings if (((Symbol.ClassSymbol)t.tsym).fullname.equals(m)) return (JmlTree.JmlAnnotation)a; } else { // FIXME this is not going to work for unattributed and not-fully qualified annotations String s = a.annotationType.toString(); if (m.toString().equals(s)) return (JmlTree.JmlAnnotation)a; if (m.toString().equals(Strings.jmlAnnotationPackage + "."+s)) return (JmlTree.JmlAnnotation)a; // FIXME - fix attribution of annotations in MemberEnter } } return null; } public JmlTree.JmlAnnotation findMod(/*@ nullable */ JCModifiers mods, /*@ non_null */JmlTokenKind ta) { if (mods == null) return null; return findMod(mods,JmlAttr.instance(context).tokenToAnnotationSymbol.get(ta)); } /** Finds whether a specified annotation is present in the given modifiers, * returning it if it is; this method requires that the annotations have * already been attributed. * @param mods the modifiers to search * @param asym the symbol of the annotation type to find * @return the annotation AST if present, null if not */ public JmlTree.JmlAnnotation findMod(/*@ nullable */ JCModifiers mods, /*@ non_null */Symbol asym) { if (mods == null) return null; for (JCTree.JCAnnotation a: mods.annotations) { Type t = a.annotationType.type; if (t != null) { if (t.tsym.equals(asym)) return (JmlTree.JmlAnnotation)a; } else { // FIXME this is not going to work for unattributed and not-fully qualified annotations, and at best is a real hack String s = a.annotationType.toString(); if (asym.flatName().toString().endsWith(s)) return (JmlTree.JmlAnnotation)a; } } return null; } // public boolean hasAnnotation(Symbol sym, JmlTokenKind token) { // for (com.sun.tools.javac.code.Attribute.Compound c: sym.getDeclarationAttributes()) { // String s = c.toString(); // String ss = token.annotationType.toString(); // if (s.equals(ss)) return true; // } // return false; // } /** Finds a member of a class with a given name; note that this works for methods only * if the method is uniquely named. */ public Symbol findMember(TypeSymbol sym, String name) { Name n = Names.instance(context).fromString(name); for (Symbol s: sym.getEnclosedElements()) { if (s.name.equals(n)) return s; } return null; } /** Returns true if the given String ends with a valid JML suffix, including the * period; there are no further checks that the argument is a sensible filename. * @param filename the String to check * @return true if the input ends in a valid JML suffix */ public boolean hasValidSuffix(String filename) { for (String s : Strings.suffixes) { if (filename.endsWith(s)) return true; } return false; } public boolean hasJavaSuffix(String filename) { return (filename.endsWith(".java")); } /** A little class to encapsulate elapsed wall-clock time */ public static class Timer { /** Time the object was constructed or reset */ protected long startTime; /** Constructs a new object, marking the time */ public Timer() { reset(); } /** Resets the timestamp */ public void reset() { startTime = System.currentTimeMillis(); } /** Returns the wall-clock time elapsed since object construction or the * most recent call to reset * * @return elapsed time in milliseconds */ public long elapsed() { return System.currentTimeMillis() - startTime; } } // FIXME - this is in the wrong class /** This method is never actually executed. It is here to provide a * convenient signature for a method used by ESC - that maps each class * to a distinct integer - not necessarily its hashCode (which are not * necessarily unique). * @param c * @return a distinct integer for the given class */ public int distinct(Class<?> c) { return c.hashCode(); } // FIXME - document public void notImplemented(DiagnosticPosition pos, String feature) { // FIXME - control with an option if (rac) log().warning(pos,"jml.not.implemented.rac",feature); else if (esc) log().warning(pos,"jml.not.implemented.esc",feature); } /** Finds OpenJML properties files in pre-defined places, reading their * contents and loading them into the System property set. */ public static Properties findProperties(Context context) { // boolean verbose = Utils.instance(context).jmldebug || // JmlOption.isOption(context,JmlOption.JMLVERBOSE) || // Options.instance(context).get("-verbose") != null; boolean verbose = context != null && Utils.instance(context).jmlverbose >= Utils.JMLVERBOSE; Properties properties = System.getProperties(); PrintWriter noticeWriter = Log.instance(context).getWriter(WriterKind.NOTICE); // Load properties files found in these locations: // These are read in inverse order of priority, so that later reads // overwrite the earlier ones. // On the system classpath { URL url2 = ClassLoader.getSystemResource(Strings.propertiesFileName); if (url2 != null) { String s = url2.getFile(); try { boolean found = readProps(properties,s); if (verbose) { if (found) noticeWriter.println("Properties read from system classpath: " + s); else noticeWriter.println("No properties found on system classpath: " + s); } } catch (java.io.IOException e) { noticeWriter.println("Failed to read property file " + s); // FIXME - review } } } // In the user's home directory // Note that this implementation does not read through symbolic links { String s = System.getProperty("user.home") + "/" + Strings.propertiesFileName; try { boolean found = readProps(properties,s); if (verbose) { if (found) noticeWriter.println("Properties read from user's home directory: " + s); else noticeWriter.println("No properties found in user's home directory: " + s); } } catch (java.io.IOException e) { noticeWriter.println("Failed to read property file " + s); // FIXME - review } } // In the working directory { String s = System.getProperty("user.dir") + "/" + Strings.propertiesFileName; try { boolean found = readProps(properties,s); if (verbose) { if (found) noticeWriter.println("Properties read from working directory: " + s); else noticeWriter.println("No properties found in working directory: " + s); } } catch (java.io.IOException e) { noticeWriter.println("Failed to read property file " + s); // FIXME - review } } // FIXME - add on the application classpath // check if -properties or -properties-default option is set. { String properties_file = JmlOption.value(context,JmlOption.PROPERTIES_DEFAULT); if (properties_file != null) { try { boolean found = readProps(properties,properties_file); if (verbose) { if (found) noticeWriter.println("Properties read from file: " + properties_file); else noticeWriter.println("No properties file option found: " + properties_file); } } catch (java.io.IOException e) { noticeWriter.println("Failed to read property file " + properties_file); // FIXME - review } } else { if (verbose) noticeWriter.println("No properties file option is set"); } } if (verbose) { // Print out the properties for (String key: new String[]{"user.home","user.dir"}) { noticeWriter.println("Environment: " + key + " = " + System.getProperty(key)); } for (java.util.Map.Entry<Object,Object> entry: properties.entrySet()) { noticeWriter.println("Local property: " + entry.getKey() + " = " + entry.getValue()); } } return properties; } /** Reads properties from the given file into the given Properties object. * @param properties the object to add properties to * @param filename the file to read properties from * @return true if the file was found and read successfully */ public static boolean readProps(Properties properties, String filename) throws java.io.IOException { // Note: Java, or at least this code, does not read through Cygwin symbolic links Path filepath = Paths.get(filename); if (filepath.toFile().exists()) { try (InputStream stream = Files.newInputStream(filepath)) { properties.load(stream); return true; } } else { return false; } } // Includes self public java.util.List<ClassSymbol> parents(TypeSymbol ct, boolean includeEnclosingClasses) { ArrayList<ClassSymbol> interfaces = new ArrayList<ClassSymbol>(20); if (!(ct instanceof ClassSymbol)) return interfaces; ClassSymbol c = (ClassSymbol)ct; // FIXME - what if we want the parents of a type variable? List<ClassSymbol> classes = new LinkedList<ClassSymbol>(); Set<ClassSymbol> interfaceSet = new HashSet<ClassSymbol>(); ClassSymbol cc = c; List<ClassSymbol> todo = new LinkedList<ClassSymbol>(); todo.add(c); while (!todo.isEmpty()) { cc = todo.remove(0); if (cc == null) continue; if (cc.owner instanceof ClassSymbol) todo.add((ClassSymbol)cc.owner); // FIXME - can this be an interface? todo.add((ClassSymbol)cc.getSuperclass().tsym); classes.add(0,cc); } for (ClassSymbol ccc: classes) { List<Type> ifs = ccc.getInterfaces(); for (Type ifc : ifs) { ClassSymbol sym = (ClassSymbol)ifc.tsym; if (interfaceSet.add(sym)) interfaces.add(sym); } } // FIXME - the interfaces are not in a good order int i = 0; while (i < interfaces.size()) { ClassSymbol ccc = interfaces.get(i++); List<Type> ifs = ccc.getInterfaces(); for (Type ifc : ifs) { ClassSymbol sym = (ClassSymbol)ifc.tsym; if (interfaceSet.add(sym)) interfaces.add(sym); } } interfaces.addAll(classes); return interfaces; } // Includes self // FIXME - review for order public java.util.List<MethodSymbol> parents(MethodSymbol m) { List<MethodSymbol> methods = new LinkedList<MethodSymbol>(); for (ClassSymbol c: parents((ClassSymbol)m.owner, false)) { for (Symbol mem: c.getEnclosedElements()) { if (mem instanceof MethodSymbol && mem.name.equals(m.name) && (mem ==m || m.overrides(mem, c, Types.instance(context), true))) { methods.add((MethodSymbol)mem); } } } return methods; } /** Creates the location prefix including the colon without any message; * 'pos' is the position in the file given by log().currentSource(). */ public String locationString(int pos) { return locationString(pos, null); } /** Creates the location prefix including the colon without any message; * 'pos' is the position in the file given by source or if source is null, by log.currentSource(). */ public String locationString(int pos, /*@ nullable */ JavaFileObject source) { return locationString(new SimpleDiagnosticPosition(pos), source); } /** Creates the location prefix including the colon without any message; * 'pos' is the position in the file given by source or if source is null, by log.currentSource(). */ public String locationString(DiagnosticPosition pos, /*@ nullable */ JavaFileObject source) { // USE JCDiagnostic.NO_SOURCE ? FIXME JavaFileObject prev = null; if (source != null) prev = log().useSource(source); try { JCDiagnostic diag = JCDiagnostic.Factory.instance(context).note(log().currentSource(), pos, "empty", ""); String msg = diag.noSource().replace("Note: ", ""); return msg; } finally { if (source != null) log().useSource(prev); } } Symbol codeBigintMath = null; Symbol codeSafeMath = null; Symbol codeJavaMath = null; Symbol specBigintMath = null; Symbol specJavaMath = null; Symbol specSafeMath = null; private void initModeSymbols() { if (codeBigintMath != null) return; specSafeMath = ClassReader.instance(context).enterClass(Names.instance(context).fromString(Strings.jmlAnnotationPackage + ".SpecSafeMath")); specJavaMath = ClassReader.instance(context).enterClass(Names.instance(context).fromString(Strings.jmlAnnotationPackage + ".SpecJavaMath")); specBigintMath = ClassReader.instance(context).enterClass(Names.instance(context).fromString(Strings.jmlAnnotationPackage + ".SpecBigintMath")); codeSafeMath = ClassReader.instance(context).enterClass(Names.instance(context).fromString(Strings.jmlAnnotationPackage + ".CodeSafeMath")); codeJavaMath = ClassReader.instance(context).enterClass(Names.instance(context).fromString(Strings.jmlAnnotationPackage + ".CodeJavaMath")); codeBigintMath = ClassReader.instance(context).enterClass(Names.instance(context).fromString(Strings.jmlAnnotationPackage + ".CodeBigintMath")); } public boolean isTypeChecked(ClassSymbol sym) { ClassSymbol c = sym; if (c == null) return false; return ((c.flags_field & UNATTRIBUTED) == 0); } public IArithmeticMode defaultArithmeticMode(Symbol sym, boolean jml) { initModeSymbols(); if (!jml) { if (sym.attribute(codeBigintMath) != null) return org.jmlspecs.openjml.ext.Arithmetic.Math.instance(context); if (sym.attribute(codeSafeMath) != null) return org.jmlspecs.openjml.ext.Arithmetic.Safe.instance(context); if (sym.attribute(codeJavaMath) != null) return org.jmlspecs.openjml.ext.Arithmetic.Java.instance(context); sym = sym.owner; if (!(sym instanceof Symbol.PackageSymbol)) return defaultArithmeticMode(sym,jml); return org.jmlspecs.openjml.ext.Arithmetic.Safe.instance(context); } else { if (sym.attribute(specBigintMath) != null) return org.jmlspecs.openjml.ext.Arithmetic.Math.instance(context); if (sym.attribute(specSafeMath) != null) return org.jmlspecs.openjml.ext.Arithmetic.Safe.instance(context); if (sym.attribute(specJavaMath) != null) return org.jmlspecs.openjml.ext.Arithmetic.Java.instance(context); sym = sym.owner; if (!(sym instanceof Symbol.PackageSymbol)) return defaultArithmeticMode(sym,jml); return org.jmlspecs.openjml.ext.Arithmetic.Math.instance(context); } } /** Returns true if a declaration with the given flags is visible in the * 'base' class when declared in the 'parent' class. This is standard * Java visibility. */ public boolean visible(Symbol base, Symbol parent, long flags) { if (base == parent) return true; // Everything is visible in its own class if (base.isEnclosedBy((ClassSymbol)parent)) return true; // Everything is visible to inner classes if ((flags & Flags.PUBLIC) != 0) return true; // public things are always visible if (parent.isInterface()) return true; // everything in an interface is public and hence visible if ((flags & Flags.PRIVATE) != 0) return false; // Private things are never visible outside their own class if (base.packge().equals(parent.packge())) return true; // Protected and default things are visible if in the same package return (flags & Flags.PROTECTED) != 0 && base.isSubClass(parent, Types.instance(context)); // Protected things are visible in subclasses } /** Returns true if a declaration in the 'parent' class with the given flags * is jml-visible in a method in the * 'base' class and the method has the given 'methodFlags'. * This check takes into account spec-public and spec-protected declarations * and also JML-specific visibility rules. The first argument can be null * if checking the visibility, say of a clause. */ public boolean jmlvisible(/*@ nullable */ Symbol s, Symbol base, Symbol parent, long flags, long methodFlags) { if (!visible(base,parent,flags)) return false; // FIXME - this looks backwards - if it is Java-visible, then it ought to be JML visible??? // In JML the clause must be at least as visible to clients as the method flags &= Flags.AccessFlags; methodFlags &= Flags.AccessFlags; //boolean isPublic = null != JmlSpecs.instance(context).fieldSpecHasAnnotation((Symbol.VarSymbol)s,JmlTokenKind.SPEC_PUBLIC); // If target is public, then it is jml-visible, since everyone can see it if (flags == Flags.PUBLIC) return true; if (s != null && s.attribute(JmlAttr.instance(context).tokenToAnnotationSymbol.get(JmlTokenKind.SPEC_PUBLIC)) != null) return true; // Otherwise a public method sees nothing if (methodFlags == Flags.PUBLIC) return false; // If the method itself is private, then anyone who can see the method // can also see the target if (methodFlags == Flags.PRIVATE) return true; // By now the method is either protected or package // The target is either protected or package or private // FIXME - comment more if (flags == Flags.PRIVATE && s != null && s.attribute(JmlAttr.instance(context).tokenToAnnotationSymbol.get(JmlTokenKind.SPEC_PROTECTED)) == null ) return false; if (flags == 0) return methodFlags == 0; // By now flags must be PROTECTED // and methodFlags is PROTECTED or package // Must be in the same package return (base.owner == parent.owner); // The rule is that the clause has to be visible wherever the method is visible // If a protected method can see a protected clause by Java rules, then either // the clause is in the same package OR in the same or a super class. // But if both the clause and method are to be visible to a client, then // the clause has to be in the same package AND in the same or a super class } public List<Symbol.VarSymbol> listJmlVisibleFields(TypeSymbol base, long baseVisibility, boolean forStatic) { List<Symbol.VarSymbol> list = new LinkedList<Symbol.VarSymbol>(); for (TypeSymbol csym: parents(base, true)) { for (Symbol s: csym.members().getElements()) { if (s.kind != Kinds.VAR) continue; if (isJMLStatic(s) != forStatic) continue; if (!jmlvisible(s,base,csym,s.flags()&Flags.AccessFlags,baseVisibility)) continue; // FIXME - jml access flags? on base and on target? list.add((Symbol.VarSymbol)s); } } return list; } public List<Symbol.VarSymbol> listAllFields(TypeSymbol base, boolean forStatic) { List<Symbol.VarSymbol> list = new LinkedList<Symbol.VarSymbol>(); for (TypeSymbol csym: parents(base, true)) { for (Symbol s: csym.members().getElements()) { if (s.kind != Kinds.VAR) continue; if (!isJMLStatic(s) && forStatic) continue; list.add((Symbol.VarSymbol)s); } } return list; } /** Returns the owning class declaration of a method declaration */ public JmlClassDecl getOwner(JmlMethodDecl methodDecl) { return (JmlClassDecl)JmlEnter.instance(context).getEnv((ClassSymbol)methodDecl.sym.owner).tree; } /** Returns a method signature with a fully-qualified method name */ public String qualifiedMethodSig(MethodSymbol sym) { return classQualifiedName(sym.owner) + "." + sym; } /** Returns a fully-qualified name for a method symbol, without the signature */ // FIXME - may include <init> public String qualifiedName(Symbol sym) { return classQualifiedName(sym.owner) + "." + sym.name; } /** Returns a fully-qualified name for a class symbol, with adjustments for anonymous types */ public String classQualifiedName(Symbol sym) { String s = sym.getQualifiedName().toString(); if (s.isEmpty()) { s = sym.flatName().toString().replace('$','.'); } return s; } /** Returns an unqualified name, but with the class name instead of init */ public String methodName(MethodSymbol sym) { String s = sym.toString(); int k = s.indexOf("("); s = k >= 0 ? s.substring(0,k) : s; if (s.isEmpty()) { // Anonymous constructor s = sym.owner.toString(); if (s.startsWith("$anonymous$")) { s = s.substring("$anonymous$".length()); } } return s; } /** Removes an element from a ListBuffer, if there is one, and return the new list */ public static <T> ListBuffer<T> remove(ListBuffer<T> list, T element) { // Remove the duplicate if it is in newdefs ListBuffer<T> n = new ListBuffer<>(); for (T ttt: list) { if (ttt != element) n.add(ttt); } return n; } /** Removes an element from a ListBuffer, if there is one, and return the new list */ public static <T> ListBuffer<T> remove(ListBuffer<T> list, ListBuffer<T> elements) { // Remove the duplicate if it is in newdefs ListBuffer<T> n = new ListBuffer<>(); x: for (T ttt: list) { for (T rem: elements) { if (ttt == rem) continue x; } n.add(ttt); } return n; } /** Removes an element from a List, if there is one, and return the new list */ public static <T> com.sun.tools.javac.util.List<T> remove(com.sun.tools.javac.util.List<T> list, T element) { // Remove the duplicate if it is in newdefs ListBuffer<T> n = new ListBuffer<>(); for (T ttt: list) { if (ttt != element) n.add(ttt); } return n.toList(); } public/* @ nullable */JCAnnotation tokenToAnnotationAST(JmlTokenKind jt, int position, int endpos) { Class<?> c = jt.annotationType; if (c == null) return null; JmlTree.Maker F = JmlTree.Maker.instance(context); Names names = Names.instance(context); JCExpression t = nametree(position, "org.jmlspecs.annotation"); t = (F.at(position).Select(t, names.fromString(c.getSimpleName()))); JCAnnotation ann = (F.at(position).Annotation(t, com.sun.tools.javac.util.List.<JCExpression> nil())); ((JmlTree.JmlAnnotation)ann).sourcefile = log().currentSourceFile(); return ann; } public JCExpression nametree(int position, String s) { String[] nms = s.split("\\."); JmlTree.Maker F = JmlTree.Maker.instance(context); Names names = Names.instance(context); JCExpression tree = F.at(position).Ident(nms[0]); for (int i = 1; i<nms.length; i++) { tree = F.at(position).Select(tree, names.fromString(nms[i])); } return tree; } /** Instances of this class are used to abort operations that are not * implemented. * @author David R. Cok */ public static class JmlNotImplementedException extends RuntimeException { private static final long serialVersionUID = 1L; public DiagnosticPosition pos; public JmlNotImplementedException(DiagnosticPosition pos, String location) { super(location); this.pos = pos; } } /** This is a predicate that can be used in a debugging condition */ public static boolean print(String s) { if (s != null) System.out.println(s); return true; } /** This is solely used to easily insert conditional breakpoints for debugging. */ public static void stop() { return; } public static class DoubleMap<T1,T2,TR> { Map<T1, Map<T2,TR>> map = new HashMap<T1, Map<T2,TR>>(); public TR get(T1 t1, T2 t2) { Map<T2,TR> m = map.get(t1); if (m == null) return null; return m.get(t2); } public void put(T1 t1, T2 t2, TR tr) { Map<T2,TR> m = map.get(t1); if (m == null) { m = new HashMap<T2,TR>(); map.put(t1, m); } m.put(t2, tr); } public void clear() { map.clear(); } } /** Reports progress to the registered IProgressListener; also checks if * the progress listener has received a user-cancellation, in which case * this method throws an exception to terminate processing * @param ticks amount of work to report * @param level level of the message (lower levels are more likely to be printed) * @param message the progress message */ public void progress(int ticks, int level, String message) { org.jmlspecs.openjml.Main.IProgressListener pr = context.get(org.jmlspecs.openjml.Main.IProgressListener.class); boolean cancelled = pr == null ? false : pr.report(ticks,level,message); if (cancelled) { throw new PropagatedException(new Main.JmlCanceledException("ESC operation cancelled")); } } /** * Checks to see if two JavaFileObjects point to the same location. * In some cases, it's a bad idea to use JavaFileObject.equals, because copying a JavaFileObject can change the path name, even if they point to the same canonical path. * This function exists for where JavaFileObject.equals may fail. */ public static boolean ifSourcesEqual(JavaFileObject jfo1, JavaFileObject jfo2) { try { File file1 = new File(jfo1.getName()); File file2 = new File(jfo2.getName()); return file1.getCanonicalPath().equals(file2.getCanonicalPath()); } catch (IOException e) {} return jfo1.equals(jfo2); } public void warning(JavaFileObject source, int pos, String key, Object ... args) { Log log = log(); JavaFileObject prev = log.useSource(source); try { log.warning(pos, key, args); } finally { log.useSource(prev); } } public void error(JavaFileObject source, int pos, String key, Object ... args) { Log log = log(); JavaFileObject prev = log.useSource(source); try { log.error(pos, key, args); } finally { log.useSource(prev); } } public void error(JavaFileObject source, DiagnosticPosition pos, String key, Object ... args) { Log log = log(); JavaFileObject prev = log.useSource(source); try { log.error(pos, key, args); } finally { log.useSource(prev); } } public void errorAndAssociatedDeclaration(JavaFileObject source, int pos, JavaFileObject assoc, int assocpos, String key, Object ... args) { Log log = log(); JavaFileObject prev = log.useSource(source); try { log.error(pos, key, args); } finally { log.useSource(prev); } prev = log.useSource(assoc); try { log.error(assocpos, "jml.associated.decl.cf", locationString(pos, source)); } finally { log.useSource(prev); } } public void errorAndAssociatedDeclaration(JavaFileObject source, DiagnosticPosition pos, JavaFileObject assoc, DiagnosticPosition assocpos, String key, Object ... args) { Log log = log(); JavaFileObject prev = log.useSource(source); try { log.error(pos, key, args); } finally { log.useSource(prev); } prev = log.useSource(assoc); try { log.error(assocpos, "jml.associated.decl.cf", locationString(pos, source)); } finally { log.useSource(prev); } } }