/* * This file is part of the OpenJML project. * Author: David R. Cok */ package com.sun.tools.javac.comp; import static com.sun.tools.javac.code.Flags.DEFAULT; import static com.sun.tools.javac.code.Flags.FINAL; import static com.sun.tools.javac.code.Flags.HASINIT; import static com.sun.tools.javac.code.Flags.INTERFACE; import static com.sun.tools.javac.code.Flags.SIGNATURE_POLYMORPHIC; import static com.sun.tools.javac.code.Flags.STATIC; import static com.sun.tools.javac.code.Kinds.PCK; import static com.sun.tools.javac.code.Kinds.TYP; import java.io.PrintWriter; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import javax.tools.JavaFileObject; import javax.tools.JavaFileObject.Kind; import org.eclipse.jdt.annotation.Nullable; import org.jmlspecs.openjml.JmlSpecs; import org.jmlspecs.openjml.JmlSpecs.MethodSpecs; import org.jmlspecs.openjml.JmlTokenKind; import org.jmlspecs.openjml.JmlTree; import org.jmlspecs.openjml.JmlTree.JmlAnnotation; import org.jmlspecs.openjml.JmlTree.JmlClassDecl; import org.jmlspecs.openjml.JmlTree.JmlCompilationUnit; import org.jmlspecs.openjml.JmlTree.JmlMethodDecl; import org.jmlspecs.openjml.JmlTree.JmlSource; import org.jmlspecs.openjml.JmlTree.JmlTypeClause; import org.jmlspecs.openjml.JmlTree.JmlTypeClauseDecl; import org.jmlspecs.openjml.JmlTree.JmlTypeClauseInitializer; import org.jmlspecs.openjml.JmlTree.JmlTypeClauseRepresents; import org.jmlspecs.openjml.JmlTree.JmlVariableDecl; import org.jmlspecs.openjml.JmlTreeUtils; import org.jmlspecs.openjml.Strings; import org.jmlspecs.openjml.Utils; import com.sun.tools.javac.code.Attribute.Compound; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.code.Kinds; import com.sun.tools.javac.code.Lint; 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.Symbol.MethodSymbol; import com.sun.tools.javac.code.Symbol.PackageSymbol; import com.sun.tools.javac.code.Symbol.TypeSymbol; import com.sun.tools.javac.code.Symbol.VarSymbol; import com.sun.tools.javac.code.Symtab; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.Types; 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.JCMethodDecl; import com.sun.tools.javac.tree.JCTree.JCModifiers; import com.sun.tools.javac.tree.JCTree.JCStatement; import com.sun.tools.javac.tree.JCTree.JCTypeParameter; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import com.sun.tools.javac.util.Assert; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.FatalError; import com.sun.tools.javac.util.JCDiagnostic; 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.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.Position; /** * This class extends MemberEnter to add some JML processing to the process of * entering the members of a class into the class definition. The Enter * process has already happened, creating a ClassSymbol and an Env for the class. * Now the contents of the class have to be filled in. * <P> * MemberEnter does the following when it 'completes' a class: * <UL> * <LI>makes sure imports have been read into the class environment * <LI>marks the class as not yet attributed * <LI>completes any enclosing classes * <LI>attributes any declared super types * <LI>attributes the annotation types * <LI>sets the annotations for later processing * <LI>attributes any type variables the class has * <LI>puts the class on the 'halfcompleted' list for finishing * </UL> * The finishing process, for each method and field of the class, defines a * symbol, puts it into the AST, puts the symbol in the class's scope(Env). * Field Declarations: type is attributed, symbol defined, annotations put on * annotateLater list. Method Declarations: creates method symbol and type; in * the process attributes type variables, result, parameter, and exception types. * Annotations are put on the annotateLater list. * <P> * JmlMemberEnter adds to the above. When a class is completed, it enters any * ghost variables and model methods that were declared in the specifications; * it also parses up the specifications and fills in the TypeSpecs, MethodSpecs * and FieldSpecs corresponding to the various declarations. * <P> * Note that when specs are read for a binary class, we also need to do all the * matching up of members to Java elements and then create all of the same * specification infrastructure. */ public class JmlMemberEnter extends MemberEnter {// implements IJmlVisitor { protected Context context; protected Utils utils; protected JmlEnter enter; protected JmlResolve resolve; protected Names names; protected JmlTree.Maker jmlF; protected Symtab syms; protected JmlSpecs specs; protected Name modelName; protected Log log; Name org_jmlspecs_lang; /** True when we are processing declarations that are in a specification file; * false when we are in a Java file (even if we are processing specifications) */ boolean inSpecFile; public JmlMemberEnter(Context context) { super(context); this.context = context; this.utils = Utils.instance(context); this.resolve = JmlResolve.instance(context); this.enter = (JmlEnter)JmlEnter.instance(context); this.names = Names.instance(context); this.org_jmlspecs_lang = names.fromString(Strings.jmlSpecsPackage); this.jmlF = JmlTree.Maker.instance(context); this.syms = Symtab.instance(context); this.specs = JmlSpecs.instance(context); this.modelName = names.fromString(JmlTokenKind.MODEL.internedName()); this.log = Log.instance(context); } public static void preRegister(final Context context) { context.put(memberEnterKey, new Context.Factory<MemberEnter>() { public MemberEnter make(Context context) { return new JmlMemberEnter(context); } }); } /** Overridden simply to catch null first arguments. The super class will crash * but the first argument may be null if we continue after parsing with * input files that contain parse errors. * @param trees * @param env */ @Override void memberEnter(List<? extends JCTree> trees, Env<AttrContext> env) { if (trees != null) super.memberEnter(trees,env); } public boolean dojml = false; @Override public void memberEnter(JCTree tree, Env<AttrContext> env) { if (tree instanceof JmlTypeClause) return; super.memberEnter(tree, env); if (tree instanceof JmlCompilationUnit) { // FIXME - this is also called with tree being a JmlClassDecl - then nothing is done; also part of the below is done already?? JmlCompilationUnit javacu = (JmlCompilationUnit)tree; JmlCompilationUnit specscu = javacu.specsCompilationUnit; // Normally, a Java CU has attached a specs CU, even if it is the same as itself. // However, memberEnter is called when completing a class symbol to complete the package declaration // in its top level environment; so for a model class in a specs compilation unit the toplevel is // the specs compilation unit, which may have a null specscu field // FIXME - not sure this is what we want. We need the normal env (without model imports), // and an env for the specs file (which may be either the .jml or .java file) that includes // model imports org.jmlspecs.lang and model declarations. if (specscu != null) { Env<AttrContext> specenv = specscu.topLevelEnv; if (specenv == null) specenv = env; if (specenv != env) importAll(tree.pos, reader.enterPackage(names.java_lang), specenv); super.memberEnter(specscu, specenv); importAll(tree.pos, reader.enterPackage(names.fromString(Strings.jmlSpecsPackage)), specenv); } } } public int modeOfFileBeingChecked = 0; protected JmlClassDecl currentClass = null; @Override protected void finishClass(JCClassDecl tree, Env<AttrContext> env) { PrintWriter noticeWriter = log.getWriter(WriterKind.NOTICE); JmlClassDecl jtree = (JmlClassDecl)tree; JavaFileObject prevSource = log.useSource(jtree.source());; JmlClassDecl prevClass = currentClass; currentClass = (JmlClassDecl)tree; int prevMode = modeOfFileBeingChecked; // FIXME _ suspect this is not accurate int nowmode = modeOfFileBeingChecked = ((JmlCompilationUnit)env.toplevel).mode; boolean prevAllowJML = resolve.allowJML(); if (jtree.isJML()) resolve.setAllowJML(true); boolean isSpecForBinary = jtree.toplevel != null && jtree.toplevel.mode == JmlCompilationUnit.SPEC_FOR_BINARY; // Adjust the members of jtree - if there is a separate specs file, then remove any JML declarations in the Java file and // replace them with JML declaratinos from the specs file boolean prevChk = ((JmlCheck)chk).noDuplicateWarn; boolean prevEntering = noEntering; if (!isSpecForBinary) { ListBuffer<JCTree> defs = new ListBuffer<>(); if (jtree.specsDecl != jtree) { for (JCTree t: jtree.defs) { if (t instanceof JmlMethodDecl) { if (!utils.isJML(((JmlMethodDecl)t).mods)) defs.add(t); // else omit it } else if (t instanceof JmlVariableDecl) { if (!utils.isJML(((JmlVariableDecl)t).mods)) defs.add(t); // else omit it } else if (t instanceof JmlClassDecl) { defs.add(t); } else if (t instanceof JmlTypeClause) { // omit } else { defs.add(t); } } if (jtree.specsDecl != null) for (JCTree t: jtree.specsDecl.defs) { if (t instanceof JmlMethodDecl) { if (utils.isJML(((JmlMethodDecl)t).mods)) { defs.add(t); } } else if (t instanceof JmlVariableDecl) { if (utils.isJML(((JmlVariableDecl)t).mods)) { defs.add(t); } } } jtree.defs = defs.toList(); dojml = true; JmlCheck.instance(context).noDuplicateWarn = false; ListBuffer<JCTree> remove = null; for (JCTree t: jtree.defs) { if (t instanceof JmlSource) log.useSource(((JmlSource)t).source()); memberEnter(t,env); // FIXME - do something special for enums if (t instanceof JCVariableDecl && ((JCVariableDecl)t).sym == null) { if (remove == null) remove = new ListBuffer<>(); remove.add(t); } } if (remove != null) { for (JCTree t: remove) { jtree.defs = Utils.remove(jtree.defs, t); if (jtree.specsDecl !=null) jtree.specsDecl.defs = Utils.remove(jtree.specsDecl.defs, t); } } //// boolean prevChk = ((JmlCheck)chk).noDuplicateWarn; //// if (isSpecForBinary) ((JmlCheck)chk).noDuplicateWarn = false; //// if (isSpecForBinary) dojml = true; //// super.finishClass(tree, env); //// if (isSpecForBinary) dojml = false; //// if (isSpecForBinary) ((JmlCheck)chk).noDuplicateWarn = prevChk; // } } else { super.finishClass(jtree, env); } // Now create symbols for all of the Java and JML methods and fields // This operation will report any duplicates } if (isSpecForBinary) { jtree.specsDecl = jtree; // Here we add the JML declarations to the class log.useSource(jtree.source()); JCTree first = jtree.defs.head; if (first != null) { if (first instanceof JmlMethodDecl) { if ((((JmlMethodDecl)first).mods.flags & Flags.GENERATEDCONSTR) != 0) { jtree.defs = Utils.remove(jtree.defs,first); } } } for (JCTree t: jtree.defs) { boolean jml = false; boolean skip = false; if (t instanceof JmlMethodDecl) { if (utils.isJML(((JmlMethodDecl)t).mods)) jml = true; // else omit it } else if (t instanceof JmlVariableDecl) { if (utils.isJML(((JmlVariableDecl)t).mods)) jml = true; // else omit it } else if (t instanceof JmlClassDecl) { if (utils.isJML(((JmlClassDecl)t).mods)) jml = true; } else { skip = true; } // Now create symbols for the JML methods and fields // We need to call memberEnter on Java declarations in the specs file to be // sure that method types and annotations are processed. However they will // then be reported as duplicates, so we turn off duplicate warnings // FIXME - however, then erroneous unmatched java declarations will be added to the class and silently accepted. if (!skip) { JmlCheck jmlcheck = JmlCheck.instance(context); boolean pre = jmlcheck.noDuplicateWarn; jmlcheck.noDuplicateWarn = !jml; noEntering = !jml && !utils.isJML(jtree); // FIXME - does this really need to be recursive? memberEnter(t,env); // FIXME - do something special for enums jmlcheck.noDuplicateWarn = pre; } } } noEntering = prevEntering; JmlCheck.instance(context).noDuplicateWarn = prevChk; if (utils.isJML(tree.mods)) resolve.setAllowJML(prevAllowJML); if (utils.jmlverbose >= Utils.JMLDEBUG) log.getWriter(WriterKind.NOTICE).println(" MEMBER ENTER FINISHING CLASS " + tree.sym.fullname); // This is the case of a java source file, with a possibly-null // specification file. Besides entering all the Java // field and method and initializer(?) members in the usual way (done above in the super call), // we need to: // connect specifications of Java members with the members // enter ghost and model fields, methods, initializers, along with // their specifications ClassSymbol csym = jtree.sym; boolean prevInSpecFile = inSpecFile; inSpecFile = jtree.source() == null ? false : jtree.source().getKind() != Kind.SOURCE; // should always be false (?) currentClass = prevClass; // Now go through everything in the specifications, finding the // JML fields and methods. These need to be entered. prevAllowJML = resolve.allowJML(); boolean prevInModel = inModelTypeDeclaration; try { JmlClassDecl specsDecl = jtree.specsDecl; if (specsDecl == null) { if (utils.jmlverbose >= Utils.JMLDEBUG) noticeWriter.println("FINISHING CLASS - NO SPECS " + tree.sym.fullname); checkTypeMatch(jtree,jtree); // Still need to check modifiers return; } if (utils.jmlverbose >= Utils.JMLDEBUG) noticeWriter.println("FINISHING CLASS - JML PHASE " + tree.sym.fullname); // First we scan through the Java parts of the spec file // Various checks happen in the appropriate visitMethods boolean isModel = JmlAttr.instance(context).isModel(specsDecl.mods) || JmlAttr.instance(context).implementationAllowed; inModelTypeDeclaration = isModel; log.useSource(specsDecl.source()); // TODO : need to do something to distinguish hand created parse trees inSpecFile = specsDecl.source() == null ? false : specsDecl.source().getKind() != Kind.SOURCE; // Could be a Java file in the specs sequence prevClass = currentClass; currentClass = jtree; // Misc checks on the Java declarations for (JCTree that: specsDecl.defs) { if (that instanceof JCTree.JCBlock) { if (inSpecFile && currentMethod == null && !utils.isJML(currentClass.mods)) { // initializer block is not allowed in a specification, unless it is a model class log.error(that.pos,"jml.initializer.block.allowed"); } } else if (that instanceof JmlVariableDecl) { // FIXME - anything here - or for other cases? } } resolve.setAllowJML(true); // At this point, all java and spec members are entered // We still need to connect specs of fields and methods with their Java counterparts // FIXME - what about blocks // First for Java fields and methods matchStuff(jtree, jtree.sym, env, specsDecl); // Now we fill in the convenience fields - we could not do this // earlier without prematurely overwriting what might be in the // Java file and used as part of the specs seqeuence // Also fill in default method specs for anything that does not have them // FIXME - same for other kinds of fields? Or should we just interpret absence as default everywhere? // First for Java fields and methods for (JCTree t: jtree.defs) { if (t instanceof JmlVariableDecl) { JmlVariableDecl v = (JmlVariableDecl)t; v.fieldSpecsCombined = specs.getSpecs(v.sym); } else if (t instanceof JmlMethodDecl) { JmlMethodDecl m = (JmlMethodDecl)t; MethodSpecs ms; if (m.specsDecl == null) { ms = specs.defaultSpecs(m); } else { ms = new MethodSpecs(m.specsDecl); } m.methodSpecsCombined = ms; JmlSpecs.instance(context).putSpecs(m.sym, ms); } } // FIXME = use a visitor to be more O-O ? // FIXME - unify this method with the near duplicate below checkFinalTypeSpecs(specs.get(csym)); } finally { inSpecFile = prevInSpecFile; inModelTypeDeclaration = prevInModel; addRacMethods(tree.sym,env); resolve.setAllowJML(prevAllowJML); log.useSource(prevSource); if (utils.jmlverbose >= Utils.JMLDEBUG) { noticeWriter.println("FINISHING CLASS - COMPLETE " + tree.sym.fullname); noticeWriter.println(" SCOPE IS " + tree.sym.members()); } modeOfFileBeingChecked = prevMode; currentClass = prevClass; } } protected boolean noEntering = false; protected void visitMethodDefHelper(JCMethodDecl tree, MethodSymbol m, Scope enclScope) { if (chk.checkUnique(tree.pos(), m, enclScope)) { if (!noEntering) { if (tree.body == null && m.owner.isInterface() && utils.isJML(m.flags())) { m.flags_field |= (Flags.ABSTRACT | Utils.JMLADDED); m.enclClass().flags_field |= Utils.JMLADDED; } enclScope.enter(m); } } } // FIXME _ not currently used public void checkForGhostModel(JCModifiers mods, JavaFileObject source, DiagnosticPosition pos) { JmlAnnotation a = utils.findMod(mods, JmlTokenKind.MODEL); if (a == null) a = utils.findMod(mods, JmlTokenKind.GHOST); if (!utils.isJML(mods)) { if (a != null) utils.error(source, pos, "jml.ghost.model.on.java"); } else { if (a == null) utils.error(source, pos , "jml.missing.ghost.model"); } } protected List<JCTree> matchStuff(@Nullable JmlClassDecl jtree, ClassSymbol csym, Env<AttrContext> env, JmlClassDecl specsDecl) { Map<Symbol,JCTree> matches = new HashMap<Symbol,JCTree>(); ListBuffer<JCTree> newlist = new ListBuffer<>(); ListBuffer<JCTree> toadd = new ListBuffer<>(); ListBuffer<JCTree> toremove = new ListBuffer<>(); Env<AttrContext> prevEnv = this.env; this.env = env; for (JCTree specsMemberDecl: specsDecl.defs) { if (specsMemberDecl instanceof JmlVariableDecl) { JmlVariableDecl specsVarDecl = (JmlVariableDecl)specsMemberDecl; boolean ok = matchAndSetFieldSpecs(jtree, csym, specsVarDecl, matches, jtree == specsDecl); if (ok) { newlist.add(specsVarDecl); // FIXME - are we actually using newlist? should we? } else { toremove.add(specsVarDecl); } } else if (specsMemberDecl instanceof JmlMethodDecl) { JmlMethodDecl specsMethodDecl = (JmlMethodDecl)specsMemberDecl; boolean ok = matchAndSetMethodSpecs(jtree, csym, specsMethodDecl, env, matches, jtree == specsDecl); if (!ok) { toremove.add(specsMethodDecl); } } else { // newlist.add(specsMemberDecl); } } // The following is somewhat inefficient, but it is only called when there are errors for (JCTree t: toremove.toList()) { jtree.defs = Utils.remove(jtree.defs, t); } this.env = prevEnv; matches.clear(); return toadd.toList(); } /** Finds a Java declaration matching the given specsVarDecl in the given class. * If javaDecl == null, then this is a match of specs to the members of a binary class symbol. * If javaDec != null, then it is the Java class declaration * <br>the matching symbol, if any, is returned * <br>if no match and specsVarDecl is not ghost or model, error message issued, null returned * <br>if match is duplicate, error message issued, match returned * <br>if non-duplicate match, and javaDecl defined, set javaDecl.specsDecl field * <br>if non-duplicate match, set specs database * <br>if non-duplicate match, set specsVarDecl.sym field * */ protected boolean matchAndSetFieldSpecs(JmlClassDecl javaDecl, ClassSymbol csym, JmlVariableDecl specsVarDecl, Map<Symbol,JCTree> matchesSoFar, boolean sameTree) { // Find any specsVarDecl counterpart in the javaDecl // For fields it is sufficient to match by name // if (specsVarDecl.name.toString().equals("configurationSizes")) Utils.stop(); Name id = specsVarDecl.name; VarSymbol matchSym = null; if (true || !sameTree) { Scope.Entry entry = csym.members().lookup(id); while (entry != null && entry.sym != null) { if (entry.sym instanceof VarSymbol && entry.sym.name == id) { matchSym = (VarSymbol)entry.sym; break; } entry = entry.next(); } } else { matchSym = specsVarDecl.sym; } // matchsym == null ==> no match; otherwise matchSym is the matching symbol if (matchSym == null) { if (!utils.isJML(specsVarDecl.mods)) { // We are going to discard this declaration because of the error, so we do extra checking JmlAnnotation a = ((JmlAttr)attr).findMod(specsVarDecl.mods,JmlTokenKind.GHOST); if (a == null) a = ((JmlAttr)attr).findMod(specsVarDecl.mods,JmlTokenKind.MODEL); if (a != null) { utils.error(specsVarDecl.sourcefile, a.pos(),"jml.ghost.model.on.java",specsVarDecl.name); } // Non-matching java declaration - an error // FIXME - the check on the owner should really be recursive if (!utils.isJML(csym.flags())) utils.error(specsVarDecl.sourcefile, specsVarDecl.pos(),"jml.no.var.match",specsVarDecl.name); return false; } else { // Non-matching JML declaration if (javaDecl != null) utils.error(specsVarDecl.sourcefile, specsVarDecl.pos(),"jml.internal","A JML declaration should have been matched, but was not"); return javaDecl == null; } } // The matches map holds any previous matches found - all to specification declarations JCTree prevMatch = matchesSoFar.get(matchSym); if (prevMatch != null && prevMatch != specsVarDecl) { // DO extra checking since we are discarding this declaration if (!utils.isJML(specsVarDecl.mods)) { JmlAnnotation a = ((JmlAttr)attr).findMod(specsVarDecl.mods,JmlTokenKind.GHOST); if (a == null) a = ((JmlAttr)attr).findMod(specsVarDecl.mods,JmlTokenKind.MODEL); if (a != null) { utils.error(specsVarDecl.sourcefile, a.pos(),"jml.ghost.model.on.java",specsVarDecl.name); } } // Previous match - give error // duplicate already reported if the specs declaration is JML declaration if (!utils.isJML(specsVarDecl.mods) && !sameTree) { utils.errorAndAssociatedDeclaration(specsVarDecl.sourcefile, specsVarDecl.pos, ((JmlVariableDecl)prevMatch).sourcefile, prevMatch.pos,"jml.duplicate.var.match",specsVarDecl.name); } return false; } { // New match - save it; also set the specs database matchesSoFar.put(matchSym, specsVarDecl); JmlSpecs.FieldSpecs fieldSpecs = specsVarDecl.fieldSpecs; if (fieldSpecs != null) JmlSpecs.instance(context).putSpecs(matchSym,fieldSpecs); else { fieldSpecs = new JmlSpecs.FieldSpecs(specsVarDecl); // FIXME - what about lists of in clauses specsVarDecl.fieldSpecs = fieldSpecs; specs.putSpecs(matchSym, fieldSpecs); } specsVarDecl.sym = matchSym; specsVarDecl.type = matchSym.type; if (!sameTree) { // Copied from MemberEnter.visitVarDef Env<AttrContext> localEnv = env; if (visitVarDefIsStatic(specsVarDecl,env)) { localEnv = env.dup(specsVarDecl, env.info.dup()); localEnv.info.staticLevel++; } annotateLater(specsVarDecl.mods.annotations, localEnv, matchSym, specsVarDecl.pos()); typeAnnotate(specsVarDecl.vartype, env, matchSym, specsVarDecl.pos()); } } // If there is a Java AST, find the match and set the specsDecl field JmlVariableDecl javaMatch = null; if (sameTree) { javaMatch = specsVarDecl; } else if (javaDecl != null) { // TODO - is there a better way to find a declaration for a symbol? for (JCTree t: javaDecl.defs) { if (t instanceof JmlVariableDecl && ((JmlVariableDecl)t).sym == matchSym) { javaMatch = (JmlVariableDecl)t; break; } } if (javaMatch == null) { log.error("jml.internal", "Unexpected absent Java field declaration, without a matching symbol: " + matchSym); } else if (javaMatch.specsDecl == null) { javaMatch.specsDecl = specsVarDecl; javaMatch.fieldSpecsCombined = specsVarDecl.fieldSpecs; } else if (javaMatch.specsDecl != javaMatch && javaMatch != specsVarDecl) { javaMatch = null; log.error("jml.internal", "Unexpected duplicate Java field declaration, without a matching symbol: " + matchSym); } } if (javaMatch != specsVarDecl) { // Check the match only if it is not a duplicate checkFieldMatch(javaMatch,matchSym,specsVarDecl); addAnnotations(matchSym,enter.getEnv(csym),specsVarDecl.mods); } return true; } /** Finds a Java method declaration matching the given specsMethodDecl in the given class * <br>returns false if the declaration is to be ignored because it is in error * <br>if no match and specsVarDecl is not ghost or model, error message issued, null returned * <br>if match is duplicate, error message issued, match returned * <br>if non-duplicate match, and javaDecl defined, set javaDecl.specsDecl field * <br>if non-duplicate match, set specs database * <br>if non-duplicate match, set specsVarDecl.sym field * */ protected boolean matchAndSetMethodSpecs(@Nullable JmlClassDecl javaDecl, ClassSymbol csym, JmlMethodDecl specsMethodDecl, Env<AttrContext> env, Map<Symbol,JCTree> matchesSoFar, boolean sameTree) { // Find the counterpart to specsMethodDecl (from the .jml file) in the Java class declaration (javaDecl or csym) // Note that if the class is binary, javaDecl will be null, but csym will not MethodSymbol matchSym = false ? specsMethodDecl.sym : matchMethod(specsMethodDecl,csym,env,false); // matchsym == null ==> no match or duplicate; otherwise matchSym is the matching symbol if (matchSym == null) { // DO we need to do any cross-linking? and in field specs? // combinedSpecs.cases.decl = specsMethodDecl; // specsMethodDecl.methodSpecsCombined = combinedSpecs; JmlAnnotation a = ((JmlAttr)attr).findMod(specsMethodDecl.mods,JmlTokenKind.GHOST); if (a == null) a = ((JmlAttr)attr).findMod(specsMethodDecl.mods,JmlTokenKind.MODEL); boolean classIsModel = ((JmlAttr)attr).isModel(javaDecl.getModifiers()); // FIXME - should really be recursive if (!utils.isJML(specsMethodDecl.mods)) { // Method is not (directly) in a JML declaration. So it should not have ghost or model annotations // We are going to discard this declaration because of the error, so we do extra checking if (a != null) { utils.error(specsMethodDecl.sourcefile, a.pos(),"jml.ghost.model.on.java",specsMethodDecl.name); } // Non-matching java declaration - an error if (!classIsModel) { utils.error(specsMethodDecl.sourcefile, specsMethodDecl.pos(),"jml.no.method.match", csym.flatName() + "." + specsMethodDecl.sym); } return false; } else { // Non-matching ghost or model declaration; this is OK - there is no symbol yet // This should have a model or ghost declaration - that is checked in JmlAttr return true; } } // The matches map holds any previous matches found - all to specification declarations JCTree prevMatch = matchesSoFar.get(matchSym); if (prevMatch != null) { // DO extra checking since we are discarding this declaration because it is already matched if (!utils.isJML(specsMethodDecl.mods)) { JmlAnnotation a = ((JmlAttr)attr).findMod(specsMethodDecl.mods,JmlTokenKind.GHOST); if (a == null) a = ((JmlAttr)attr).findMod(specsMethodDecl.mods,JmlTokenKind.MODEL); if (a != null) { utils.error(specsMethodDecl.sourcefile, a.pos(),"jml.ghost.model.on.java",specsMethodDecl.name); } } // Previous match - give error - duplicate already reported if the specsMethodDecl is JML if (!utils.isJML(specsMethodDecl.mods) && !sameTree) { utils.errorAndAssociatedDeclaration(specsMethodDecl.sourcefile, specsMethodDecl.pos, ((JmlMethodDecl)prevMatch).sourcefile, prevMatch.pos,"jml.duplicate.method.match",specsMethodDecl.sym.toString(), csym.flatName()); } return false; } { // New match - save it; also set the specs database matchesSoFar.put(matchSym, specsMethodDecl); JmlSpecs.MethodSpecs mspecs = new JmlSpecs.MethodSpecs(specsMethodDecl); specs.putSpecs(matchSym,mspecs); specsMethodDecl.sym = matchSym; specsMethodDecl.methodSpecsCombined = mspecs; } // If we have actual source, then find the source declaration JmlMethodDecl javaMatch = null; if (javaDecl != null) { // TODO - is there a better way to find the declaration for a method symbol? if (sameTree) { javaMatch = specsMethodDecl; } else { for (JCTree t: javaDecl.defs) { if (t instanceof JmlMethodDecl && ((JmlMethodDecl)t).sym == matchSym) { javaMatch = (JmlMethodDecl)t; break; } } } if (javaMatch == null) { log.error("jml.internal", "Unexpected absent Java method declaration, without a matching symbol: " + matchSym); } else if (javaMatch.specsDecl == null) { // The specs file declaration corresponds to // MethodSymbol matchSym and // to a Java source method declaration javaMatch // Cross link them and set the specs field for the parameters as well javaMatch.specsDecl = specsMethodDecl; javaMatch.methodSpecsCombined = specsMethodDecl.methodSpecsCombined; javaMatch.methodSpecsCombined.cases.decl = javaMatch; // FIXME - is this needed? } else { javaMatch = null; log.error("jml.internal", "Unexpected duplicate Java method declaration, without a matching symbol: " + matchSym); } } { // Check the match only if it is not a duplicate checkMethodMatch(javaMatch,matchSym,specsMethodDecl,csym); addAnnotations(matchSym,env,specsMethodDecl.mods); } return true; } public void checkFinalTypeSpecs(JmlSpecs.TypeSpecs tspecs) { for (JmlTypeClause tc: tspecs.clauses) { if (tc instanceof JmlTypeClauseInitializer) { } } } public void addInitializerBlocks(ClassSymbol sym, Env<AttrContext> env) { JmlClassDecl classDecl = (JmlClassDecl)env.tree; JCTree.JCBlock block = jmlF.Block(Flags.SYNTHETIC, List.<JCStatement>nil()); classDecl.defs = classDecl.defs.append(block); classDecl.initializerBlock = block; block = jmlF.Block(Flags.STATIC|Flags.SYNTHETIC, List.<JCStatement>nil()); classDecl.defs = classDecl.defs.append(block); classDecl.staticInitializerBlock = block; } public void addRacMethods(ClassSymbol sym, Env<AttrContext> env) { if (!utils.rac) return; // We can't add methods to a binary class, can we? if (((JmlCompilationUnit)env.toplevel).mode == JmlCompilationUnit.SPEC_FOR_BINARY) return; if (sym.isAnonymous()) return; if (sym.isInterface()) return; // FIXME - deal with interfaces. ALso, no methods added to annotations JmlSpecs.TypeSpecs tsp = JmlSpecs.instance(context).get(sym); JCExpression vd = jmlF.Type(syms.voidType); JmlClassDecl jtree = (JmlClassDecl)env.tree; JmlClassDecl specstree = jtree.toplevel.mode == JmlCompilationUnit.SPEC_FOR_BINARY ? jtree : jtree.specsDecl; JmlTree.JmlMethodDecl m = jmlF.MethodDef( jmlF.Modifiers(Flags.PUBLIC|Flags.SYNTHETIC), names.fromString(org.jmlspecs.utils.Utils.invariantMethodString), vd, List.<JCTypeParameter>nil(), null, List.<JCVariableDecl>nil(), List.<JCExpression>nil(), jmlF.Block(0,List.<JCStatement>nil()), null); m.specsDecl = m; // Inner (non-static) classes may not have static members long staticFlag = Flags.STATIC; if (sym.getEnclosingElement() instanceof ClassSymbol && !sym.isStatic()) staticFlag = 0; JmlTree.JmlMethodDecl ms = jmlF.MethodDef( jmlF.Modifiers(Flags.PUBLIC|staticFlag|Flags.SYNTHETIC), names.fromString(org.jmlspecs.utils.Utils.staticinvariantMethodString), vd, List.<JCTypeParameter>nil(), null, List.<JCVariableDecl>nil(), List.<JCExpression>nil(), jmlF.Block(0,List.<JCStatement>nil()), null); ms.specsDecl = ms; utils.setJML(m.mods); utils.setJML(ms.mods); JCAnnotation a = tokenToAnnotationAST(JmlTokenKind.HELPER); m.mods.annotations = m.mods.annotations.append(a); ms.mods.annotations = ms.mods.annotations.append(a); a = tokenToAnnotationAST(JmlTokenKind.PURE); m.mods.annotations = m.mods.annotations.append(a); ms.mods.annotations = ms.mods.annotations.append(a); a = tokenToAnnotationAST(JmlTokenKind.MODEL); m.mods.annotations = m.mods.annotations.append(a); ms.mods.annotations = ms.mods.annotations.append(a); ListBuffer<JCTree> newdefs = new ListBuffer<>(); newdefs.add(m); newdefs.add(ms); // We can't use the annotations on the symbol because the annotations are not // necessarily completed. We could force it, but in the interst of least disruption // of the OpenJDK processing, we just use the AST instead. JmlAttr attr = JmlAttr.instance(context); Map<Name,JmlVariableDecl> modelMethodNames = new HashMap<>(); Symbol modelSym = attr.tokenToAnnotationSymbol.get(JmlTokenKind.MODEL); for (JCTree decl: specstree.defs) { if (!(decl instanceof JmlVariableDecl)) continue; JmlVariableDecl vdecl = (JmlVariableDecl)decl; JCAnnotation annotation = utils.findMod(vdecl.mods, modelSym); if (annotation == null) continue; VarSymbol vsym = vdecl.sym; JCTree.JCReturn returnStatement = jmlF.Return(JmlTreeUtils.instance(context).makeZeroEquivalentLit(vdecl.pos,vdecl.sym.type)); JCTree.JCThrow throwStatement = jmlF.Throw(jmlF.NewClass(null, List.<JCExpression>nil(), utils.nametree(decl.pos,Strings.jmlSpecsPackage + ".NoModelFieldMethod"), List.<JCExpression>nil(), null)); modelMethodNames.put(vsym.name,vdecl); JmlMethodDecl mr = makeModelFieldMethod(vdecl,tsp); newdefs.add(mr); JmlTypeClauseRepresents found = null; for (JCTree ddecl: tsp.clauses) { if (!(ddecl instanceof JmlTypeClauseRepresents)) continue; JmlTypeClauseRepresents rep = (JmlTypeClauseRepresents)ddecl; if (((JCTree.JCIdent)rep.ident).name != vdecl.name) continue; if (utils.isJMLStatic(vdecl.sym) != utils.isJMLStatic(rep.modifiers,sym)) continue; if (rep.suchThat) { continue; } if (found != null) { utils.warning(rep.source,ddecl.pos,"jml.duplicate.represents"); // FIXME - the duplicate is at found.pos continue; } returnStatement.expr = rep.expression; mr.body.stats = List.<JCStatement>of(returnStatement); mr.mods.flags &= ~Utils.JMLADDED; found = rep; } } List<JCTree> nd = newdefs.toList(); memberEnter(nd,env); jtree.defs = jtree.defs.appendList(nd); // The call to set the specs must come after the the method symbol is set, so after memberEnter for (JCTree md: nd) { setDefaultCombinedMethodSpecs((JmlMethodDecl)md); } } public JmlMethodDecl makeModelFieldMethod(JmlVariableDecl modelVarDecl, JmlSpecs.TypeSpecs tsp) { long flags = Flags.SYNTHETIC; flags |= (modelVarDecl.sym.flags() & (Flags.STATIC|Flags.AccessFlags)); JCTree.JCReturn returnStatement = jmlF.Return(JmlTreeUtils.instance(context).makeZeroEquivalentLit(modelVarDecl.pos,modelVarDecl.sym.type)); Name name = names.fromString(Strings.modelFieldMethodPrefix + modelVarDecl.name); JmlTree.JmlMethodDecl mr = (JmlTree.JmlMethodDecl)jmlF.MethodDef(jmlF.Modifiers(flags),name, jmlF.Type(modelVarDecl.sym.type), List.<JCTypeParameter>nil(),List.<JCVariableDecl>nil(),List.<JCExpression>nil(), jmlF.Block(0,List.<JCStatement>of(returnStatement)), null); mr.mods.flags |= Utils.JMLADDED; // FIXME - why? mr.pos = modelVarDecl.pos; utils.setJML(mr.mods); mr.mods.annotations = List.<JCAnnotation>of(utils.tokenToAnnotationAST(JmlTokenKind.MODEL,modelVarDecl.pos,modelVarDecl.getEndPosition(log.getSource(modelVarDecl.sourcefile).getEndPosTable()))); JmlSpecs.FieldSpecs fspecs = specs.getSpecs(modelVarDecl.sym); JmlTypeClauseDecl tcd = jmlF.JmlTypeClauseDecl(mr); tcd.pos = mr.pos; tcd.source = fspecs.source(); tcd.modifiers = mr.mods; tsp.modelFieldMethods.append(tcd); tsp.decls.append(tcd); return mr; } /** For synthetic methods or methods that do not have occasion to declare * specs in a specification file, this sets the combined specs to be those * that are associated with the method declaration itself. * @param mdecl */ protected void setDefaultCombinedMethodSpecs(JmlMethodDecl mdecl) { mdecl.methodSpecsCombined = new JmlSpecs.MethodSpecs(mdecl); specs.putSpecs(mdecl.sym,mdecl.methodSpecsCombined); } /** Checks that the jml annotations match Java annotations for annotations not in org.jmlspecs.annotation * and are a superset of the Java annotations for annotations in org.jmlspecs.annotation) */ // MUST HAVE log.useSource set to specs file! protected void checkSameAnnotations(Symbol sym, JCModifiers specsmods, JavaFileObject javaSource) { // FIXME - check for null in annotations? if (sym.isAnonymous()) return; PackageSymbol p = ((JmlAttr)attr).annotationPackageSymbol; for (Compound a : sym.getAnnotationMirrors()) { if (a.type.tsym.owner.equals(p)) { if (utils.findMod(specsmods,a.type.tsym) == null) { JavaFileObject prev = log.useSource(javaSource); log.error(specsmods.pos,"jml.java.annotation.superseded",a); log.useSource(prev); } } else { if (utils.findMod(specsmods,a.type.tsym) == null && !a.toString().startsWith("@sun")) { log.error(specsmods.pos,"jml.missing.annotation",a); } } } } /** Checks that the jml annotations are a superset of the Java annotations (for annotations in org.jmlspecs.annotation) */ // MUST HAVE log.useSource set to specs file! protected void checkSameAnnotations(JCModifiers javaMods, JCModifiers specsmods, JavaFileObject javaSource) { // FIXME - don't need last argument PackageSymbol p = ((JmlAttr)attr).annotationPackageSymbol; for (JCAnnotation a: javaMods.getAnnotations()) { if (a.type.tsym.owner.equals(p)) { if (utils.findMod(specsmods,a.type.tsym) == null) { JavaFileObject prev = log.useSource(((JmlTree.JmlAnnotation)a).sourcefile); log.error(specsmods.pos,"jml.java.annotation.superseded",a); log.useSource(prev); } } else { if (utils.findMod(specsmods,a.type.tsym) == null && !a.toString().startsWith("@sun")) { log.error(specsmods.pos,"jml.missing.annotation",a); } } } } public void checkFieldMatch(JmlVariableDecl javaField, VarSymbol javaSym, JmlVariableDecl specField) { if (javaField == specField) return; // Presume that we can't get here unless the names are the same JavaFileObject prev = log.currentSourceFile(); log.useSource(specField.sourcefile); // Check that the modifiers are the same long javaFlags = javaSym.flags(); long specFlags = specField.mods.flags; boolean isInterface = (javaFlags & Flags.INTERFACE) != 0; long diffs = (javaFlags ^ specFlags)&(isInterface? Flags.InterfaceVarFlags : Flags.VarFlags); if (diffs != 0) { log.error(specField.pos(),"jml.mismatched.field.modifiers", specField.name, javaSym.enclClass().getQualifiedName()+"."+javaSym.name,Flags.toString(diffs)); // FIXME - test this } // check for no initializer if (specField.getInitializer() != null && specField != javaField && !utils.isJML(specField.mods) && !specField.sym.owner.isEnum()) { log.error(specField.getInitializer().pos(),"jml.no.initializer.in.specs",javaSym.enclClass().getQualifiedName()+"."+javaSym.name); } // Match in the types is checked in JmlAttr.checkVarMods // FIXME - attribAnnotations, compare annotations // FIXME _ this needs to be implements // FIXME - what if an annotation is declared within the class? // attr.attribAnnotationTypes(javaField.mods.annotations, env); // checkSameAnnotations(javaField.mods,specField.mods); log.useSource(prev); } public void checkTypeMatch(JmlClassDecl javaDecl, JmlClassDecl specsClassDecl) { ClassSymbol javaClassSym = javaDecl.sym; JmlSpecs.TypeSpecs combinedTypeSpecs = specs.get(javaClassSym); // If these are the same declaration we don't need to check // that the spec decl matches the java decl //if (javaDecl == specsClassDecl) return; // Check annotations if (javaDecl != specsClassDecl) { // Check that modifiers are the same long matchf = javaClassSym.flags(); long specf = combinedTypeSpecs.modifiers.flags; long diffs = (matchf ^ specf)&Flags.ClassFlags; // Includes whether both are class or both are interface if (diffs != 0) { boolean isInterface = (matchf & Flags.INTERFACE) != 0; boolean isEnum = (matchf & Flags.ENUM) != 0; if ((Flags.ABSTRACT & matchf & ~specf) != 0 && isInterface) diffs &= ~Flags.ABSTRACT; if ((Flags.STATIC & matchf & ~specf) != 0 && isEnum) diffs &= ~Flags.STATIC; if ((Flags.FINAL & matchf & ~specf) != 0 && isEnum) diffs &= ~Flags.FINAL; if (diffs != 0) log.error(specsClassDecl.pos(),"jml.mismatched.modifiers", specsClassDecl.name, javaClassSym.fullname, Flags.toString(diffs)); // FIXME - test this // FIXME - how can we tell where in which specs file the mismatched modifiers are // SHould probably check this in the combining step } // FIXME - this is needed, but it is using the environment from the java class, not the // spec class, and so it is using the import statements in the .java file, not those in the .jml file attr.attribAnnotationTypes(specsClassDecl.mods.annotations, baseEnv(javaDecl,env)); // FIXME - this is done later; is it needed here? JavaFileObject prev = log.useSource(specsClassDecl.source()); checkSameAnnotations(javaDecl.mods,specsClassDecl.mods,javaDecl.source()); log.useSource(prev); // FIXME - check that both are Enum; check that both are Annotation } if (specsClassDecl.source() == null || specsClassDecl.source().getKind() == JavaFileObject.Kind.SOURCE){ // This is already checked in enterTypeParametersForBinary (for binary classes) List<Type> t = ((Type.ClassType)javaClassSym.type).getTypeArguments(); List<JCTypeParameter> specTypes = specsClassDecl.typarams; if (t.size() != specTypes.size()) { log.error(specsClassDecl.pos(),"jml.mismatched.type.arguments",javaClassSym.fullname,javaClassSym.type.toString()); } // FIXME - check that the names and bounds are the same } } public void checkTypeMatch(ClassSymbol javaClassSym, JmlClassDecl specsClassDecl) { // Check annotations JmlSpecs.TypeSpecs combinedTypeSpecs = specs.get(javaClassSym); JavaFileObject prev = log.useSource(specsClassDecl.source()); { // Check that modifiers are the same long matchf = javaClassSym.flags(); long specf = combinedTypeSpecs.modifiers.flags; long diffs = (matchf ^ specf)&Flags.ClassFlags; // Includes whether both are class or both are interface if (diffs != 0) { boolean isInterface = (matchf & Flags.INTERFACE) != 0; boolean isEnum = (matchf & Flags.ENUM) != 0; if ((Flags.ABSTRACT & matchf & ~specf) != 0 && isInterface) diffs &= ~Flags.ABSTRACT; if ((Flags.STATIC & matchf & ~specf) != 0 && isEnum) diffs &= ~Flags.STATIC; if ((Flags.FINAL & matchf & ~specf) != 0 && isEnum) diffs &= ~Flags.FINAL; if ((diffs & Flags.FINAL) != 0 && javaClassSym.isAnonymous()) diffs &= ~Flags.FINAL; if (diffs != 0) log.error(specsClassDecl.pos(),"jml.mismatched.modifiers", specsClassDecl.name, javaClassSym.fullname, Flags.toString(diffs)); // FIXME - test this } // FIXME - check that both are Enum; check that both are Annotation checkSameAnnotations(javaClassSym,specsClassDecl.mods,prev); // FIXME - is prev the java source? } { List<Type> t = ((Type.ClassType)javaClassSym.type).getTypeArguments(); List<JCTypeParameter> specTypes = specsClassDecl.typarams; if (t.size() != specTypes.size()) { log.error(specsClassDecl.pos(),"jml.mismatched.type.arguments",javaClassSym.fullname,javaClassSym.type.toString()); } // FIXME - check that the names and bounds are the same } log.useSource(prev); } /** Find the method symbol in the class csym that corresponds to the method declared as specMethod; * if complain is true, then an error is reported if there is no match. * Does not presume that the parameter and return types and annotations have been attributed. * Presumes that specMethod.sym == null unless specMethod is part of the JmlClassDecl in the Java declaration. */ public MethodSymbol matchMethod(JmlMethodDecl specMethod, ClassSymbol csym, Env<AttrContext> env, boolean complain) { JCMethodDecl tree = specMethod; MethodSymbol msym = tree.sym; MethodSymbol mtemp = msym; Type computedResultType = null; Env<AttrContext> localEnv = null; if (msym != null) { localEnv = methodEnv(tree, env); computedResultType = msym.getReturnType(); } else { // Copied from MemberEnter.visitMethodDef which can't be called directly Scope enclScope = enter.enterScope(env); mtemp = new MethodSymbol(0, tree.name, null, enclScope.owner); //m.flags_field = chk.checkFlags(tree.pos(), tree.mods.flags, m, tree); tree.sym = mtemp; localEnv = methodEnv(tree, env); // Compute the method type mtemp.type = signature(msym, tree.typarams, tree.params, tree.restype, tree.recvparam, tree.thrown, localEnv); computedResultType = mtemp.type.getReturnType(); // Set m.params ListBuffer<VarSymbol> params = new ListBuffer<VarSymbol>(); JCVariableDecl lastParam = null; for (List<JCVariableDecl> l = tree.params; l.nonEmpty(); l = l.tail) { JCVariableDecl param = lastParam = l.head; assert param.sym != null; params.append(param.sym); } mtemp.params = params.toList(); // mark the method varargs, if necessary if (lastParam != null && (lastParam.mods.flags & Flags.VARARGS) != 0) mtemp.flags_field |= Flags.VARARGS; localEnv.info.scope.leave(); // if (chk.checkUnique(tree.pos(), m, enclScope)) { // enclScope.enter(m); // } annotateLater(tree.mods.annotations, localEnv, mtemp, tree.pos()); // FIXME - is this the right position if (tree.defaultValue != null) annotateDefaultValueLater(tree.defaultValue, localEnv, mtemp); } MethodSymbol match = null; ListBuffer<Type> typaramTypes = new ListBuffer<Type>(); for (List<JCTypeParameter> l = tree.typarams; l.nonEmpty(); l = l.tail) { typaramTypes.append(l.head.type); } ListBuffer<Type> paramTypes = new ListBuffer<Type>(); JCVariableDecl lastParam = null; for (List<JCVariableDecl> l = tree.params; l.nonEmpty(); l = l.tail) { JCVariableDecl param = lastParam = l.head; paramTypes.append(param.vartype.type); } // JmlResolve.findMethod is designed for matching a method call to some // declaration. Here however, we are trying to match to method signatures. // We use this as a start, but then have to check that we have exact matches // for parameter types. Also, we make the match using varargs=false - // the parameter types themselves are already arrays if they were declared // as varargs parameters. // Symbol lookupMethod(Env<AttrContext> env, DiagnosticPosition pos, Symbol location, MethodCheck methodCheck, LookupHelper lookupHelper) { Symbol s; JmlResolve jmlResolve = JmlResolve.instance(context); boolean prevSilentErrors = jmlResolve.silentErrors; jmlResolve.silentErrors = true; jmlResolve.errorOccurred = false; try { s = jmlResolve.resolveMethod(tree.pos(), localEnv, tree.name, paramTypes.toList(),typaramTypes.toList()); } finally { jmlResolve.silentErrors = prevSilentErrors; if (jmlResolve.errorOccurred) s = null; } // Symbol s = JmlResolve.instance(context).findMethod(env,csym.asType(), // tree.name,paramTypes.toList(),typaramTypes.toList(), // /*allowBoxing*/false,/*varargs*/false,/*is an operator*/false); if (s instanceof MethodSymbol) { match = (MethodSymbol)s; // Require exact type match [findMethod returns best matches ] List<VarSymbol> params = match.getParameters(); List<Type> paramT = paramTypes.toList(); Types types = Types.instance(context); boolean hasTypeArgs = !typaramTypes.isEmpty(); while (params.nonEmpty()) { if (!types.isSameType(params.head.type,paramT.head) && // FIXME - this is a hack to cover lots of cases // We actually need to map type arguments in order to compare for eqauality with isSameType (paramT.head.isPrimitive())) { match = null; break; } params = params.tail; paramT = paramT.tail; } } if (msym == null && match != null) { tree.sym = match; if (localEnv != null) localEnv.info.scope.owner = match; for (List<JCVariableDecl> l = tree.params; l.nonEmpty(); l = l.tail) { JCVariableDecl param = l.head; param.sym.owner = match; } } if (match == null) { if (complain && (specMethod.mods.flags & Flags.GENERATEDCONSTR) == 0 && !inModelTypeDeclaration && utils.findMod(specMethod.mods,JmlTokenKind.MODEL) == null) { log.error(specMethod.pos(),"jml.no.method.match", csym.flatName() + "." + mtemp); } } else { // FIXME - do we need to check for model methods, and that they match? // boolean isModel = JmlAttr.instance(context).findMod(specMethod.mods,JmlToken.MODEL) != null; // boolean isMatchModel = match.attribute(((JmlAttr)attr).tokenToAnnotationSymbol.get(JmlToken.MODEL)) != null; // if (isModel == isMatchModel) { // Attributes the annotations and adds them to the given MethodSymbol, if they are not already present // addAnnotations(match,env,specMethod.mods); // Might repeat annotations, so we use the conditional call // FIXME - we aren't using the conditional call // } else { // // We have a model and non-model method with matching signatures. Declare them // // non-matching and wait for an error when the model method is entered. // match = null; // } } localEnv.info.scope.leave(); return match; } /////////////////// DON'T USE BUT REVIEW FOR ACTIONS THAT HAVE BEEN FORGOTTEN ///////////////////////// // public MethodSymbol matchMethod(JmlMethodDecl specMethod, ClassSymbol javaClassSymbol) { // // FIXME - set env properly for the following call? Is it really attribBOunds? // // Env<AttrContext> localenv = Enter.instance(context).getEnv(javaClassSymbol); // //Env<AttrContext> localenv = methodEnv(specMethod, env); // // //Scope enclScope = enter.enterScope(env); //// MethodSymbol m = new MethodSymbol(0, specMethod.name, null, javaClassSymbol); //// m.flags_field = specMethod.mods.flags; //// specMethod.sym = m; //// Env<AttrContext> localEnv = methodEnv(specMethod, env); // // Env<AttrContext> prevEnv = env; // env = localenv; // // Attr attr = Attr.instance(context); // // // Enter and attribute type parameters. // { // From Enter.visitTypeParameter // for (JCTypeParameter tree: specMethod.typarams) { // TypeVar a = (tree.type != null) // ? (TypeVar)tree.type // : new TypeVar(tree.name, env.info.scope.owner, syms.botType); // tree.type = a; // //if (Check.instance(context).checkUnique(tree.pos(), a.tsym, localenv.info.scope)) { // env.info.scope.enter(a.tsym); // //} // } // } // attr.attribTypeVariables(specMethod.typarams, localenv); // // ListBuffer<Type> tatypes = ListBuffer.<Type>lb(); // for (JCTypeParameter tp: specMethod.typarams) { // tatypes.append(tp.type); // } // // // attribute value parameters. // int n = specMethod.getParameters().size(); // ListBuffer<Type> ptypes = ListBuffer.<Type>lb(); // for (List<JCVariableDecl> l = specMethod.params; l.nonEmpty(); l = l.tail) { // attr.attribType(l.head.vartype,javaClassSymbol); // ptypes.append(l.head.vartype.type); // } // // // Attribute result type, if one is given. // if (specMethod.restype != null) attr.attribType(specMethod.restype, env); // // // Attribute thrown exceptions. // ListBuffer<Type> thrownbuf = new ListBuffer<Type>(); // for (List<JCExpression> l = specMethod.thrown; l.nonEmpty(); l = l.tail) { // Type exc = attr.attribType(l.head, env); //// if (exc.tag != TYPEVAR) //// FIXME exc = chk.checkClassType(l.head.pos(), exc); // thrownbuf.append(exc); // } // //// int n = specMethod.getParameters().size(); //// for (int i=0; i<n; i++) { //// // FIXME - should the following use getEnv, getClassEnv? should it use the env of the javaClassSymbol or the spec decl? //// Attr.instance(context).attribType(specMethod.getParameters().get(i).vartype, localenv); //// } // boolean hasTypeParameters = specMethod.getTypeParameters().size() != 0; // MethodSymbol match = null; // try { // if (utils.jmldebug) { // log.noticeWriter.println(" CLASS " + javaClassSymbol.name + " SPECS HAVE METHOD " + specMethod.name); // if (specMethod.name.toString().equals("equals")) { // log.noticeWriter.println(" CLASS " + javaClassSymbol.name + " SPECS HAVE METHOD " + specMethod.name); // } // } // JmlResolve rs = (JmlResolve)Resolve.instance(context); // try { // rs.noSuper = true; // Symbol sym = rs.findMatchingMethod(specMethod.pos(), env, specMethod.name, ptypes.toList(), tatypes.toList()); // if (sym instanceof MethodSymbol) { // match = (MethodSymbol)sym; // } else if (sym == null) { // match = null; // } else { // log.warning("jml.internal","Match found was not a method: " + sym + " " + sym.getClass()); // return null; // } // } finally { // rs.noSuper = false; // } // //// Entry e = javaClassSymbol.members().lookup(specMethod.name); //// loop: while (true) { //// //if (e.sym != null && e.sym.kind != Kinds.MTH && e.sym.owner == javaClassSymbol) e = e.next(); //// //if (!(e.sym != null && e.sym.owner == javaClassSymbol)) break; //// // Allow to match inherited methods //// if (e.sym != null && e.sym.kind != Kinds.MTH) e = e.next(); //// if (e.sym == null) break; //// MethodSymbol javaMethod = (Symbol.MethodSymbol)e.sym; //// if (javaMethod.getParameters().size() != specMethod.getParameters().size()) { e = e.next(); continue; } //// if (javaMethod.getTypeParameters().size() != specMethod.getTypeParameters().size()) { e = e.next(); continue; } //// n = javaMethod.getParameters().size(); //// if (!hasTypeParameters) for (int i=0; i<n; i++) { // FIXME - need to do actual matching for parameters with types //// if (!Types.instance(context).isSameType(javaMethod.getParameters().get(i).type,specMethod.getParameters().get(i).vartype.type)) { e = e.next(); continue loop; } //// } //// match = javaMethod; //// break; //// } //// if (match == null && javaClassSymbol.isInterface()) { //// // Check for a match against Object methods //// e = Symtab.instance(context).objectType.tsym.members().lookup(specMethod.name); //// loop: while (true) { //// //if (e.sym != null && e.sym.kind != Kinds.MTH && e.sym.owner == javaClassSymbol) e = e.next(); //// //if (!(e.sym != null && e.sym.owner == javaClassSymbol)) break; //// // Allow to match inherited methods //// if (e.sym != null && e.sym.kind != Kinds.MTH) e = e.next(); //// if (e.sym == null) break; //// MethodSymbol javaMethod = (Symbol.MethodSymbol)e.sym; //// if (javaMethod.getParameters().size() != specMethod.getParameters().size()) { e = e.next(); continue; } //// if (javaMethod.getTypeParameters().size() != specMethod.getTypeParameters().size()) { e = e.next(); continue; } //// // FIXME - need to check that type parameters have the same names and put them in scope so that we can test whether the //// // parameters have the same type; also check the bounds //// int n = javaMethod.getParameters().size(); //// for (int i=0; i<n; i++) { //// if (!Types.instance(context).isSameType(javaMethod.getParameters().get(i).type,specMethod.getParameters().get(i).vartype.type)) { e = e.next(); continue loop; } //// } //// match = javaMethod; //// break; //// } //// } // // if (match == null) { // // // Make a string of the signatures of the Java methods that we are comparing against // // and that do not match, to make a nice error message // StringBuilder sb = new StringBuilder(); // sb.append("\n Signatures found:"); // int len = sb.length(); // Entry e = javaClassSymbol.members().lookup(specMethod.name); // while (sb.length() < 500) { // //if (e.sym != null && e.sym.kind != Kinds.MTH && e.sym.owner == javaClassSymbol) e = e.next(); // //if (!(e.sym != null && e.sym.owner == javaClassSymbol)) break; // // Allow to match inherited methods // if (e.sym != null && e.sym.kind != Kinds.MTH) e = e.next(); // if (e.sym == null) break; // MethodSymbol javaMethod = (Symbol.MethodSymbol)e.sym; // sb.append("\n\t\t\t").append(javaMethod.toString()); // e = e.next(); // } // if (sb.length() >= 500) sb.append(" ....."); // if (len == sb.length()) sb.append(" <none>"); // log.error(specMethod.pos(),"jml.no.method.match", // javaClassSymbol.fullname + "." + specMethod.name, // sb.toString()); // } else { // checkMethodMatch(match,specMethod,javaClassSymbol); // specMethod.sym = match; // Env<AttrContext> localEnv = methodEnv(specMethod, env); // // FIXME - are the annotations attributed? // //attr.attribAnnotationTypes(specMethod.mods.annotations,localenv); // addAnnotations(match,localEnv,specMethod.mods); // for (int i = 0; i<specMethod.typarams.size(); i++) { // specMethod.typarams.get(i).type = match.getTypeParameters().get(i).type; // } // } // } catch (Exception e) { // log.noticeWriter.println("METHOD EXCEOPTION " + e); // } // env = prevEnv; // return match; // } /** Checks that the modifiers and annotations in the .java and .jml declarations match appropriately, * for both the method declaration and any parameter declarations; * does not do any semantic checks of whether the modifiers or annotations are allowed. */ public void checkMethodMatch(@Nullable JmlMethodDecl javaMatch, MethodSymbol match, JmlMethodDecl specMethodDecl, ClassSymbol javaClassSymbol) { JavaFileObject prev = log.currentSourceFile(); log.useSource(specMethodDecl.sourcefile); // All logged errors are with respect to positions in the jml file try { if (javaMatch != specMethodDecl) { boolean isInterface = match.owner.isInterface(); // Check that modifiers are the same long matchf = match.flags(); long specf = specMethodDecl.mods.flags; matchf |= (specf & Flags.SYNCHRONIZED); // binary files do not seem to always have the synchronized modifier? FIXME long diffs = (matchf ^ specf)&Flags.MethodFlags; if (diffs != 0) { boolean isEnum = (javaClassSymbol.flags() & Flags.ENUM) != 0; if ((Flags.NATIVE & matchf & ~specf)!= 0) diffs &= ~Flags.NATIVE; if (isInterface) diffs &= ~Flags.PUBLIC & ~Flags.ABSTRACT; if (isEnum && match.isConstructor()) { specMethodDecl.mods.flags |= (matchf & 7); diffs &= ~7; } // FIXME - should only do this if specs are default if ((matchf & specf & Flags.ANONCONSTR)!= 0 && isEnum) { diffs &= ~2; specMethodDecl.mods.flags |= 2; } // enum constructors can have differences if (diffs != 0 && !(match.isConstructor() && diffs == 3)) { // FIXME - hide this case for now because of default constructors in binary files log.error(specMethodDecl.pos(),"jml.mismatched.method.modifiers", specMethodDecl.name, match.toString(), Flags.toString(diffs)); } } } if (javaMatch != null) { // Check that parameters have the same modifiers - FIXME - should check this in the symbol, not just in the Java Iterator<JCVariableDecl> javaiter = javaMatch.params.iterator(); Iterator<JCVariableDecl> jmliter = specMethodDecl.params.iterator(); while (javaiter.hasNext() && jmliter.hasNext()) { JmlVariableDecl javaparam = (JmlVariableDecl)javaiter.next(); JmlVariableDecl jmlparam = (JmlVariableDecl)jmliter.next(); javaparam.specsDecl = jmlparam; jmlparam.sym = javaparam.sym; long diffs = (javaparam.mods.flags ^ jmlparam.mods.flags); if (diffs != 0) { utils.errorAndAssociatedDeclaration(specMethodDecl.sourcefile, jmlparam.pos(), javaMatch.sourcefile, javaparam.pos(), "jml.mismatched.parameter.modifiers", jmlparam.name, javaClassSymbol.getQualifiedName()+"."+match.name,Flags.toString(diffs)); } } // FIXME - should check names of parameters, names of type parameters if (javaiter.hasNext() || jmliter.hasNext()) { log.error("jml.internal", "Java and jml declarations have different numbers of arguments, even though they have been type matched"); } } // FIXME - we do need to exclude some anonymous classes, but all of them? if (!javaClassSymbol.isAnonymous()) checkSameAnnotations(match,specMethodDecl.mods,prev); // FIXME - is prev really the file object for Java Iterator<JCVariableDecl> jmliter = specMethodDecl.params.iterator(); Iterator<Symbol.VarSymbol> javaiter = match.getParameters().iterator(); while (javaiter.hasNext() && jmliter.hasNext()) { Symbol.VarSymbol javaparam = javaiter.next(); JmlVariableDecl jmlparam = (JmlVariableDecl)jmliter.next(); checkSameAnnotations(javaparam,jmlparam.mods,prev); // FIXME - is prev really the file object for Java } // Check that the return types are the same if (specMethodDecl.restype != null) { // not a constructor if (specMethodDecl.restype.type == null) Attr.instance(context).attribType(specMethodDecl.restype, match.enclClass()); // if (match.name.toString().equals("defaultEmpty")) { // log.noticeWriter.println(match.name); // } Type javaReturnType = match.type.getReturnType(); Type specReturnType = specMethodDecl.restype.type; if (!Types.instance(context).isSameType(javaReturnType,specReturnType)) { // FIXME - when the result type is parameterized in a static method, the java and spec declarations // end up with different types for the parameter. Is this also true for the regular parameters? // FIXME - avoud the probloem for now. if (!(specReturnType instanceof Type.TypeVar) && specReturnType.getTypeArguments().isEmpty() && (!(specReturnType instanceof Type.ArrayType) || !(((Type.ArrayType)specReturnType).elemtype instanceof Type.TypeVar)) ) log.error(specMethodDecl.restype.pos(),"jml.mismatched.return.type", match.enclClass().fullname + "." + match.toString(), specReturnType, javaReturnType); } } // Check that parameter names are the same (a JML requirement to avoid having to rename within specs) if (javaMatch != null) { for (int i = 0; i<javaMatch.getParameters().size(); i++) { JCTree.JCVariableDecl javaparam = javaMatch.getParameters().get(i); JCTree.JCVariableDecl jmlparam = specMethodDecl.params.get(i); if (!javaparam.name.equals(jmlparam.name)) { log.error(jmlparam.pos(),"jml.mismatched.param.names",i, match.enclClass().fullname + "." + match.toString(), javaparam.name, jmlparam.name); } } } else if (JmlCompilationUnit.isForSource(modeOfFileBeingChecked)) { for (int i = 0; i<match.getParameters().size(); i++) { Symbol.VarSymbol javasym = match.getParameters().get(i); JCTree.JCVariableDecl jmlparam = specMethodDecl.params.get(i); if (!javasym.name.equals(jmlparam.name)) { log.error(jmlparam.pos(),"jml.mismatched.param.names",i, match.enclClass().fullname + "." + match.toString(), javasym.name, jmlparam.name); } } } // Check that the specification method has no body if it is not a .java file if (specMethodDecl.body != null && specMethodDecl.sourcefile.getKind() != Kind.SOURCE && !((JmlAttr)attr).isModel(specMethodDecl.mods) && !inModelTypeDeclaration && match.owner == javaClassSymbol // FIXME - this is here to avoid errors on methods of anonymous classes within specifications within a .jml file - it might not be fully robust // FIXME - should test other similar locations - e.g. model classes, model methods, methods within local class declarations in model methods or methods of model classes && (specMethodDecl.mods.flags & (Flags.GENERATEDCONSTR|Flags.SYNTHETIC)) == 0) { log.error(specMethodDecl.body.pos(),"jml.no.body.allowed",match.enclClass().fullname + "." + match.toString()); } // FIXME - from a previous comparison against source // // A specification method may not have a body. However, the spec // // method declaration may also be identical to the java method (if the // // java file is in the specification sequence) - hence the second test. // // There is an unusual case in which a method declaration is duplicated // // in a .java file (same signature). In that case, there is already // // an error message, but the duplicate will be matched against the // // first declaration at this point, though they are different // // delcarations (so the second test will be true). Hence we include the // // 3rd test as well. [ TODO - perhaps we need just the third test and not the second.] // if (specMethodDecl.body != null && match != specMethodDecl // && match.sourcefile != specMethodDecl.sourcefile // && (specMethodDecl.mods.flags & (Flags.GENERATEDCONSTR|Flags.SYNTHETIC)) == 0) { // log.error(specMethodDecl.body.pos(),"jml.no.body.allowed",match.sym.enclClass().fullname + "." + match.sym.toString()); // } // // // Check that the return types are the same // if (specMethodDecl.restype != null) { // not a constructor // if (specMethodDecl.restype.type == null) Attr.instance(context).attribType(specMethodDecl.restype, match.sym.enclClass()); //// if (match.name.toString().equals("defaultEmpty")) { //// log.noticeWriter.println(match.name); //// } // if (!Types.instance(context).isSameType(match.restype.type,specMethodDecl.restype.type)) { // // FIXME - when the result type is parameterized in a static method, the java and spec declarations // // end up with different types for the parameter. Is this also true for the regular parameters? // // FIXME - avoud the probloem for now. // if (!(specMethodDecl.restype.type.getTypeArguments().head instanceof Type.TypeVar)) // log.error(specMethodDecl.restype.pos(),"jml.mismatched.return.type", // match.sym.enclClass().fullname + "." + match.sym.toString(), // specMethodDecl.restype.type,match.restype.type); // } // } } finally { log.useSource(prev); } // FIXME - what about covariant return types ????? // FIXME - check that JML annotations are ok } /** Attributes the annotations and adds them to the given Symbol, if they are not already present */ public void addAnnotations(Symbol sym, Env<AttrContext> env, JCTree.JCModifiers mods) { if (env == null) { log.error("jml.internal","Unexpected NULL ENV in JmlMemberEnter.addAnnotations" + sym); } annotateLaterConditional(mods.annotations, env, sym, mods.pos()); // FIXME - is this an OK position? } VarSymbol findVarMatch(ClassSymbol csym, Name name) { Scope.Entry e = csym.members().lookup(name); // FIXME - can have variables and methods with the same name while (e.sym != null && !(e.sym instanceof VarSymbol)) { e = e.next(); } return (VarSymbol)e.sym; } /** When we are handling the specs for a binary file, we have the situation * of performing an annotation given in the source that has already been * performed in loading the binary. Thus we don't give an error about this. * However we don't know that all of the annotations are already present * (presumably just the ones retained in the class file are present), so we * proceed to do them anyway, at the risk of repeating some. Repeating the * work does not appear to do any harm, though it may be that we should check * for those annotations already present and not repeat them. (TODO) * @param annotations * @param localEnv * @param s */ // MAINTENANCE - modified from MemberEnter.annotateLater // FIXME - currently the same as in the super class void annotateLaterConditional(final List<JCAnnotation> annotations, final Env<AttrContext> localEnv, final Symbol s, final DiagnosticPosition deferPos) { if (annotations.isEmpty()) return; if (s.kind != PCK) { s.resetAnnotations(); // mark Annotations as incomplete for now } annotate.normal(new Annotate.Worker() { @Override public String toString() { return "annotate " + annotations + " onto " + s + " in " + s.owner; } @Override public void run() { // Assert.check(s.kind == PCK || s.annotationsPendingCompletion()); // Removed check - why? JavaFileObject prev = log.useSource(localEnv.toplevel.sourcefile); DiagnosticPosition prevLintPos = deferPos != null ? deferredLintHandler.setPos(deferPos) : deferredLintHandler.immediate(); Lint prevLint = deferPos != null ? null : chk.setLint(lint); try { if (!(s.hasAnnotations() && annotations.nonEmpty())) actualEnterAnnotations(annotations, localEnv, s); // else if (!(annotations.head.type instanceof Type.ErrorType) ) // log.error(annotations.head.pos, // "already.annotated", // kindName(s), s); } finally { if (prevLint != null) chk.setLint(prevLint); deferredLintHandler.setPos(prevLintPos); log.useSource(prev); } } }); annotate.validate(new Annotate.Worker() { //validate annotations @Override public void run() { JavaFileObject prev = log.useSource(localEnv.toplevel.sourcefile); try { chk.validateAnnotations(annotations, s); } finally { log.useSource(prev); } } }); } // annotate.later(new Annotate.Annotator() { // public String toString() { // return "conditional annotate " + annotations + " onto " + s + " in " + s.owner; // } // public void enterAnnotation() { // assert s.kind == PCK || s.attributes_field == null; // FIXME - SF patch # says this assert triggers incorrectly when -ea option is used // JavaFileObject prev = log.useSource(localEnv.toplevel.sourcefile); // try { // if (s.attributes_field != null && // s.attributes_field.nonEmpty() && // annotations.nonEmpty()) { //// log.error(annotations.head.pos, //// "already.annotated", //// kindName(s), s); // } else enterAnnotations(annotations, localEnv, s); // } finally { // log.useSource(prev); // } // } // }); // } // // void annotateLater(final List<JCAnnotation> annotations, // final Env<AttrContext> localEnv, // final Symbol s) { // annotateLaterConditional(annotations,localEnv,s); // } /** This inherited method is overridden to do an automatic import of org.jmlspecs.lang.* */ // @Override // public void visitTopLevel(JCTree.JCCompilationUnit tree) { // if (tree.starImportScope.elems == null) { // Check if already done // super.visitTopLevel(tree); // // Import-on-demand org.jmlspecs.lang. // importAll(tree.pos, ClassReader.instance(context).enterPackage(org_jmlspecs_lang), env); // } // } /** Set in visitiMethodDef so that all chlidren can know which method they are part of */ JmlMethodDecl currentMethod = null; @Override public void visitMethodDef(JCMethodDecl tree) { JmlMethodDecl prevMethod = currentMethod; currentMethod = (JmlMethodDecl) tree; boolean prevAllowJml = resolve.allowJML(); long flags = tree.mods.flags; boolean isJMLMethod = utils.isJML(flags); try { boolean isSpecForBinary = modeOfFileBeingChecked == JmlCompilationUnit.SPEC_FOR_BINARY; boolean isSpecFile = currentMethod.sourcefile == null || currentMethod.sourcefile.getKind() != JavaFileObject.Kind.SOURCE; // boolean isClassModel = ((JmlAttr)attr).isModel(env.enclClass.mods); if (isSpecFile && tree.sym != null) return; //Sometimes this is called when the method already is entered if (isJMLMethod) resolve.setAllowJML(true); super.visitMethodDef(tree); // if (!isSpecFile) super.visitMethodDef(tree); // if (isSpecFile) visitMethodDefBinary(tree); } finally { if (isJMLMethod) resolve.setAllowJML(prevAllowJml); currentMethod = prevMethod; } } public void visitMethodDef(JmlMethodDecl tree, ClassSymbol owner) { JmlMethodDecl prevMethod = currentMethod; currentMethod = (JmlMethodDecl) tree; boolean prevAllowJml = resolve.allowJML(); long flags = tree.mods.flags; boolean isJMLMethod = utils.isJML(flags); try { boolean isSpecFile = currentMethod.sourcefile == null || currentMethod.sourcefile.getKind() != JavaFileObject.Kind.SOURCE; // boolean isClassModel = ((JmlAttr)attr).isModel(env.enclClass.mods); if (isSpecFile && tree.sym != null) return; //Sometimes this is called when the method already is entered if (isJMLMethod) resolve.setAllowJML(true); //super.visitMethodDef(tree); visitMethodDefBinary(tree); } finally { if (isJMLMethod) resolve.setAllowJML(prevAllowJml); currentMethod = prevMethod; } } // This is a duplicate of super.vistMethodDef -- with some stuff elided for handling specs of binarys public void visitMethodDefBinary(JCMethodDecl tree) { Scope enclScope = enter.enterScope(env); MethodSymbol m = new MethodSymbol(0, tree.name, null, enclScope.owner); m.flags_field = chk.checkFlags(tree.pos(), tree.mods.flags, m, tree); tree.sym = m; //if this is a default method, add the DEFAULT flag to the enclosing interface if ((tree.mods.flags & DEFAULT) != 0) { m.enclClass().flags_field |= DEFAULT; } Env<AttrContext> localEnv = methodEnv(tree, env); DiagnosticPosition prevLintPos = deferredLintHandler.setPos(tree.pos()); try { // Compute the method type m.type = signature(m, tree.typarams, tree.params, tree.restype, tree.recvparam, tree.thrown, localEnv); } finally { deferredLintHandler.setPos(prevLintPos); } if (types.isSignaturePolymorphic(m)) { m.flags_field |= SIGNATURE_POLYMORPHIC; } // Set m.params ListBuffer<VarSymbol> params = new ListBuffer<VarSymbol>(); JCVariableDecl lastParam = null; for (List<JCVariableDecl> l = tree.params; l.nonEmpty(); l = l.tail) { JCVariableDecl param = lastParam = l.head; params.append(Assert.checkNonNull(param.sym)); } m.params = params.toList(); // mark the method varargs, if necessary if (lastParam != null && (lastParam.mods.flags & Flags.VARARGS) != 0) m.flags_field |= Flags.VARARGS; localEnv.info.scope.leave(); boolean prevCheck = ((JmlCheck)chk).noDuplicateWarn; ((JmlCheck)chk).noDuplicateWarn = true; if (chk.checkUnique(tree.pos(), m, enclScope)) { // Not a duplicate - OK if the declaration is JML - if not, then ignore it if (!utils.isJML(m.flags())) { // This is an error, but it is reported later //utils.error(((JmlMethodDecl)tree).sourcefile, tree, "jml.no.method.match", enclScope.owner.flatName() + "." + m); } else { enclScope.enter(m); } } else { // A duplicate - OK if the declaration is not JML if (utils.isJML(m.flags())) { // FIXME } } ((JmlCheck)chk).noDuplicateWarn = prevCheck; annotateLater(tree.mods.annotations, localEnv, m, tree.pos()); // Visit the signature of the method. Note that // TypeAnnotate doesn't descend into the body. typeAnnotate(tree, localEnv, m, tree.pos()); if (tree.defaultValue != null) annotateDefaultValueLater(tree.defaultValue, localEnv, m); } // public void visitBlock(JCTree.JCBlock that) { // super.visitBlock(that); // if (inSpecFile && currentMethod == null && !utils.isJML(currentClass.mods)) { // // initializer block is not allowed in a specification, unless it is a model class // log.error(that.pos,"jml.initializer.block.allowed"); // } // } // // TODO - review this // // Duplicated from MemberEnter because it is declared private // protected void importAll(int pos, // final TypeSymbol tsym, // Env<AttrContext> env) { //// Check that packages imported from exist (JLS ???). // if (tsym.kind == PCK && tsym.members().elems == null && !tsym.exists()) { //// If we can't find java.lang, exit immediately. // if (((PackageSymbol)tsym).fullname.equals(Names.instance(context).java_lang)) { // JCDiagnostic msg = JCDiagnostic.fragment("fatal.err.no.java.lang"); // throw new FatalError(msg); // } else { // log.error(pos, "doesnt.exist", tsym); // } // } // final Scope fromScope = tsym.members(); // final Scope toScope = env.toplevel.starImportScope; // for (Scope.Entry e = fromScope.elems; e != null; e = e.sibling) { // if (e.sym.kind == TYP && !toScope.includes(e.sym)) // toScope.enter(e.sym, fromScope); // } // } /** We override the superclass method in order to add the symbol for 'this' * into the environment for an interface. The javac tool does not because * there is never a need - all expressions are static. However, I have not * done the same for super. (TODO) */ @Override public void complete(Symbol sym) throws CompletionFailure { JmlResolve jresolve = JmlResolve.instance(context); boolean prevAllowJML = jresolve.setAllowJML(utils.isJML(sym.flags())); try { Env<AttrContext> env = enter.typeEnvs.get(sym.type.tsym); if (env == null) { log.error("jml.internal","JmlMemberEnter.complete called with a null env, presumably from a binary class, which should not be the argument of this complete call: " + sym); return; } super.complete(sym); // FIXME - not sure this should be called for binary classes // If this is a specification file then remove any automatically generated constructor if (env.tree instanceof JmlClassDecl) { JmlClassDecl jmltree = (JmlClassDecl)env.tree; if (jmltree.toplevel != null && jmltree.toplevel.mode == JmlCompilationUnit.SPEC_FOR_BINARY && !utils.isJML(jmltree)) { if (jmltree.defs.head instanceof JmlMethodDecl) { JmlMethodDecl md = (JmlMethodDecl)jmltree.defs.head; if ((md.getModifiers().flags & Flags.GENERATEDCONSTR) != 0) { jmltree.defs = jmltree.defs.tail; } } } } // FIXME - this might already be done? // If this is an interface, enter symbol for this into // current scope. ClassSymbol c = (ClassSymbol)sym; if ((c.flags_field & INTERFACE) == INTERFACE) { VarSymbol thisSym = new VarSymbol(FINAL | HASINIT, Names.instance(context)._this, c.type, c); thisSym.pos = Position.FIRSTPOS; env.info.scope.enter(thisSym); } } finally { jresolve.setAllowJML(prevAllowJML); } } /** True when we are processing declarations within a model type; false * otherwise. This is to distinguish behaviors of Java declarations within * model types from those not in model types. */ public boolean inModelTypeDeclaration = false; private boolean isInJml = false; public boolean setInJml(boolean inJml) { boolean b = isInJml; isInJml = inJml; return b; } @Override public boolean visitVarDefIsStatic(JCVariableDecl tree, Env<AttrContext> env) { boolean b = super.visitVarDefIsStatic(tree,env); if (!isInJml && !utils.isJML(tree.mods)) return b; // FIXME - why isn't isInJml enough here - we need the second conjunct for ghost declarations in an interface if ((tree.mods.flags & STATIC) != 0) return true; // In the case where we are in an interface but within a JML expression // we can use type variables. return false; // FIXME - improve this } @Override public void visitVarDef(JCVariableDecl tree) { long flags = tree.mods.flags; boolean wasFinal = (flags&Flags.FINAL) != 0; boolean wasStatic = (flags&Flags.STATIC) != 0; if ((env.enclClass.mods.flags & INTERFACE) != 0 && utils.isJML(tree.mods)) { // FIXME - but the @Instance declaration might be in the .jml file boolean isInstance = JmlAttr.instance(context).findMod(tree.mods,JmlTokenKind.INSTANCE) != null; if (isInstance && !wasStatic) tree.mods.flags &= ~Flags.STATIC; } boolean prev = resolve.allowJML(); if (utils.isJML(tree.mods)) resolve.setAllowJML(true); // boolean prevChk = ((JmlCheck)chk).noDuplicateWarn; // ((JmlCheck)chk).noDuplicateWarn = false; JavaFileObject prevSource = log.useSource( ((JmlVariableDecl)tree).source()); super.visitVarDef(tree); log.useSource(prevSource); // ((JmlCheck)chk).noDuplicateWarn = prevChk; if (utils.isJML(tree.mods)) resolve.setAllowJML(prev); if (tree.sym == null) { // A duplicate return; } Symbol sym = tree.sym; if (specs.getSpecs(tree.sym) != null) log.warning("jml.internal","Expected null field specs here: " + tree.sym.owner + "." + tree.sym); JmlVariableDecl jtree = (JmlVariableDecl)tree; // FIXME - the following duplicates setting the specs with matchAndSetFieldSpecs - but if there is a source file, this comes first JmlSpecs.FieldSpecs fspecs = jtree.fieldSpecs; if (fspecs == null) fspecs = new JmlSpecs.FieldSpecs(jtree); // Does not include any in or maps clauses jtree.fieldSpecsCombined = fspecs; specs.putSpecs(tree.sym,fspecs); if (sym.kind == Kinds.VAR && sym.owner.kind == TYP && (sym.owner.flags_field & INTERFACE) != 0 && utils.isJML(tree.mods)) { // In the case of a JML ghost variable that is a field of an interface, the default is static and not final // (unless explicitly specified final) // FIXME _ the following is not robust because annotations are not attributed yet - test these as well boolean isInstance = utils.findMod(tree.mods,JmlTokenKind.INSTANCE) != null; //boolean isGhost = JmlAttr.instance(context).findMod(tree.mods,JmlToken.GHOST) != null; //boolean isModel = JmlAttr.instance(context).findMod(tree.mods,JmlToken.MODEL) != null; if (isInstance && !wasStatic) tree.sym.flags_field &= ~Flags.STATIC; // FIXME - this duplicates JmlCheck if (!wasFinal) sym.flags_field &= ~FINAL; } } protected void visitFieldDefHelper(JCVariableDecl tree, VarSymbol v, Scope enclScope) { if (chk.checkUnique(tree.pos(), v, enclScope)) { chk.checkTransparentVar(tree.pos(), v, enclScope); if (!noEntering) enclScope.enter(v); } else { tree.sym = null; // An indication that the field is a duplicate and should be removed/ignored } } /** Creates a JCAnnotation tree (without position, source, or type information) from a token; has limited use */ protected JmlTree.JmlAnnotation tokenToAnnotationAST(JmlTokenKind jt) { Class<?> c = jt.annotationType; if (c == null) { log.warning("jml.internal","Expected annotation type to be defined when calling tokenToAnnotationAST"); return null; } // FIXME - this is also repeated code and repeated fixed strings JCExpression t = jmlF.Ident(names.fromString("org")); t = jmlF.Select(t, names.fromString("jmlspecs")); t = jmlF.Select(t, names.fromString("annotation")); t = jmlF.Select(t, names.fromString(c.getSimpleName())); JmlTree.JmlAnnotation ann = jmlF.Annotation(t, List.<JCExpression>nil()); return ann; } protected void enterSpecsForBinaryFields(ClassSymbol parent, JmlVariableDecl specstree) { Name nm = specstree.name; boolean isJML = utils.isJML(specstree.mods); boolean isModel = isJML && utils.findMod(specstree.mods, JmlTokenKind.MODEL) != null; boolean isGhost = isJML && utils.findMod(specstree.mods, JmlTokenKind.GHOST) != null; Scope.Entry e = parent.members().lookup(nm); // Presume there is just one declaration with a matching name, // or at least, that the first one to match is the one we want. Symbol.VarSymbol vsym = e.sym instanceof Symbol.VarSymbol ? (Symbol.VarSymbol)e.sym : null; specstree.sym = vsym; if (vsym == null) { // There is no match of a declaration in the specs file to the Java class // isJML --> OK, but it should be model or ghost (which is checked in JmlAttr.checkVarMods) // !isJML --> error - but we treat it as model or ghost if (!isJML) { JavaFileObject prev = log.currentSourceFile(); log.useSource(specstree.source()); log.error(specstree.pos,"jml.no.var.match",nm.toString()); log.useSource(prev); } Env<AttrContext> prevenv = env; try { env = enter.typeEnvs.get(parent); visitVarDef(specstree); } finally { env = prevenv; } if (specstree.sym == null) log.error(specstree, "jml.internal", "Failed to set a variable declaration symbol as expected"); JmlSpecs.instance(context).putSpecs(specstree.sym, specstree.fieldSpecsCombined); vsym = specstree.sym; } checkFieldMatch(null, specstree.sym, specstree); long flags = specstree.mods.flags; boolean wasFinal = (flags&Flags.FINAL) != 0; boolean wasStatic = (flags&Flags.STATIC) != 0; if ((parent.flags() & INTERFACE) != 0 && utils.isJML(specstree.mods)) { // FIXME - but the @Instance declaration might be in the .jml file boolean isInstance = JmlAttr.instance(context).findMod(specstree.mods,JmlTokenKind.INSTANCE) != null; if (isInstance && !wasStatic) specstree.mods.flags &= ~Flags.STATIC; } if (specs.getSpecs(specstree.sym) != null) log.warning("jml.internal","Expected null field specs here: " + specstree.sym.owner + "." + specstree.sym); specs.putSpecs(specstree.sym,new JmlSpecs.FieldSpecs(specstree)); // This specs only has modifiers - field spec clauses are added later (FIXME - where? why not here?) if (vsym.kind == Kinds.VAR && vsym.owner.kind == TYP && (vsym.owner.flags_field & INTERFACE) != 0 && isJML) { // In the case of a JML ghost variable that is a field of an interface, the default is static and not final // (unless explicitly specified final) // FIXME _ the following is not robust because annotations are not attributed yet - test these as well boolean isInstance = utils.findMod(specstree.mods,JmlTokenKind.INSTANCE) != null; if (isInstance && !wasStatic) specstree.sym.flags_field &= ~Flags.STATIC; // FIXME - this duplicates JmlCheck if (!wasFinal) vsym.flags_field &= ~FINAL; } } // FIXME - ressurrect these checks and corresponding tests // /** Checks that the inheritance relationships in the specification // * declaration match those in the class. Presumes all types have been // * entered and have symbols assigned. // * @param specTypeDeclaration the spec declaration to check // */ // private void checkSpecInheritance(JmlClassDecl specTypeDeclaration) { // // ClassSymbol matchingCSymbol = specTypeDeclaration.sym; // // // Check that the package is correct // if (specTypeDeclaration.toplevel.packge != matchingCSymbol.packge()) { // log.error(specTypeDeclaration.toplevel.pid.pos,"jml.mismatched.package", // TODO _ test this // specTypeDeclaration.toplevel.packge,matchingCSymbol.packge()); // } // // FIXME - use type comparison here // // // Check that the specification has the correct super types // if (!matchingCSymbol.equals(syms.objectType.tsym) && !matchingCSymbol.isInterface()) { // JCTree sup = specTypeDeclaration.extending; // Type suptype = matchingCSymbol.getSuperclass(); // Name s = suptype.tsym.getQualifiedName(); // if (sup == null && !suptype.tsym.equals(syms.objectType.tsym)) { // log.error("jml.missing.spec.superclass",matchingCSymbol.getQualifiedName().toString(),s.toString()); // } else if (sup instanceof JCTree.JCIdent) { // if ( s != null && !s.toString().endsWith(((JCTree.JCIdent)sup).name.toString()) ) { // log.error("jml.incorrect.spec.superclass",matchingCSymbol.getQualifiedName().toString(),((JCTree.JCIdent)sup).name.toString(),s.toString()); // } // } else if (sup instanceof JCTree.JCFieldAccess) { // if ( !s.toString().endsWith(((JCTree.JCFieldAccess)sup).name.toString()) ) { // log.error("jml.incorrect.spec.superclass",matchingCSymbol.getQualifiedName().toString(),((JCTree.JCFieldAccess)sup).name.toString(),s.toString()); // } // } // } // // // Check the interfaces // // List<Type> interfaces = matchingCSymbol.getInterfaces(); // Collection<Type> copy = new LinkedList<Type>(); // for (Type t: interfaces) copy.add(t); // // for (JCTree.JCExpression e : specTypeDeclaration.implementing) { // // FIXME - should match types // Name nm = null; // if (e instanceof JCTree.JCIdent) { // nm = ((JCTree.JCIdent)e).name; // } else if (e instanceof JCTree.JCFieldAccess) { // nm = ((JCTree.JCFieldAccess)e).name; // } else if (e instanceof JCTree.JCTypeApply){ // JCTree.JCExpression ee = e; // while (ee instanceof JCTree.JCTypeApply) ee = ((JCTree.JCTypeApply)ee).clazz; // if (ee instanceof JCTree.JCIdent) nm = ((JCTree.JCIdent)ee).name; // if (ee instanceof JCTree.JCFieldAccess) nm = ((JCTree.JCFieldAccess)ee).name; // } else { // log.noticeWriter.println("UNSUPPORTED IMPLEMENTS TYPE (" + matchingCSymbol + "): " + e.getClass() + " " + e); // // ERROR - FIXME // } // if (nm != null) { // boolean found = false; // java.util.Iterator<Type> iter = copy.iterator(); // while (iter.hasNext()) { // Name nmm = iter.next().tsym.getQualifiedName(); // if (nmm.toString().contains(nm.toString())) { // iter.remove(); // found = true; // break; // } // } // if (!found) { // log.error("jml.missing.spec.interface",matchingCSymbol.getQualifiedName().toString(),nm.toString()); // } // } // } // for (Type t: copy) { // if (t.toString().equals("java.lang.annotation.Annotation") && matchingCSymbol.isInterface()) continue; // log.error("jml.unimplemented.spec.interface",matchingCSymbol.getQualifiedName().toString(),t.toString()); // } // // // FIXME - should do thte above from resolved symbols // // FIXME - need to check modifiers // } // Only used for binary enter // Only used for entering binary classes // FIXME - REVIEW @Override protected void importHelper(JCCompilationUnit tree) { // Import-on-demand java.lang. importAll(tree.pos, reader.enterPackage(names.java_lang), env); importAll(tree.pos, reader.enterPackage(names.fromString(Strings.jmlSpecsPackage)), env); // Process all import clauses. memberEnter(tree.defs, env); } @Override protected void importAll(int pos, final TypeSymbol tsym, Env<AttrContext> env) { if (tsym.kind == PCK && tsym.members().elems == null && !tsym.exists()) { // If we can't find org.jmlspecs.lang, exit immediately. if (((PackageSymbol)tsym).fullname.toString().equals(Strings.jmlSpecsPackage)) { JCDiagnostic msg = diags.fragment("fatal.err.no." + Strings.jmlSpecsPackage); throw new FatalError(msg); } } super.importAll(pos, tsym, env); } }