/* * This file is part of the OpenJML project. * Author: David R. Cok * Reviewed: 2016-12-12 */ package com.sun.tools.javac.comp; import org.jmlspecs.openjml.JmlCompiler; import org.jmlspecs.openjml.JmlSpecs; import org.jmlspecs.openjml.JmlTokenKind; import org.jmlspecs.openjml.Utils; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.code.Scope; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.Log.WriterKind; import com.sun.tools.javac.util.Name; /** * This class extends Resolve in order to implement lookup of JML names. In the * current design of OpenJML, we use just one symbol table for Java and JML * names. This is no restriction because JML declarations have to be legal Java * declarations and have to create a legal Java compilation unit if they were * actual Java declarations. However JML names must only be visible from within * JML annotations. To implement that, each JML declaration has its JMLBIT set * in the modifiers bit-vector and the field JmlResolve.allowJML determines * whether or not JML-flagged declarations are returned as the result of a name * lookup: if allowJML is true, any declaration is returned; if allowJML is * false, JML-flagged declarations are not returned. * * @author David Cok */ public class JmlResolve extends Resolve { /** The compilation context in which to do lookup. */ public Context context; /** This flag determines whether or not JML-flagged declarations are * returned as the result of a name lookup. It may be set by clients of * this class through the setJML method. Note that contexts are nested, * so you should remember and * restore the value of this flag when you are setting it. */ protected boolean allowJML = false; /** Cached value of a org.jmlspecs.openjml.Utils object */ final protected Utils utils; /** Cached value of JmlCompiler, used for loading classes */ protected JmlCompiler jmlcompiler; /** Cached value of JmlAttr, used for resolving annotations */ final protected JmlAttr attr; // /** A constant symbol that represents the <# operation on locks; it is // * initialized in the constructor. // */ // final public OperatorSymbol lockLT; // // /** A constant symbol that represents the <#= operation on locks; it is // * initialized in the constructor. // */ // final public OperatorSymbol lockLE; // // /** A private cache for the java.lang.Integer type */ // final private Type integerType; /** Returns the unique instance of this class for the given compilation context * @param context the compilation context whose instance of Resolve is desired * @return the unique instance of JmlResolve */ public static JmlResolve instance(Context context) { JmlResolve instance = (JmlResolve)context.get(resolveKey); if (instance == null) instance = new JmlResolve(context); return instance; } /** Registers a factory for JmlResolve against the attrKey. Once an instance * is created it registers itself, so there is a unique instance per context. * @param context the context in which to register the instance */ public static void preRegister(final Context context) { context.put(Resolve.resolveKey, new Context.Factory<Resolve>() { public Resolve make(Context context) { return new JmlResolve(context); } }); } /** Creates an instance of this class for the given context; clients should * use the instance() method rather than calling this constructor directly. * @param context the compilation context in which to construct this instance */ //@ ensures context != null; //@ ensures lockLT != null; //@ ensures lockLE != null; //@ ensures integerType != null; protected JmlResolve(Context context) { super(context); this.context = context; this.utils = Utils.instance(context); this.attr = JmlAttr.instance(context); // Symtab syms = Symtab.instance(context); // integerType = reader.enterClass(names.fromString("java.lang.Integer")).type; // lockLT = new OperatorSymbol( // names.fromString("<#"), // new MethodType(List.of(syms.objectType, syms.objectType), syms.booleanType, // List.<Type>nil(), syms.methodClass), // 1000, // syms.predefClass); // lockLE = new OperatorSymbol( // names.fromString("<#="), // new MethodType(List.of(syms.objectType, syms.objectType), syms.booleanType, // List.<Type>nil(), syms.methodClass), // 1001, // syms.predefClass); // // We could enter these operators into the symbol table // // with the code below. That works nicely for operator // // lookup, but it is hard to control that they are looked // // up only when JML is active. Also, these operators take // // Objects as arguments. With type boxing, that means that // // they can take any argument. However, boxed arguments // // will not have locks anyway, so with direct control in // // resolveBinaryOperator we avoid this. //// syms.predefClass.members().enter(lockLT); //// syms.predefClass.members().enter(lockLE); } /** This method is used to set the value of the allowJML flag. It returns * the previous value. * @param allowJML the new value * @return the old value */ public boolean setAllowJML(boolean allowJML) { boolean b = this.allowJML; this.allowJML = allowJML; return b; } /** Returns the value of the allowJML flag. */ public boolean allowJML() { return this.allowJML; } // /** This method overrides the super class method in order to check for // * resolutions against JML operators: in particular, the < and <= operators // * on Objects that used to implement lock ordering comparisons. The method // * returns an error symbol (Symtab.instance(context).errSymbol) if there // * is no matching operator. // * // * Once < and <= are no longer used for lock ordering, this method can be // * removed. // */ // @Override // public Symbol resolveBinaryOperator(DiagnosticPosition pos, // JCTree.Tag optag, // Env<AttrContext> env, // Type left, // Type right) { // // TODO: Eventually disallow using < and <= for lock operations // // Then this whole method can be removed // // FIXME - should compare against Float or Double or Character or Short or Byte or Long as well? // if (allowJML && !left.isPrimitive() && !right.isPrimitive()) { // if (!types.isSameType(left,integerType) && !types.isSameType(right,integerType)) { // if (optag == JCTree.Tag.LT) { // log.warning(pos,"lock.ops"); // return lockLT; // } // if (optag == JCTree.Tag.LE) { // log.warning(pos,"lock.ops"); // return lockLE; // } // } // } // return super.resolveBinaryOperator(pos, optag, env, left, right); // } /** This check is inserted in the superclass methods: JML symbols are mixed in * with the Java symbols in the various scopes; we do this check to forbid using * JML symbols when we are not in a JML context. */ @Override protected boolean symbolOK(Scope.Entry e) { return allowJML || !utils.isJML(e.sym.flags_field); } // A hook method added into Resolve.findMethod to avoid replication in the // parent class. /** TODO: Not sure exactly what this controls in the superclass */ @Override protected boolean abstractOK(ClassSymbol c) { return allowJML || super.abstractOK(c); } /** This method overrides the superclass method in order to load spec files * when a class is loaded. If the superclass loads a method from source, then * the specs are parsed at the same time that the source file is parsed. * However, if the specs are loaded from binary, then the code here is needed * to obtain and parse the specification files as well. * * @param env the environment within which a class will be loaded (e.g. package or containing class) * @param name the qualified name of the class to load * @return the unique symbol corresponding to this class */ @Override public Symbol loadClass(Env<AttrContext> env, Name name) { if (utils.jmlverbose >= Utils.JMLDEBUG) log.getWriter(WriterKind.NOTICE).println("BINARY LOADING STARTING " + name ); Symbol s = super.loadClass(env, name); // Here s can be a type or a package or not exist // s may not exist because it is being tested whether such a type exists // (rather than a package) and is a legitimate workflow in this // architecture. Hence no warning or error is given. // This happens for example in the resolution of org.jmlspecs.annotation if (!s.exists()) { return s; } if (!(s instanceof ClassSymbol)) { if (utils.jmlverbose >= Utils.JMLDEBUG) log.getWriter(WriterKind.NOTICE).println(" LOADING IS NOT A CLASS " + name ); return s; // loadClass can be called for a package } JmlMemberEnter memberEnter = (JmlMemberEnter)JmlMemberEnter.instance(context); boolean completion = memberEnter.completionEnabled; memberEnter.completionEnabled= true; try { JmlSpecs specs = JmlSpecs.instance(context); JmlSpecs.TypeSpecs tsp = specs.get((ClassSymbol)s); if (tsp == null) { //if (true || utils.jmldebug) log.noticeWriter.println(" LOADING SPECS FOR (BINARY) CLASS " + name); // Cannot set jmlcompiler in the constructor because we get a circular initialization problem. if (jmlcompiler == null) jmlcompiler = ((JmlCompiler)JmlCompiler.instance(context)); jmlcompiler.loadSpecsForBinary(env,(ClassSymbol)s); //if (true || utils.jmldebug) log.noticeWriter.println(" LOADED BINARY " + name + " HAS SCOPE WITH SPECS " + s.members()); // if (specs.get((ClassSymbol)s) == null) // log.getWriter(WriterKind.NOTICE).println("(Internal error) POSTCONDITION PROBLEM - no typeSpecs stored for " + s); } else { //log.noticeWriter.println(" LOADED CLASS " + name + " ALREADY HAS SPECS LOADED"); } return s; } finally { memberEnter.completionEnabled = completion; } } /** A cache of the symbol for the spec_public annotation class, created on * demand. */ private ClassSymbol specPublicSym = null; /** A cache of the symbol for the spec_protected annotation class, created on * demand. */ private ClassSymbol specProtectedSym = null; /** This class is overridden in order to allow access according to the rules * for spec_public and spec_protected. */ @Override public boolean isAccessible(Env<AttrContext> env, Type site, Symbol sym) { if (super.isAccessible(env,site,sym)) return true; if (!allowJML) return false; // If not accessible and we are in JML, see if spec_public or spec_protected helps JCTree.JCModifiers mods=null; if (sym instanceof Symbol.VarSymbol) { JmlSpecs.FieldSpecs f = JmlSpecs.instance(context).getSpecs((Symbol.VarSymbol)sym); if (f != null) mods = f.mods; } if (specPublicSym == null) { specPublicSym = attr.tokenToAnnotationSymbol.get(JmlTokenKind.SPEC_PUBLIC); } if (specProtectedSym == null) { specProtectedSym = attr.tokenToAnnotationSymbol.get(JmlTokenKind.SPEC_PROTECTED); } boolean isSpecPublic = utils.findMod(mods,specPublicSym)!=null; if (isSpecPublic) { long saved = sym.flags(); sym.flags_field |= Flags.PUBLIC; boolean b = super.isAccessible(env,site,sym); sym.flags_field = saved; return b; } if ((sym.flags() & Flags.PROTECTED) != 0) return false; boolean isSpecProtected = utils.findMod(mods,specProtectedSym)!=null; if (isSpecProtected) { long saved = sym.flags_field; sym.flags_field |= Flags.PROTECTED; boolean b = super.isAccessible(env,site,sym); sym.flags_field = saved; return b; } return false; } /** This is declared in order to provide public visibility */ public Symbol resolveUnaryOperator(DiagnosticPosition pos, JCTree.Tag optag, Env<AttrContext> env, Type arg) { return super.resolveUnaryOperator(pos,optag,env,arg); } // /** Returns the predefined operator with the given operator and type */ // public Symbol resolveUnaryOperator(DiagnosticPosition pos, JCTree.Tag optag, Type arg) { // Scope.Entry e = syms.predefClass.members().lookup(treeinfo.operatorName(optag)); // return e.sym; // } // /** Finds the constructor in the given environment that matches the given type arguments // * and arguments. // */ // public Symbol resolveConstructor(DiagnosticPosition pos, Env<AttrContext> env, // Type site, List<Type> argtypes, // List<Type> typeargtypes) { // return super.resolveConstructor(pos,env,site,argtypes,typeargtypes); // } boolean silentErrors = false; boolean errorOccurred = false; protected void logResolveError(ResolveError error, DiagnosticPosition pos, Symbol location, Type site, Name name, List<Type> argtypes, List<Type> typeargtypes) { if (!silentErrors) super.logResolveError(error,pos,location,site,name,argtypes,typeargtypes); } // Overridden purely to invoke JmlLookupFilter; otherwise a copy of Resolve.findMethodInScope @Override protected Symbol findMethodInScope(Env<AttrContext> env, Type site, Name name, List<Type> argtypes, List<Type> typeargtypes, Scope sc, Symbol bestSoFar, boolean allowBoxing, boolean useVarargs, boolean operator, boolean abstractok) { for (Symbol s : sc.getElementsByName(name, new JmlLookupFilter(abstractok))) { bestSoFar = selectBest(env, site, argtypes, typeargtypes, s, bestSoFar, allowBoxing, useVarargs, operator); } return bestSoFar; } /** This class extends Resolve.LookupFilter to disallow using variables declared in * JML within Java code */ class JmlLookupFilter extends LookupFilter { boolean abstractOk; JmlLookupFilter(boolean abstractOk) { super(abstractOk); } public boolean accepts(Symbol s) { if (!super.accepts(s)) return false; if (utils.isJML(s.flags()) && !allowJML()) return false; return true; } }; }