/* * This file is part of the OpenJML project. * Author: David R. Cok */ package com.sun.tools.javac.comp; import java.util.Collection; import javax.tools.JavaFileObject; import org.jmlspecs.annotation.NonNull; import org.jmlspecs.openjml.JmlSpecs; import org.jmlspecs.openjml.JmlTokenKind; import org.jmlspecs.openjml.JmlTree; import org.jmlspecs.openjml.JmlTree.JmlClassDecl; import org.jmlspecs.openjml.JmlTree.JmlCompilationUnit; import org.jmlspecs.openjml.Main; import org.jmlspecs.openjml.Strings; import org.jmlspecs.openjml.Utils; 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.Symbol.CompletionFailure; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.Type.ClassType; import com.sun.tools.javac.code.Type.TypeVar; 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.JCClassDecl; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import com.sun.tools.javac.tree.TreeInfo; 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.ListBuffer; import com.sun.tools.javac.util.Name; /** * This class extends Enter, which has the job of creating symbols for all the * types mentioned in a set of parse trees. JmlEnter adds to that functionality * to create symbols for all JML types (i.e., model classes) that are present in * the parse trees. In addition it adds to the source class file any model classes * that need to be compiled and it links each class declaration to its * specifications (another class declaration), or to itself. * <P> * JmlEnter expects that a compilation unit knows its specification files * (via its specsCompilationUnit field). It * walks those specification files, matching classes in the specification file * to the corresponding classes in the Java file, making links from the Java * classes to their specifications. JmlEnter also expects that the parse * tree contains JmlClassDecl nodes instead of JCClassDecl nodes, even where * no additional specs are present. * <P> * Per the current version of JML, specifications for a .java file are taken from * just one file (previously, the specifications were a combination of specifications * from a sequence of specification files). The one file may be the .java file containing * the Java definition of the class or it may be a different (e.g., .jml) file. The file used * is the one attached to the JmlCompilationUnit.specsCompilationUnit field (which may be * but is not necessarily the AST for the .java file itself). * <P> * Note that classes referenced in the set of compilation unit trees passed to Enter.main * are not processed until later (during MemberEnter or Attribution). If those classes * exist as .java files they will be parsed and their specifications attached as usual. * If the referenced classes are only binary, the specifications still need to be obtained * and attached to the class symbol. * <P> * The process of entering a CU does these things: * <UL> * <LI> packages are completed by entering all the classes in the package * <LI> classes: a class symbol is defined; a completer is attached to the symbol * <LI> type parameters: recorded in the Env associated with the class * <LI> initiates the MemberEnter processing, which adds the members of a class * to its Env (its scope); this can have the side-effect of uncovering more * classes that need to be loaded (by either parsing or finding the binary class) * and entered. * </UL> * Also typeEnvs is a map that gives an Env<AttrContext> object for each class, * to be used when attributing types and resolving references within the class. * The enter process creates and stores this Env. JmlEnter does the same for * model classes and for the specifications corresponding to binary classes. * * @author David Cok */ /* FIXME - REVIEW THE FOLLOWING * Relationships that need to be set up (for binary classes as well) * class symbol: ClassSymbol csym * class environment : Env<AttrContext> classenv * class specs: TypeSpecs cspecs * class declaration: JmlClassDecl cdecl * * classenv = getEnv(csym) ; classenv created by classEnv(cdecl, topLevelEnv) * csym = cdecl.sym * cspecs = specs.get(csym) * * cdecl.typeSpecsCombined = cspecs (only for Java declaration) * cdecl.typeSpecs = specifications from this cdecl only, not combined [Set by filterTypeBodyDeclarations() ] * cdecl.toplevel = not reliably set ??? FIXME * cdecl.sourcefile = toplevel.sourcefile [ Set by JmlParser ] * cdecl.specsDecls = list of JmlClassDecls, including cdecl if class is binary [ Set in JmlEnter, during matching of specs to class symbols ] * cdecl.sym = csym [For Java files, assigned when class dfinition is entered; * for binary files, assigned in JmlEnter during matching of specs to class symbols ] * * cspecs.refiningSpecDecls = list of specifications declarations * cspecs.csymbol = csym * cspecs.file = file for Java declaration; if binary, file for most refined specs file (can be used for modifiers) * cspecs.decl = decl for Java declaration; if binary, decl for most refined specs file * cspecs.modifiers = accumulated modifiers, so from most refined specs file, else from symbol * [ JmlParser sets up the individual cdecl.sourcefile, cdecl.typeSpecs field * and the cdecl.typeSpecs.modifiers, file, decl fields ] * * csym.sourcefile = file for Java declaration; if binary, file for most refined specs file (or null?) */ public class JmlEnter extends Enter { /** This registers a factory so that we do not have to prematurely * create an instance of Enter, but the factory only produces a singleton * instance per context. * @param context the context in which to register */ public static void preRegister(final Context context) { context.put(enterKey, new Context.Factory<Enter>() { Enter instance = null; public Enter make(Context context) { return instance != null ? instance : (instance = new JmlEnter(context)); } }); } /** The context in which this instance was created. */ @NonNull final protected Context context; /** A cached value of the specs tool for this compilation context */ @NonNull final protected JmlSpecs specs; /** A cached value of the Utils tool */ @NonNull final protected Utils utils; /** Creates an instance of the JmlEnter tool in the given context; note that * any options must be already set in Options prior to creating the tool. * @param context the compilation context to use for this tool */ //@ assignable this.*; public JmlEnter(Context context) { super(context); // automatically registers the new object this.context = context; this.utils = Utils.instance(context); this.specs = JmlSpecs.instance(context); } /** The env (scope) to be used within specifications corresponding to the env for Java, as passed internally * from visitTopLevel to classEnter. */ private Env<AttrContext> specTopEnv; /** This method is called when a JmlCompilationUnit is in the list of things to 'enter'. * It visits the designated compilation unit; first it matches * class declarations in the specification files to class declarations in * Java; then it calls the super class visitTopLevel method to initiate * walking the tree; overriding methods in JmlEnter will be called when visiting * class declarations, so that a class can register in the symbol table * any model classes that are declared within it. As classes are visited, * the specs for that class are extracted from the specification sequence * and attached to the class. We also take care of secondary top-level * class declarations and top-level model declarations. */ public void visitTopLevel(JCCompilationUnit tree) { // Already set: toplevel, sourcefile, specsCompilationUnit, // Need to set: topLevelEnv if (!(tree instanceof JmlCompilationUnit)) { log.warning("jml.internal.notsobad","Encountered an unexpected JCCompilationUnit instead of a JmlCompilationUnit in JmlEnter.visitTopeLevel"); super.visitTopLevel(tree); return; } JmlCompilationUnit jmltree = (JmlCompilationUnit)tree; if (utils.jmlverbose >= Utils.JMLVERBOSE) context.get(Main.IProgressListener.class).report(0,2,"entering " + jmltree.sourcefile.getName()); // FIXME - a problem here is that the specs and the model fields/classes/methods will be attributed using the set of imports from the Java source file JmlCompilationUnit specscu; if (jmltree.specsCompilationUnit == null) { // If this is the case we have called visitTopLevel on a specs file specTopEnv = null; specscu = jmltree; } else { specscu = jmltree.specsCompilationUnit; } String owner; { // This if-else statement copied from Enter if (specscu.pid != null) { specscu.packge = reader.enterPackage(TreeInfo.fullName(specscu.pid)); owner = specscu.packge.flatName().toString() + "."; // if (specscu.packageAnnotations.nonEmpty() // || pkginfoOpt == PkgInfo.ALWAYS // || specscu.docComments != null) { // if (specscu.packageAnnotations.nonEmpty()){ // log.error(specscu.packageAnnotations.head.pos(), // "pkg.annotations.sb.in.package-info.java"); // } // } } else { specscu.packge = syms.unnamedPackage; owner = ""; } specscu.packge.complete(); // Find all classes in package. specTopEnv = topLevelEnv(specscu); specscu.topLevelEnv = specTopEnv; } // Match specifications to the corresponding Java class, for each (toplevel) class; if (jmltree.specsCompilationUnit != null && jmltree.mode != JmlCompilationUnit.SPEC_FOR_BINARY) { tree.defs = matchClasses(tree.defs, jmltree.specsCompilationUnit.defs, tree.sourcefile.toString()); } else { specscu.defs = matchClassesForBinary(specTopEnv, owner, specscu.defs, null, tree.sourcefile.toString()); } // Then do all the regular Java registering of packages and types super.visitTopLevel(jmltree); // Checking that the specs and the java source declare the same package if (jmltree.specsCompilationUnit != null && jmltree.specsCompilationUnit != jmltree) { if (specscu.packge != jmltree.packge) { // FIXME - use jml.mismatched.package utils.error(specscu.sourcefile,specscu.getPackageName().pos,"jml.internal","The package in " + specscu.sourcefile.getName() + " is " + (specscu.pid == null ? "<default>" : specscu.pid.toString() + ", which does not match the .java file: " + jmltree.packge.toString())); String s = utils.locationString(specscu.getPackageName().pos, specscu.sourcefile); utils.error(jmltree.getSourceFile(), jmltree.getPackageName().pos,"jml.associated.decl.cf",s); } // specscu.packge = jmltree.packge; } if (utils.jmlverbose >= Utils.JMLVERBOSE) context.get(Main.IProgressListener.class).report(0,2," completed entering " + jmltree.sourcefile.getName()); } /** This routine matches class declarations in the specifications ('specsDefs' list) with Java declarations ('defs' list). * Note that these may be top-level declarations in corresponding files; they may also be lists of nested declaration * from corresponding nested locations. The composite list of declarations (to replace 'defs') is returned. Any duplicate * orphan declarations are warned about. The returned 'defs' will omit duplicates and include any classes in JML specifications; * thus this revised 'defs' list is the one to be submitted to 'classEnter' */ public List<JCTree> matchClasses(List<JCTree> defs, List<? extends JCTree> specsDefs, String javasource) { ListBuffer<JCTree> newdefs = new ListBuffer<JCTree>(); if (defs != specsDefs) { for (JCTree tt: defs) { // Iterate over the Java classes if (tt instanceof JmlTree.IInJML) { if (((JmlTree.IInJML)tt).isJML()) continue; } newdefs.add(tt); } } else { newdefs.addAll(defs); } for (JCTree specDecl: specsDefs) { // Iterate over the classes in the specification, to find the matching java declaration if (!(specDecl instanceof JmlClassDecl)) continue; JmlClassDecl specClassDecl = (JmlClassDecl)specDecl; boolean isSpecsJML = utils.isJML(specClassDecl.mods); // The declaration 'specClassDecl' is in a specification file. // We need to find the Java declaration that it matches // There is supposed to be one, and there should only be one declaration in the specsDefs list // that matches a particular java declaration. // A Java declaration need not have a match Name name = specClassDecl.name; JmlClassDecl matched = null; for (JCTree tt: newdefs) { // Iterate over the list of Java declarations if (!(tt instanceof JmlClassDecl)) continue; JmlClassDecl javaDecl = (JmlClassDecl)tt; boolean isJML = utils.isJML(javaDecl.mods); if (name.equals(javaDecl.name)) { matched = javaDecl; if (javaDecl.specsDecl == null) { // No previous match, so far if (!isJML && isSpecsJML) { // A specification declaration matches a java declaration, // but the specification declaration is in a JML annotation - error - but use it as a match anyway utils.error(specClassDecl.source(), specClassDecl.pos, "jml.duplicate.model", specClassDecl.name,javasource); String s = utils.locationString(specClassDecl.pos, specClassDecl.source()); utils.error(javaDecl.source(), javaDecl.pos,"jml.associated.decl.cf",s); javaDecl.specsDecl = specClassDecl; // Attach the specification to the matching Java AST } else if (isJML && !isSpecsJML) { // A specification declaration matches a java declaration, // but the declaration in the Java file is in a JML annotation, and the specification declaration is not // Since if the specs declaration list is different than the Java declaration list, // any such JML annotated declarations are removed from the Java declaration list, // this must be a case of a file containing two declarations with the same name, // one in a JML annotation and one not // Issue and error and omit the JML declaration newdefs = Utils.remove(newdefs, javaDecl); if (defs != specsDefs) { utils.error(javaDecl.source(), javaDecl.pos, "jml.internal", "Unexpected JML declaration in the Java file"); } else { utils.error(javaDecl.source(), javaDecl.pos,"jml.duplicate.jml.class.decl", javaDecl.name); utils.error(specClassDecl.source(), specClassDecl.pos,"jml.associated.decl.cf", utils.locationString(javaDecl.pos, javaDecl.source())); } } else if (isJML && isSpecsJML) { if (javaDecl != specClassDecl) { // There are two declarations, both in JML annotations, with the same name utils.error(javaDecl.source(), javaDecl.pos,"jml.duplicate.jml.class.decl", javaDecl.name); utils.error(specClassDecl.source(), specClassDecl.pos,"jml.associated.decl.cf", utils.locationString(javaDecl.pos, javaDecl.source())); newdefs = Utils.remove(newdefs,specClassDecl); } else { // The two declarations are the same one - OK javaDecl.specsDecl = specClassDecl; } } else { // else OK match javaDecl.specsDecl = specClassDecl; // Attach the specification to the matching Java AST } } else { // Duplicate - warn and ignore if (!isJML) { // This less informational error message is here just to duplicate previous behavior (and the Java compiler) for Java duplicates utils.error(specClassDecl.source(), specClassDecl.pos,"duplicate.class",specClassDecl.name); } else { utils.error(specClassDecl.source(), specClassDecl.pos,"jml.duplicate.jml.class.decl",specClassDecl.name); utils.error(javaDecl.specsDecl.source(), javaDecl.specsDecl.pos,"jml.associated.decl.cf", utils.locationString(specClassDecl.pos, specClassDecl.source())); } if (!isJML && utils.isJML(javaDecl.specsDecl.mods) && !isSpecsJML) javaDecl.specsDecl = specClassDecl; newdefs = Utils.remove(newdefs, specClassDecl); } break; } } if (matched == null) { // This specification file is not matched, so it is like a // model class declaration. Pretend it is one. // If necessary, add information so that it appears to be declared in a JML annotation and as a model class // In any case, add it to the list of declarations to export if (!utils.isJML(specClassDecl.mods)) { utils.error(specClassDecl.source(), specClassDecl.pos, "jml.orphan.jml.class.decl", specClassDecl.name,javasource); utils.setJML(specClassDecl.mods); JCAnnotation x = utils.tokenToAnnotationAST(JmlTokenKind.MODEL, specClassDecl.pos, specClassDecl.pos); boolean has = false; for (JCAnnotation a: specClassDecl.getModifiers().getAnnotations()) { // FIXME - this is an inadequate comparison if (((JCTree.JCFieldAccess)a.annotationType).name == ((JCTree.JCFieldAccess)x.annotationType).name) { has = true; break; } } if (!has) { specClassDecl.mods.annotations = specClassDecl.mods.getAnnotations().append(x); } else { utils.error(specClassDecl.source(), specClassDecl.pos, "jml.ghost.model.on.java"); } } specClassDecl.specsDecl = specClassDecl; specClassDecl.env = null; newdefs.add(specClassDecl); } } return newdefs.toList(); } /** * * @param ownerenv * @param owner The flat String name of the package or enclosing class (with trailing period) that holds the declarations in specsDefs * @param specsDefs The specification declarations to be associated with classes * @param unmatchedTypesList * @param javasource */ public List<JCTree> matchClassesForBinary(Env<AttrContext> ownerenv, String owner, List<JCTree> specsDefs, Collection<JmlClassDecl> unmatchedTypesList, String javasource) { // The owner env is either the top-level env, if specsDefs is the list of top-level declarations, // or the class env if specsDefs is a nested list of class body declarations. The important aspect for this // procedure are any classes declared ListBuffer<JCTree> newlist = null; for (JCTree specDecl: specsDefs) { // Iterate over the classes in the list if (!(specDecl instanceof JmlClassDecl)) continue; JmlClassDecl specsClass = (JmlClassDecl)specDecl; // The declaration 'specsClass' is in a specification file. // This is matching for a binary class, so there is no source code Java declarations // We need to find the Java declaration that it matches // There must be one, and there should only be one declaration in the specsDefs list // that matches a particular java declaration. // A Java declaration need not have a match // Load the binary if it exists // FIXME - might this load from source? Name flatname = names.fromString( owner + specsClass.name.toString()); ClassSymbol c; try { // The following just returns the symbol if the class is already loaded or known c = reader.loadClass(flatname); } catch (CompletionFailure eee) { c = null; } // FIXME - we are not checking for duplicate declarations in the JML file. if (c != null) { // The binary exists for the given name if (utils.isJML(specsClass.mods)) { // The specs class is in a JML annotation but still matches a java class - error // FIXME _ fix this error message utils.error(specsClass.source(), specsClass.pos, "jml.duplicate.model", specsClass.name,javasource); String s = utils.locationString(specsClass.pos, specsClass.source()); utils.error(specsClass.source(), specsClass.pos,"jml.associated.decl.cf",s); if (newlist == null) { newlist = new ListBuffer<JCTree>(); newlist.addAll(specsClass.defs); } newlist = Utils.remove(newlist, specDecl); } else { // OK - there is a specification matching the binary class specsClass.sym = c; // Env<AttrContext> localenv = classEnv(specsClass, ownerenv); // typeEnvs.put(c,localenv); // specsClass.env = localenv; // specs.combineSpecs(c,null,specsClass); } } if (c == null) { if (!utils.isJML(specsClass.mods)) { // We have a Java declaration in the specs file that does not match an actual Java class. // This is an error. We will ignore the declaration. utils.error(specsClass.source(), specsClass.pos, "jml.unmatched.type", owner + specsClass.name.toString(),javasource); if (newlist == null) { newlist = new ListBuffer<JCTree>(); newlist.addAll(specsDefs); } newlist = Utils.remove(newlist, specDecl); } else { // There is no Java declaration, but we have a JML declaration (presumably a model declaration) // We leave it to be added as a new declaration // FIXME - we need to catch duplicate declarations if (unmatchedTypesList != null) unmatchedTypesList.add(specsClass); } } } return newlist == null ? specsDefs : newlist.toList(); } // FIXME - if we do not need spescTopEnv, then delete this override // Overridden to use the specTopEnv when appropriate @Override public <T extends JCTree> List<Type> classEnter(List<T> trees, Env<AttrContext> env) { ListBuffer<Type> ts = new ListBuffer<Type>(); for (List<T> l = trees; l.nonEmpty(); l = l.tail) { T clazz = l.head; // if (specTopEnv != null && clazz instanceof JmlClassDecl && utils.isJML(((JmlClassDecl)clazz).mods)) { // FIXME - also must not be an inner class // env = specTopEnv; // } Type t = classEnter(clazz, env); if (t != null) { ts.append(t); } } return ts.toList(); } // FIXME - document public boolean binaryEnter(JmlCompilationUnit specs) { Env<AttrContext> prevEnv = env; ((JmlMemberEnter)JmlMemberEnter.instance(context)).modeOfFileBeingChecked = specs.mode; env = specs.topLevelEnv; // FIXME - is this ever nonnull? if (env == null) { env = specs.topLevelEnv = topLevelEnv(specs); visitTopLevel(specs); Env<AttrContext> prevEnvME = JmlMemberEnter.instance(context).env; JmlMemberEnter.instance(context).env = env; JmlMemberEnter.instance(context).importHelper(specs); // super.memberEnter(specs, env); // Does not do members for binary classes // env = specs.topLevelEnv; ListBuffer<JCTree> newlist = null; for (JCTree d: specs.defs) { if (!(d instanceof JmlClassDecl)) continue; JmlClassDecl cd = (JmlClassDecl)d; if (cd.sym == null) { // This class had errors such that we should remove it if (newlist == null) { newlist = new ListBuffer<>(); newlist.appendList(specs.defs); } newlist = Utils.remove(newlist, cd); continue; } cd.specsDecl = cd; Env<AttrContext> clenv = typeEnvs.get(cd.sym); if (clenv == null) { clenv = classEnv(cd, env); typeEnvs.put(cd.sym, clenv); } cd.env = clenv; JmlMemberEnter.instance(context).memberEnter(cd,clenv); // FIXME - does nothing } if (newlist != null) specs.defs = newlist.toList(); JmlMemberEnter.instance(context).env = prevEnvME; } for (JCTree cd: specs.defs) { if (!(cd instanceof JmlClassDecl)) continue; JmlClassDecl jcd = (JmlClassDecl)cd; //JmlSpecs.instance(context).putSpecs(jcd.sym, new JmlSpecs.TypeSpecs(jcd)); if (utils.isJML(jcd.mods)) { // A class declared within JML - so it is supposed to be a model class with a unique name // FIXME - check that name is not already used by a real Java class // FIXME - each model method will be entered for each declaration in the specification file // We have the source code for this so we want to enter this as a source-based class classEnter(cd,env); } // FIXME - need to handle any secondary classes and nested classes as well } env = prevEnv; return true; } /** Complain about a duplicate class. Overridde so we can shut off the error * when we are just checking to see if the class is already declared. */ @Override protected void duplicateClass(DiagnosticPosition pos, ClassSymbol c) { if (((JmlCheck)chk).noDuplicateWarn) return; log.error(pos, "duplicate.class", c.fullname); } @Override public void visitClassDef(JCClassDecl that) { // We need to match up classes before calling super.classDefs so that // the specs for nested classes can be computed. Nothing else should // be done with the specs, however, until the Java class is 'entered' // in the visitClassDef call. JmlClassDecl thattree = (JmlClassDecl)that; boolean isSpecForBinary = thattree.toplevel != null && thattree.toplevel.mode == JmlCompilationUnit.SPEC_FOR_BINARY; JmlClassDecl specstree; JmlClassDecl jmltree; ClassSymbol csym = null; String flatname = null; if (isSpecForBinary) { if (env.tree instanceof JmlCompilationUnit) { String s = ((JmlCompilationUnit)env.tree).packge.toString(); if (!s.isEmpty()) s = s + "."; flatname = s + that.name.toString(); csym = ClassReader.instance(context).classExists(names.fromString(flatname)); flatname = flatname + "$"; } else if (env.tree instanceof JmlClassDecl) { String s = ((JmlClassDecl)env.tree).sym.flatname + "$"; flatname = s + that.name.toString(); csym = ClassReader.instance(context).classExists(names.fromString(flatname)); flatname = flatname + "$"; } specstree = thattree; specstree.sym = csym; jmltree = null; } else { jmltree = thattree; specstree = thattree.specsDecl; } java.util.List<JmlClassDecl> unmatched = new java.util.LinkedList<>(); // Match up specs classes with java classes and adjust for unmatched classes or duplicates if (specstree != null) { // Attaches specs tree from second list at classdecl.specsDecls for each classdecl in the first list // Matching classes has to come before visitClassDef because we need to filter out any non-Java class declarations // but we cannot add JML classes here because we don't have a class symbol yet if (!isSpecForBinary) { that.defs = matchClasses(that.defs, specstree.defs, thattree.source().toString()); } else { specstree.defs = matchClassesForBinary(null, flatname, specstree.defs, unmatched, thattree.source().toString()); } } if (csym == null) { boolean pre = ((JmlCheck)chk).noDuplicateWarn; if (isSpecForBinary) ((JmlCheck)chk).noDuplicateWarn = true; // FIXME - should this be !jml - as for COllection.Content super.visitClassDef(that); // Uses this.env if (isSpecForBinary) ((JmlCheck)chk).noDuplicateWarn = pre; if (that.sym == null) { log.error("jml.internal", "Unexpected null class symbol after processing class " + that.name); result = null; return; } csym = that.sym; } else { // Binary classes can come here if, for example, the class symbol was created by completing a package. // It that.sym = csym; csym.completer = memberEnter; chk.checkFlags(thattree.pos(), thattree.mods.flags, csym, thattree); // Do not overwrite flags in the binary csym.sourcefile = thattree.sourcefile; //cs.members_field = new Scope(cs); // DO not overwrite the fields that are in the binary Scope enclScope = enterScope(env); enclScope.enter(csym); } Env<AttrContext> localEnv = null; if (isSpecForBinary) { // FIXME - this is already done for source classes. Is it needed for binary classes? localEnv = classEnv(that, env); // FIXME - we might well need this, but classEnv(that,env) fails for loading secs of binary classes typeEnvs.put(csym, localEnv); thattree.env = localEnv; } boolean ok = true; if (specstree != null) { // Check the names of type parameters in the specifications against those defined // in the class symbol (so should work for both source and binary). // Sets the type of type parameters in the specs declaration accordingly. // With the third argument null, no class entering is performed; all the type // parameters from the source/binary Java file should be entered and in scope. ok = checkAndEnterTypeParameters(csym, specstree, typeEnvs.get(csym)); } // Set the sym and env fields of the classes if (ok) { Env<AttrContext> localenv = getEnv(csym); thattree.env = localenv; if (jmltree != null) { jmltree.env = localenv; } if (specstree != null) { specstree.sym = that.sym; // FIXME - the specstree might actually want a different local environment because it may have different imports specstree.env = localenv; } } else { if (specstree != null) { specstree.sym = null; specstree.env = null; } } if (ok) { JmlSpecs.TypeSpecs tsp = specs.combineSpecs(that.sym,jmltree,specstree); if (jmltree != null) jmltree.typeSpecs = tsp; if (specstree != null) specstree.typeSpecs = tsp; } else { csym.completer = null; recordEmptySpecs(csym); result = null; } if (isSpecForBinary && !unmatched.isEmpty()) { for (JmlClassDecl c: unmatched) { classEnter(c, localEnv); } } } // FIXME - unify the recording of empty specs with default specs?? public void recordEmptySpecs(ClassSymbol csymbol) { // TODO - change this if we store JML specs in binary files - then could get annotation information from the symbol JmlSpecs.TypeSpecs typespecs = new JmlSpecs.TypeSpecs(csymbol, csymbol.sourcefile, JmlTree.Maker.instance(context).Modifiers(csymbol.flags(),List.<JCTree.JCAnnotation>nil()), null); specs.putSpecs(csymbol,typespecs); // FIXME - should we be checking for members_field being null? or should we allow completing to proceed? if (csymbol.members_field != null) for (Symbol s: csymbol.getEnclosedElements()) { if (s instanceof ClassSymbol) recordEmptySpecs((ClassSymbol)s); } } // FIXME - needs review /** Compares the type parameters for the Java class denoted by csym and the * type parameters in the given type declaration (typically from a * specification file), in the context of the given name environment. * Issues error messages if types or names do not match; attributes * the types; returns false if there were errors. * @param csym the class whose local env we are manipulating * @param specTypeDeclaration the declaration of the class in a specification file * @param classEnv the environment which is modified by the addition of any type parameter information */ public boolean checkAndEnterTypeParameters(ClassSymbol csym, JmlClassDecl specTypeDeclaration, Env<AttrContext> classEnv) { Env<AttrContext> localEnv = classEnv; //Scope enterScope = enterScope(classEnv); boolean result = true; int numSpecTypeParams = specTypeDeclaration.typarams.size(); int numJavaTypeParams = csym.type.getTypeArguments().size(); if (numSpecTypeParams != numJavaTypeParams) { utils.error(specTypeDeclaration.source(),specTypeDeclaration.pos(),"jml.mismatched.type.arguments",specTypeDeclaration.name,csym.type.toString()); //log.error(specTypeDeclaration.pos(),"jml.mismatched.type.parameters", specTypeDeclaration.name, csym.fullname, n, javaN); result = false; } int nn = numSpecTypeParams; if (numJavaTypeParams < nn) nn = numJavaTypeParams; for (int i = 0; i<nn; i++) { JCTree.JCTypeParameter specTV = specTypeDeclaration.typarams.get(i); TypeVar javaTV = (TypeVar)((ClassType)csym.type).getTypeArguments().get(i); if (specTV.name != javaTV.tsym.name) { log.error(specTV.pos(),"jml.mismatched.type.parameter.name", specTypeDeclaration.name, csym.fullname, specTV.name, javaTV.tsym.name); result = false; } // classEnter will set the type of the Type Variable, but it sets it to // something new for each instance, which causes trouble in type mathcing // that I have not figured out. Here we preemptively set the type to be the // same as the Java type that it matches in the specification. specTV.type = javaTV; if (localEnv != null) classEnter(specTV,localEnv); // FIXME - wouldn't this be a duplicate - or is localEnv always null //enterScope.enter(javaTV.tsym); } for (int i = nn; i<numSpecTypeParams; i++) { JCTree.JCTypeParameter specTV = specTypeDeclaration.typarams.get(i); if (localEnv != null) classEnter(specTV,localEnv); } // FIXME need to check that the types have the same bounds return result; //log.noticeWriter.println(" LOCAL ENV NOW " + localEnv); } /** This overrides the parent class method so that we allow file names * with spec extensions, not just .java * * @param c the class the file is associated with * @param env the Env object representing the filename */ @Override public boolean classNameMatchesFileName(ClassSymbol c, // OPENJML - changed from private to public Env<AttrContext> env) { JavaFileObject jfo = env.toplevel.sourcefile; if (jfo.getKind() == JavaFileObject.Kind.SOURCE) return super.classNameMatchesFileName(c, env); String classname = c.name.toString(); // FIXME: Actually we are loose in our comparison String filename = jfo.getName(); return filename.endsWith(classname + Strings.specsSuffix); // FIXME - what if classname is just the tail of the filename } /** The net result of this call is that all classes, including secondary and nested classes (but not local classes) * defined in the given list of source code compilation units are created: * a symbol is defined, * a typespecs structure is created and initialized with class-level annotations, * for each class the JmlClassDecl.specsDecls field is filled in with the JmlClassDecl for the specifications (if any) * But no members other than class members are processed. * Note that the compilation units in the argument list are all JmlCompilationUnits; the * tree.specsCompilationUnit field is filled in either with a reference to the same java source compilation unit or to the * specs compilation unit obtained by parsing the corresponding .jml file. * * @param trees the parse trees coming from the user-specified list of * files to process */ @Override public void main(List<JCCompilationUnit> trees) { super.main(trees); } }