/*
* 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.ABSTRACT;
import static com.sun.tools.javac.code.Flags.BLOCK;
import static com.sun.tools.javac.code.Flags.INTERFACE;
import static com.sun.tools.javac.code.Flags.NATIVE;
import static com.sun.tools.javac.code.Flags.STATIC;
import static com.sun.tools.javac.code.Flags.STRICTFP;
import static com.sun.tools.javac.code.Flags.SYNTHETIC;
import static com.sun.tools.javac.code.Flags.UNATTRIBUTED;
import static com.sun.tools.javac.code.Kinds.MTH;
import static com.sun.tools.javac.code.Kinds.TYP;
import static com.sun.tools.javac.code.Kinds.VAL;
import static com.sun.tools.javac.code.Kinds.VAR;
import static com.sun.tools.javac.code.TypeTag.FORALL;
import static com.sun.tools.javac.code.TypeTag.METHOD;
import static com.sun.tools.javac.tree.JCTree.Tag.ASSIGN;
import static org.jmlspecs.openjml.JmlTokenKind.*;
import java.util.Collection;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import javax.lang.model.type.TypeKind;
import javax.tools.JavaFileObject;
import org.jmlspecs.annotation.NonNull;
import org.jmlspecs.annotation.Nullable;
import org.jmlspecs.openjml.*;
import org.jmlspecs.openjml.JmlSpecs.MethodSpecs;
import org.jmlspecs.openjml.JmlSpecs.FieldSpecs;
import org.jmlspecs.openjml.JmlSpecs.TypeSpecs;
import org.jmlspecs.openjml.JmlTree.JmlAnnotation;
import org.jmlspecs.openjml.JmlTree.JmlBinary;
import org.jmlspecs.openjml.JmlTree.JmlBlock;
import org.jmlspecs.openjml.JmlTree.JmlChoose;
import org.jmlspecs.openjml.JmlTree.JmlClassDecl;
import org.jmlspecs.openjml.JmlTree.JmlCompilationUnit;
import org.jmlspecs.openjml.JmlTree.JmlDoWhileLoop;
import org.jmlspecs.openjml.JmlTree.JmlEnhancedForLoop;
import org.jmlspecs.openjml.JmlTree.JmlExpression;
import org.jmlspecs.openjml.JmlTree.JmlForLoop;
import org.jmlspecs.openjml.JmlTree.JmlGroupName;
import org.jmlspecs.openjml.JmlTree.JmlImport;
import org.jmlspecs.openjml.JmlTree.JmlLblExpression;
import org.jmlspecs.openjml.JmlTree.JmlMethodClause;
import org.jmlspecs.openjml.JmlTree.JmlMethodClauseCallable;
import org.jmlspecs.openjml.JmlTree.JmlMethodClauseConditional;
import org.jmlspecs.openjml.JmlTree.JmlMethodClauseDecl;
import org.jmlspecs.openjml.JmlTree.JmlMethodClauseExpr;
import org.jmlspecs.openjml.JmlTree.JmlMethodClauseGroup;
import org.jmlspecs.openjml.JmlTree.JmlMethodClauseSignals;
import org.jmlspecs.openjml.JmlTree.JmlMethodClauseSignalsOnly;
import org.jmlspecs.openjml.JmlTree.JmlMethodClauseStoreRef;
import org.jmlspecs.openjml.JmlTree.JmlMethodDecl;
import org.jmlspecs.openjml.JmlTree.JmlMethodInvocation;
import org.jmlspecs.openjml.JmlTree.JmlMethodSig;
import org.jmlspecs.openjml.JmlTree.JmlMethodSpecs;
import org.jmlspecs.openjml.JmlTree.JmlModelProgramStatement;
import org.jmlspecs.openjml.JmlTree.JmlPrimitiveTypeTree;
import org.jmlspecs.openjml.JmlTree.JmlQuantifiedExpr;
import org.jmlspecs.openjml.JmlTree.JmlSetComprehension;
import org.jmlspecs.openjml.JmlTree.JmlSingleton;
import org.jmlspecs.openjml.JmlTree.JmlSpecificationCase;
import org.jmlspecs.openjml.JmlTree.JmlStatementLoop;
import org.jmlspecs.openjml.JmlTree.JmlStoreRefArrayRange;
import org.jmlspecs.openjml.JmlTree.JmlStoreRefKeyword;
import org.jmlspecs.openjml.JmlTree.JmlStoreRefListExpression;
import org.jmlspecs.openjml.JmlTree.JmlTypeClause;
import org.jmlspecs.openjml.JmlTree.JmlTypeClauseConditional;
import org.jmlspecs.openjml.JmlTree.JmlTypeClauseConstraint;
import org.jmlspecs.openjml.JmlTree.JmlTypeClauseDecl;
import org.jmlspecs.openjml.JmlTree.JmlTypeClauseExpr;
import org.jmlspecs.openjml.JmlTree.JmlTypeClauseIn;
import org.jmlspecs.openjml.JmlTree.JmlTypeClauseInitializer;
import org.jmlspecs.openjml.JmlTree.JmlTypeClauseMaps;
import org.jmlspecs.openjml.JmlTree.JmlTypeClauseMonitorsFor;
import org.jmlspecs.openjml.JmlTree.JmlTypeClauseRepresents;
import org.jmlspecs.openjml.JmlTree.JmlVariableDecl;
import org.jmlspecs.openjml.JmlTree.JmlWhileLoop;
import com.sun.source.tree.IdentifierTree;
import com.sun.tools.javac.code.*;
import com.sun.tools.javac.code.Attribute.Compound;
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.Type.ArrayType;
import com.sun.tools.javac.code.Type.ClassType;
import com.sun.tools.javac.comp.Attr.ResultInfo;
import com.sun.tools.javac.jvm.ClassReader;
import com.sun.tools.javac.parser.ExpressionExtension;
import com.sun.tools.javac.parser.JmlScanner;
import com.sun.tools.javac.parser.JmlTokenizer;
import com.sun.tools.javac.tree.EndPosTable;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.*;
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.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.Pair;
import com.sun.tools.javac.util.Position;
/**
* This class is an extension of the Attr class; it adds visitors methods so
* that as the Attr class walks the entire AST, attributing all nodes
* (i.e., doing name lookup and type assignment), the JML parts of the source tree are
* attributed and checked as well.
* <P>
* On input to this class all top-level types and all their members,
* recursively, are already entered into the symbol table (but local classes and
* declarations are not). Also, the types of all annotations and of member
* fields and member methods (return type, arguments and type arguments) are
* already attributed.
* <P>
* The class has the responsibility of attributing the rest of the AST (e.g.,
* method bodies, field initializers, expressions within JML annotations, etc.).
* It also checks that JML modifiers are used correctly.
*
* @author David Cok
*/
/* Note on the visitor class: Each node visitor is responsible to
* do the following:
* a) any attribution of its subtrees, by calling an appropriate
* attrib... method (e.g., attribExpr or attribType),
* b) checking that the types of those subtrees are correct,
* c) determining the type of the node itself,
* d) checking that against the value of pt. If a check call is
* used to do this, then tree.type is set for you. If you do the
* checking yourself, then you have to set tree.type directly.
* The check call handles casting, widening etc. for you, and
* e) set the value of result to the resulting type (if it is needed
* by the containing expression).
*
* This design appears to involve a lot of extra work. Instead of
* having the attrib calls pass in an expected type, which is set as
* a member of the Attr class, why not just check it upon return?
* That would save the stacking that is necessary in attribTree.
*/
public class JmlAttr extends Attr implements IJmlVisitor {
/** This is the compilation context for which this is the unique instance */
@NonNull final protected Context context;
/** The Name version of resultVarString in the current context */
@NonNull final public Name resultName;
/** The Name version of exceptionVarString in the current context */
@NonNull final public Name exceptionName;
/** The fully-qualified name of the Utils class */
// Use .class on the class name instead of a string so that an error happens if the class is renamed
// This class is in the runtime library
@NonNull final public static String utilsClassName = org.jmlspecs.utils.Utils.class.getCanonicalName();
/** Cached value of the org.jmlspecs.utils.Utils class */
@NonNull final protected ClassSymbol utilsClass;
/** Cached identifier of the org.jmlspecs.utils.Utils class */
@NonNull final protected JCIdent utilsClassIdent;
/** Cached value of the JMLDataGroup class */
@NonNull final protected ClassSymbol datagroupClass;
/** The JmlSpecs instance for this context */
@NonNull final protected JmlSpecs specs;
/** The Utils instance for this context */
@NonNull final protected Utils utils;
/** The Types instance for this context */
@NonNull final public JmlTypes jmltypes;
/** The Names table from the compilation context, initialized in the constructor */
@NonNull final protected Names names;
/** The tool used to read binary classes */
@NonNull final protected ClassReader classReader;
/** The factory used to create AST nodes, initialized in the constructor */
@NonNull final protected JmlTree.Maker factory;
/** A JmlCompiler instance */
@NonNull final protected JmlCompiler jmlcompiler;
/** A JmlResolve instance */
@NonNull final protected JmlResolve jmlresolve;
/** An instance of the tree utility class */
@NonNull final protected JmlTreeUtils treeutils;
/** A Literal for a boolean true */
@NonNull final protected JCLiteral trueLit;
/** A Literal for a boolean false */
@NonNull final protected JCLiteral falseLit;
/** A Literal for a null constant */
@NonNull final protected JCLiteral nullLit;
/** A Literal for an int zero */
@NonNull final protected JCLiteral zeroLit;
/** Cached value of the @Model annotation symbol */
public ClassSymbol modelAnnotationSymbol = null;
/** Cached value of the @Pure annotation symbol */
public ClassSymbol pureAnnotationSymbol = null;
/** Cached value of the @NonNull annotation symbol */
public ClassSymbol nonnullAnnotationSymbol = null;
/** Cached value of the @Nullable annotation symbol */
public ClassSymbol nullableAnnotationSymbol = null;
/** Cached value of the @NonNullByDefault annotation symbol */
public ClassSymbol nonnullbydefaultAnnotationSymbol = null;
/** Cached value of the @NullableByDefault annotation symbol */
public ClassSymbol nullablebydefaultAnnotationSymbol = null;
// Unfortunately we cannot increase the number of primitive
// type tags without modifying the TypeInfo file. These
// type tags are out of range, so we cannot use the usual
// initType call to initialize them.
// FIXME - may need to revisit this for boxing and unboxing
final public Type Lock;// = new Type(1003,null);
final public Type LockSet;// = new Type(1004,null);
final public Type JMLValuesType;
final public Type JMLIterType;
final public Type JMLSetType;
// The following fields are stacked as the environment changes as one
// visits down the tree.
/** When true, we are visiting subtrees that allow only pure methods and
* pure operations */
protected boolean pureEnvironment = false;
/** Holds the env of the enclosing method for subtrees of a MethodDecl. */
protected Env<AttrContext> enclosingMethodEnv = null;
/** Holds the env of the enclosing class for subtrees of a ClassDecl. */
protected Env<AttrContext> enclosingClassEnv = null;
/** Set to true when we are within a JML declaration */
protected boolean isInJmlDeclaration = false;
/** This field stores the clause type when a clause is visited (before
* visiting its components), in order that various clause-type-dependent
* semantic tests can be performed (e.g. \result can only be used in some
* types of clauses).
*/
protected JmlTokenKind currentClauseType = null;
/**
* Holds the visibility of JML construct that is currently being visited.
* Values are 0=package, Flags.PUBLIC=public, Flags.PROTECTED=protected,
* Flags.PRIVATE=private, -1=not in JML
*/ // FIXME - isa this Java visibility or JML visibility?
protected long jmlVisibility = -1;
/** This value is valid within a Signals clause */
protected Type currentExceptionType = null;
/** Queued up classes to be attributed */
protected java.util.List<ClassSymbol> todo = new LinkedList<ClassSymbol>();
/** A counter that indicates a level of nesting of attribClass calls */
protected int level = 0;
/** Registers a singleton factory for JmlAttr against the attrKey, so that there is
* just one instance per context.
* @param context the context in which to register the instance
*/
public static void preRegister(final Context context) {
context.put(Attr.attrKey, new Context.Factory<Attr>() {
public Attr make(Context context) {
return new JmlAttr(context); // Registers itself on construction
}
});
}
/** Returns the instance for the given context
*
* @param context the context in which we are working
* @return the non-null instance of JmlAttr for this context
*/
public static JmlAttr instance(Context context) {
Attr instance = context.get(attrKey);
if (instance == null)
instance = new JmlAttr(context); // Registers itself in the super constructor
return (JmlAttr)instance; // If the registered instance is only an Attr, something is catastrophically wrong
}
/** Constructor of a JmlAttr tool for the given context
*
* @param context the compilation context in which we are working
*/
protected JmlAttr(Context context) {
super(context);
this.context = context;
this.utils = Utils.instance(context);
this.specs = JmlSpecs.instance(context);
this.factory = JmlTree.Maker.instance(context);
this.names = Names.instance(context);
this.classReader = ClassReader.instance(context);
this.jmltypes = JmlTypes.instance(context);
this.classReader.init(syms);
this.jmlcompiler = (JmlCompiler)JmlCompiler.instance(context);
this.jmlresolve = (JmlResolve)rs;
this.treeutils = JmlTreeUtils.instance(context);
// Caution, because of circular dependencies among constructors of the
// various tools, it can happen that syms is not fully constructed at this
// point and syms.objectType will be null.
if (syms.objectType == null) {
System.err.println("INTERNAL FAILURE: A circular dependency among constructors has caused a failure to correctly construct objects. Please report this internal problem.");
// Stack trace is printed inside the constructor
throw new JmlInternalError();
}
initAnnotationNames(context);
utilsClass = createClass(utilsClassName);
utilsClassIdent = ((JmlTree.Maker)make).Ident(utilsClassName);
utilsClassIdent.type = utilsClass.type;
utilsClassIdent.sym = utilsClassIdent.type.tsym;
datagroupClass = createClass(Strings.jmlSpecsPackage + ".JMLDataGroup");
JMLSetType = createClass(Strings.jmlSpecsPackage + ".JMLSetType").type;
JMLValuesType = createClass(Strings.jmlSpecsPackage + ".JMLList").type;
JMLIterType = createClass("java.util.Iterator").type;
Lock = syms.objectType;
LockSet = JMLSetType;
nullablebydefaultAnnotationSymbol = createClass("org.jmlspecs.annotation.NullableByDefault"); // FIXME - use defined Strings
nonnullbydefaultAnnotationSymbol = createClass("org.jmlspecs.annotation.NonNullByDefault");
nonnullAnnotationSymbol = createClass("org.jmlspecs.annotation.NonNull");
nullableAnnotationSymbol = createClass("org.jmlspecs.annotation.Nullable");
pureAnnotationSymbol = createClass("org.jmlspecs.annotation.Pure");
modelAnnotationSymbol = createClass("org.jmlspecs.annotation.Model");
this.resultName = names.fromString(Strings.resultVarString);
this.exceptionName = names.fromString(Strings.exceptionVarString);
trueLit = makeLit(syms.booleanType,1);
falseLit = makeLit(syms.booleanType,0);
nullLit = makeLit(syms.botType,null);
zeroLit = makeLit(syms.intType,0);
}
/** Returns (creating if necessary) a class symbol for a given fully-qualified name */
public ClassSymbol createClass(String fqName) {
return classReader.enterClass(names.fromString(fqName));
}
/** Overrides the super class call in order to perform JML checks on class
* modifiers. (Actually, the work was moved to attribClassBody since attribClass
* gets called multiple times for a Class).
* @param c The class to check
* @throws CompletionFailure
*/
@Override
public void attribClass(ClassSymbol c) throws CompletionFailure {
boolean isUnattributed = (c.flags_field & UNATTRIBUTED) != 0;
if (utils.jmlverbose >= Utils.JMLDEBUG) log.getWriter(WriterKind.NOTICE).println("Attributing-requested " + c + " specs="+(specs.get(c)!=null) + " env="+(enter.getEnv(c)!=null));
// FIXME - can we make the following more efficient - this gets called a lot for classes already attributed
/*@Nullable*/ JmlSpecs.TypeSpecs classSpecs = specs.get(c); // Get null if there are none yet
if (classSpecs == null) {
jmlcompiler.loadSpecsForBinary(env,c);
classSpecs = specs.get(c);
if (classSpecs == null) {
// loadSpecsForBinary should always result in a TypeSpecs for the
// class symbol, even if the TypeSpecs is empty
log.warning("jml.internal.notsobad","loadSpecsForBinary failed for class " + c);
c.complete(); // At least complete it
}
}
if (utils.jmlverbose >= Utils.JMLDEBUG) log.getWriter(WriterKind.NOTICE).println("Attributing specs for " + c + " " + level);
level++;
if (c != syms.predefClass) {
if (utils.jmlverbose >= Utils.PROGRESS) context.get(Main.IProgressListener.class).report(0,2,"typechecking " + c);
}
// classSpecs.file is only useful for the modifiers/annotations
// the specs themselves might come from any specification file
JavaFileObject prev = classSpecs == null ? null : log.useSource(classSpecs.file);
// We track pureEnvironment since calls can be nested -
// we can enter a pure environment from either an impure or pure
// environment and we need to restore it properly. Also, when in a
// pure environment we may need to attribute a class, not all of which
// is pure.
boolean prevPureEnvironment = pureEnvironment;
pureEnvironment = false;
try {
Env<AttrContext> eee = typeEnvs.get(c);
if (eee != null) { // FIXME - is null an error? is null for annotations like SpecPublic, for example, but no attribution is needed either, since there are no spec files.
JavaFileObject prevv = log.useSource(eee.toplevel.sourcefile);
try {
super.attribClass(c); // No need to attribute the class itself if it was binary
attribFieldSpecs(eee,c);
} finally {
log.useSource(prevv);
}
}
if (!isUnattributed) return;
// FIXME - do we still need this?
// Binary files with specs had entries put in the Env map so that the
// specs had environments against which to be attributed. However, the
// presence of envs for non-.java files confuses later compiler phases,
// and we no longer need that mapping since all the specs are now
// attributed and entered into the JmlSpecs database. Hence we remove
// the entry.
Env<AttrContext> e = enter.getEnv(c);
if (e != null) { // SHould be non-null the first time, but subsequent calls will have e null and so we won't do duplicate checking
// // FIXME - RAC should take advantage of the same stuff as ESC
// if (true || !JmlOptionName.isOption(context,JmlOptionName.RAC)) {
// new JmlTranslator(context).translate(e.tree);
// }
if (e.tree != null && e.tree instanceof JmlClassDecl) {
((JmlClassDecl)e.tree).thisSymbol = (VarSymbol)thisSym(e.tree.pos(),e);
// //((JmlClassDecl)e.tree).thisSymbol = (VarSymbol)rs.resolveSelf(e.tree.pos(),e,c,names._this);
// if (!((JmlClassDecl)e.tree).sym.isInterface() && c != syms.objectType.tsym) ((JmlClassDecl)e.tree).superSymbol = (VarSymbol)rs.resolveSelf(e.tree.pos(),e,c,names._super);
}
if (e.toplevel.sourcefile.getKind() != JavaFileObject.Kind.SOURCE) {
// If not a .java file
enter.typeEnvs.remove(c); // FIXME - after flow checking of model methods and classes for binary classes?
}
}
} finally {
pureEnvironment = prevPureEnvironment;
if (prev != null) log.useSource(prev);
level--;
if (c != syms.predefClass) {
if (utils.jmlverbose >= Utils.PROGRESS) context.get(Main.IProgressListener.class).report(0,2,"typechecked " + c);
}
if (utils.jmlverbose >= Utils.JMLDEBUG) log.getWriter(WriterKind.NOTICE).println("Attributing-complete " + c.fullname + " " + level);
if (level == 0) completeTodo();
}
}
public void attribFieldSpecs(Env<AttrContext> env, ClassSymbol csym) {
Env<AttrContext> prev = this.env;
this.env = env;
try {
JmlClassDecl cdecl = (JmlClassDecl)env.tree;
for (JCTree t: cdecl.defs) {
if (t instanceof JCVariableDecl) {
JCVariableDecl vdecl = (JCVariableDecl)t;
if (vdecl.sym != null) { // FIXME - WHY WOULD THIS SYM EVER BE NULL?
FieldSpecs fspecs = specs.getSpecs(vdecl.sym);
if (fspecs != null) {
for (JmlTypeClause spec: fspecs.list) {
if (spec instanceof JmlTypeClauseIn) spec.accept(this);
if (spec instanceof JmlTypeClauseMaps) spec.accept(this);
}
}
}
}
}
} finally {
this.env = prev;
}
}
/** Attribute all classes whose attribution was deferred (while in the middle of attributing another class) */
public void completeTodo() {
level++;
while (!todo.isEmpty()) {
ClassSymbol sym = todo.remove(0);
if (utils.jmlverbose >= Utils.JMLDEBUG) log.getWriter(WriterKind.NOTICE).println("Retrieved for attribution " + sym + " " + todo.size());
attribClass(sym);
}
level--;
}
/** Adds a class symbol to the list of classes to be attributed later */
public void addTodo(ClassSymbol c) {
if (!todo.contains(c)) {
if (!utils.isTypeChecked(c)) todo.add(c);
if (utils.jmlverbose >= Utils.JMLDEBUG) log.getWriter(WriterKind.NOTICE).println("Queueing for attribution " + result.tsym + " " + todo.size());
}
}
/** Overrides in order to attribute class specs appropriately. */
@Override
protected void attribClassBody(Env<AttrContext> env, ClassSymbol c) {
Env<AttrContext> prevClassEnv = enclosingClassEnv;
enclosingClassEnv = env;
// FIXME - for a binary class c, env.tree appears to be the tree of the specs
// FIXME - why should we attribute the Java class body in the case of a binary class
boolean prevIsInJmlDeclaration = isInJmlDeclaration;
isInJmlDeclaration = isInJmlDeclaration || utils.isJML(c.flags()); // REMOVED implementationAllowed ||
((JmlCheck)chk).setInJml(isInJmlDeclaration);
if (utils.jmlverbose >= Utils.JMLDEBUG) log.getWriter(WriterKind.NOTICE).println("ATTRIBUTING-BODY " + c.fullname + " " + (isInJmlDeclaration?"inJML":"notInJML") + " WAS " + (prevIsInJmlDeclaration?"inJML":"notInJML"));
// JavaFileObject prev = log.useSource(((JmlClassDecl)env.enclClass).toplevel.sourcefile); // FIXME - no write for multiple source files
boolean oldRelax = relax;
try {
// If the class is binary only, then we have not yet attributed the super/extending/implementing classes in the source AST for the specifications
JmlClassDecl cd = (JmlClassDecl)env.tree;
{
JCExpression e = cd.extending;
if (e != null && e.type == null) attribType(e,env);
}
if (cd != null) for (JCExpression e: cd.implementing) {
if (e.type == null) attribType(e,env);
}
// The JML specs to check are are in the TypeSpecs structure
relax = true; // Turns off some bogus lack of overriding warnings
super.attribClassBody(env,c);
attribClassBodySpecs(env,c,prevIsInJmlDeclaration);
} finally {
relax = oldRelax;
isInJmlDeclaration = prevIsInJmlDeclaration;
enclosingClassEnv = prevClassEnv;
((JmlCheck)chk).setInJml(isInJmlDeclaration);
// log.useSource(prev);
}
}
// FIXME - do not need this is we can avoid having invariants in the class body
@Override
public Type attribStat(JCTree tree, Env<AttrContext> env) {
if (tree instanceof JmlTypeClause && relax) return null;
return super.attribStat(tree,env);
}
/** Attributes the specs (in the TypeSpecs structure) for the given class
*
* @param env the current class environment
* @param c the current class
* @param prevIsInJmlDeclaration true if we are in a non-model JML declaration (FIXME - this needs better evaluation)
*/
public void attribClassBodySpecs(Env<AttrContext> env, ClassSymbol c, boolean prevIsInJmlDeclaration) {
JmlSpecs.TypeSpecs tspecs = JmlSpecs.instance(context).get(c);
JmlClassDecl classDecl = (JmlClassDecl)env.tree;
JavaFileObject prev = log.currentSourceFile();
// if (c.flatName().toString().equals("java.lang.Throwable")) Utils.stop();
// This is not recursive within a class, but we can call out to attribute
// another class while in the middle of a clause
JmlTokenKind prevClauseType = currentClauseType;
try {
if (tspecs != null) {
Env<AttrContext> prevEnv = this.env;
this.env = env;
// The modifiers and annotations are checked in checkClassMods
// which is part of checking the class itself
// clauses and declarations
for (JmlTypeClause clause: tspecs.clauses) {
currentClauseType = clause.token;
clause.accept(this);
}
currentClauseType = null;
// model types, which are not in clauses
// for (JmlClassDecl mtype: tspecs.modelTypes) {
// mtype.accept(this);
// }
// // Do the specs for JML initialization blocks // FIXME - test the need for this - for initializatino blocks and JML initializations
// for (JCTree.JCBlock m: tspecs.blocks.keySet()) {
// m.accept(this);
// }
if (tspecs.modifiers != null) {
log.useSource(tspecs.file);
attribAnnotationTypes(tspecs.modifiers.annotations,env);
isInJmlDeclaration = prevIsInJmlDeclaration;
((JmlCheck)chk).setInJml(isInJmlDeclaration);
if (tspecs.file != null) log.useSource(tspecs.file);
checkClassMods(c,classDecl,tspecs);
} else {
// log.warning("jml.internal.notsobad","Unexpected lack of modifiers in class specs structure for " + c); // FIXME - testJML2
}
// Checking all the in and maps clauses is delayed until all the fields have been processed
for (JCTree t: classDecl.defs) {
if (t instanceof JmlVariableDecl) {
checkVarMods2((JmlVariableDecl)t);
}
}
this.env = prevEnv;
} else {
log.warning("jml.internal.notsobad","Unexpected lack of class specs structure for " + c);
}
} finally {
currentClauseType = prevClauseType;
log.useSource(prev);
}
}
public Env<AttrContext> localEnv(Env<AttrContext> env, JCTree tree) {
return env.dup(tree, env.info.dup(env.info.scope.dupUnshared()));
}
/** This is overridden to attribute the specs for the method,
* if there are any. The specs are walked at the
* time the block that is the method body is walked so that the scope
* is right.
*/
@Override
public void visitBlock(JCBlock tree) {
Env<AttrContext> prevEnclosingMethodEnv = enclosingMethodEnv;
// FIXME - what about initializer blocks - they also need an old environment
if (env.info.scope.owner.kind == TYP) {
// FIXME - if we are in a spec source file that is not Java, we may not have one of these - error
super.visitBlock(tree);
JmlSpecs.MethodSpecs msp = JmlSpecs.instance(context).getSpecs(env.enclClass.sym,tree);
//if (attribSpecs && sp != null) {
if (msp != null) {
JmlMethodSpecs sp = msp.cases;
Env<AttrContext> localEnv = localEnv(env,tree);
localEnv.info.scope.owner =
new MethodSymbol(tree.flags | BLOCK, names.empty, null,
env.info.scope.owner);
if (isStatic(tree.flags)) localEnv.info.staticLevel++;
//boolean prev = attribSpecs;
//attribSpecs = true;
attribStat(sp,localEnv);
//attribSpecs = prev;
}
} else {
// Method blocks
boolean topMethodBodyBlock = env.enclMethod != null &&
env.enclMethod.body == tree;
if (topMethodBodyBlock) {
// Scope is not duplicated
enclosingMethodEnv = env.dup(env.tree,env.info.dupUnshared());
}
super.visitBlock(tree);
//if (!isStatic(env.enclMethod.mods.flags)) {
if (env.info.staticLevel == 0 && topMethodBodyBlock) {
((JmlMethodDecl)env.enclMethod)._this = (VarSymbol)thisSym(tree.pos(),enclosingMethodEnv);
}
if (env.enclMethod != null &&
env.enclMethod.body == tree) {
// Note: we cannot just cache the current value of env for use later.
// This is because the envs are presumed to be nested and share their
// symbol tables. Access to scopes is presumed to be nested - in Java
// a reference to an identifier is always resolved in the current scope first,
// not in an enclosing scope. However, JML has the \old operator which gives
// access to the scope at method definition time from within other nestings.
boolean prevAllowJML = jmlresolve.setAllowJML(true);
try {
JmlSpecs.MethodSpecs sp = ((JmlMethodDecl)env.enclMethod).methodSpecsCombined; //specs.getSpecs(env.enclMethod.sym);
currentSecretContext = sp.secretDatagroup;
currentQueryContext = null;
// if (enclosingMethodEnv == null) {
// FIXME - This can happen for anonymous classes, so I expect that
// specs (or at least \old) in anonymous classes will cause disaster
// log.getWriter(WriterKind.NOTICE).println("DISASTER-2 AWAITS: " + env.enclMethod.name);
// log.getWriter(WriterKind.NOTICE).println(env.enclMethod);
// }
if (sp != null && sp.cases != null) sp.cases.accept(this);
// if (enclosingMethodEnv == null) {
// log.getWriter(WriterKind.NOTICE).println("DODGED-2: " + env.enclMethod.name);
deSugarMethodSpecs(((JmlMethodDecl)env.enclMethod),sp);
// }
} finally {
jmlresolve.setAllowJML(prevAllowJML);
}
}
}
enclosingMethodEnv = prevEnclosingMethodEnv;
}
@Override
public void visitUnary(JCUnary tree) {
super.visitUnary(tree);
if (pureEnvironment) {
// FIXME - this and the next two happen in pure model methods - should probably treat them as pure methods and not pureEnvironments
if (tree.arg instanceof JCIdent && ((JCIdent)tree.arg).sym.owner.kind == Kinds.MTH) return;
JCTree.Tag op = tree.getTag();
if (op == JCTree.Tag.PREINC || op == JCTree.Tag.POSTINC ||
op == JCTree.Tag.PREDEC || op == JCTree.Tag.POSTDEC)
log.error(tree.pos,"jml.no.incdec.in.pure");
}
}
@Override
public void visitAssign(JCAssign tree) {
super.visitAssign(tree);
if (pureEnvironment) {
// The following checks that the assignment is local (the symbol being assigned is owned by the method)
if (tree.lhs instanceof JCIdent && ((JCIdent)tree.lhs).sym.owner.kind == Kinds.MTH) return;
log.error(tree.pos,"jml.no.assign.in.pure");
}
if (currentSecretContext != null) checkWritable(tree.lhs);
}
@Override
public void visitAssignop(JCAssignOp tree) {
super.visitAssignop(tree);
if (pureEnvironment) {
// The following checks that the assignment is local (the symbol being assigned is owned by the method)
if (tree.lhs instanceof JCIdent && ((JCIdent)tree.lhs).sym.owner.kind == Kinds.MTH) return;
log.error(tree.pos,"jml.no.assign.in.pure");
}
if (currentSecretContext != null) checkWritable(tree.lhs);
}
protected void checkWritable(JCTree lhs) {
Type saved = result;
Symbol s = null;
if (lhs instanceof JCArrayAccess) { // FIXME - shuold this be while instead of if
lhs = ((JCArrayAccess)lhs).indexed;
}
if (lhs instanceof JCIdent) {
if ((s=((JCIdent)lhs).sym) instanceof VarSymbol) checkSecretWritable(lhs.pos(),(VarSymbol)s);
// FIXME - else not writable?
} else if (lhs instanceof JCFieldAccess) {
if ((s=((JCFieldAccess)lhs).sym) instanceof VarSymbol) checkSecretWritable(lhs.pos(),(VarSymbol)s);
// FIXME - else not writable?
}
if (s == null || (!(s instanceof VarSymbol) && !(lhs instanceof JmlSingleton))) {
log.error(lhs.pos(),"jml.not.writable.in.secret"); // FIXME - see what relaxed rules we can support; rely on assignable?
}
result = saved;
}
public JmlTokenKind[] allowedTypeModifiers = new JmlTokenKind[]{
CODE_JAVA_MATH, CODE_SAFE_MATH, CODE_BIGINT_MATH, SPEC_JAVA_MATH, SPEC_SAFE_MATH, SPEC_BIGINT_MATH,
OPTIONS, PURE, MODEL, QUERY, SKIP_RAC, NULLABLE_BY_DEFAULT, NON_NULL_BY_DEFAULT, IMMUTABLE};
public JmlTokenKind[] allowedNestedTypeModifiers = new JmlTokenKind[]{
CODE_JAVA_MATH, CODE_SAFE_MATH, CODE_BIGINT_MATH, SPEC_JAVA_MATH, SPEC_SAFE_MATH, SPEC_BIGINT_MATH,
OPTIONS, PURE, MODEL, QUERY, SPEC_PUBLIC, SPEC_PROTECTED, NULLABLE_BY_DEFAULT, NON_NULL_BY_DEFAULT, IMMUTABLE};
public JmlTokenKind[] allowedNestedModelTypeModifiers = new JmlTokenKind[]{
CODE_JAVA_MATH, CODE_SAFE_MATH, CODE_BIGINT_MATH, SPEC_JAVA_MATH, SPEC_SAFE_MATH, SPEC_BIGINT_MATH,
OPTIONS, PURE, MODEL, QUERY, NULLABLE_BY_DEFAULT, NON_NULL_BY_DEFAULT, IMMUTABLE};
public JmlTokenKind[] allowedLocalTypeModifiers = new JmlTokenKind[]{
CODE_JAVA_MATH, CODE_SAFE_MATH, CODE_BIGINT_MATH, SPEC_JAVA_MATH, SPEC_SAFE_MATH, SPEC_BIGINT_MATH,
OPTIONS, PURE, MODEL, QUERY, IMMUTABLE};
/** Checks the JML modifiers so that only permitted combinations are present. */
public void checkClassMods(ClassSymbol classSymbol, /*@ nullable */ JmlClassDecl javaDecl, TypeSpecs tspecs) {
//System.out.println("Checking " + javaDecl.name + " in " + owner);
if (classSymbol.toString().endsWith("A")) Utils.stop();
checkTypeMatch(classSymbol, tspecs.decl);
Symbol owner = classSymbol.owner;
JCModifiers specsModifiers = tspecs.modifiers;
JmlClassDecl specsDecl = tspecs.decl;
boolean inJML = utils.isJML(specsModifiers);
JCAnnotation model = findMod(specsModifiers,JmlTokenKind.MODEL);
boolean isModel = model != null;
if (specsModifiers == null) {
// no annotations to check
} else if (owner.kind == Kinds.PCK) { // Top level type declaration
allAllowed(specsModifiers.annotations,allowedTypeModifiers,"type declaration");
} else if (owner.kind != Kinds.TYP) { // Local
allAllowed(specsModifiers.annotations,allowedLocalTypeModifiers,"local type declaration");
} else if (!isModel) { // Nested/inner type declaration
allAllowed(specsModifiers.annotations,allowedNestedTypeModifiers,"nested type declaration");
} else { // Nested model type declaration
allAllowed(specsModifiers.annotations,allowedNestedModelTypeModifiers,"nested model type declaration");
}
if (isInJmlDeclaration && isModel) {
log.error(specsDecl,"jml.no.nested.model.type");
} else if (inJML && !isModel && !isInJmlDeclaration) {
log.error(specsDecl,"jml.missing.model");
} else if (!inJML && isModel) {
log.error(specsDecl,"jml.ghost.model.on.java");
}
if (!isModel) {
checkForConflict(specsModifiers,SPEC_PUBLIC,SPEC_PROTECTED);
checkForRedundantSpecMod(specsModifiers);
}
checkForConflict(specsModifiers,NON_NULL_BY_DEFAULT,NULLABLE_BY_DEFAULT);
checkForConflict(specsModifiers,PURE,QUERY);
{
// FIXME - this attribution should be done in the Enter or MemberEnter phase?
attribAnnotationTypes(specsModifiers.annotations,env);
JavaFileObject prev = log.useSource(tspecs.file);
if (javaDecl != null) {
checkSameAnnotations(javaDecl.mods,specsModifiers,"class",classSymbol.toString());
} else {
long flags = classSymbol.flags();
JCModifiers m = factory.Modifiers(flags, null); // FIXME - should check annotations
checkSameAnnotations(m,specsModifiers,"class",classSymbol.toString());
}
log.useSource(prev);
}
}
//public void checkTypeMatch(JmlClassDecl javaDecl, JmlClassDecl specsClassDecl) {
public void checkTypeMatch(ClassSymbol javaClassSym, 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
// Check that every specification class declaration (e.g. class decl in a .jml file) has
// Java modifiers that match the modifiers in the Java soursce or class file.
JmlClassDecl specsDecl = combinedTypeSpecs.refiningSpecDecls;
if (specsDecl != null) {
// FIXME - no way to skip the loop if the specsDecl is the javaDecl
// Check that modifiers are the same
long matchf = javaClassSym.flags();
long specf = specsDecl.mods.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||isEnum)) diffs &= ~Flags.ABSTRACT; // FIXME - why for enum?
if ((Flags.STATIC & matchf & ~specf) != 0 && isEnum) diffs &= ~Flags.STATIC;
if ((Flags.FINAL & matchf & ~specf) != 0 && isEnum) diffs &= ~Flags.FINAL;
if ((Flags.FINAL & matchf & ~specf) != 0 && javaClassSym.name.isEmpty()) diffs &= ~Flags.FINAL; // Anonymous classes are implicitly final
if (diffs != 0) {
//JavaFileObject prev = log.useSource(specsClassDecl.source());
Log.instance(context).error(specsDecl.pos(),"jml.mismatched.modifiers", specsClassDecl.name, javaClassSym.fullname, Flags.toString(diffs)); // FIXME - test this
//log.useSource(prev);
}
// 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
attribAnnotationTypes(specsClassDecl.mods.annotations, Enter.instance(context).typeEnvs.get(javaClassSym)); // FIXME - this is done later; is it needed here?
//checkSameAnnotations(javaDecl.mods,specsClassDecl.mods);
// FIXME - check that both are Enum; check that both are Annotation
}
if (combinedTypeSpecs.file == null || combinedTypeSpecs.file.getKind() == JavaFileObject.Kind.SOURCE){
// This is already checked in enterTypeParametersForBinary (for binary classes)
List<Type> t = ((Type.ClassType)javaClassSym.type).getTypeArguments();
if (specsClassDecl != null) { // FIXME
List<JCTypeParameter> specTypes = specsClassDecl.typarams;
if (t.size() != specTypes.size()) {
Log.instance(context).error(specsClassDecl.pos(),"jml.mismatched.type.arguments",javaClassSym.fullname,javaClassSym.type.toString());
}
// FIXME - check that the names and bounds are the same
}
}
}
// FIXME - this should be done in MemberEnter, not here -- needed for JmlVariableDecl, checking class mods
protected boolean checkSameAnnotations(JCModifiers javaMods, JCModifiers specMods, String kind, String name) {
//if (javaMods == specMods) return true;
PackageSymbol p = annotationPackageSymbol;
boolean r = false;
for (JCAnnotation a: javaMods.getAnnotations()) {
// if sourcefile is null, the annotation was inserted to make an implicit annotation explicit; we don't complain about those, as the default may be superseded by a different explicit annotation
TypeSymbol tsym = a.annotationType.type.tsym;
if (((JmlTree.JmlAnnotation)a).sourcefile != null && tsym.owner.equals(p) && utils.findMod(specMods,tsym) == null) {
JavaFileObject prev = log.useSource(((JmlTree.JmlAnnotation)a).sourcefile);
log.warning(a.pos,"jml.java.annotation.superseded",kind,name,a);
log.useSource(prev);
r = true;
}
}
return r;
}
VarSymbol currentSecretContext = null;
VarSymbol currentQueryContext = null;
boolean implementationAllowed = false;
@Override
public void visitNewClass(JCNewClass tree) {
boolean prev = implementationAllowed;
boolean prevJml = isInJmlDeclaration;
isInJmlDeclaration = true; // FIXME - why is this true
try {
implementationAllowed= true;
super.visitNewClass(tree);
if (pureEnvironment) {
Symbol sym = tree.constructor;
MethodSymbol msym = null;
if (sym instanceof MethodSymbol) msym = (MethodSymbol)sym;
boolean isPure = msym == null || isPureMethod(msym);
if (!isPure && JmlOption.isOption(context,JmlOption.PURITYCHECK)) {
log.warning(tree.pos,"jml.non.pure.method",utils.qualifiedMethodSig(msym));
}
}
Type saved = result;
TypeSymbol tsym = tree.clazz.type.tsym;
if (tsym instanceof ClassSymbol) {
isInJmlDeclaration = false;
attribClass((ClassSymbol)tsym); // FIXME - perhaps this needs to be checked when specs are retrieved
}
result = saved;
} finally {
implementationAllowed = prev;
isInJmlDeclaration = prevJml;
}
}
/** This is overridden in order to do correct checking of whether a method body is
* present or not.
*/
@Override
public void visitMethodDef(JCMethodDecl m) {
// if (m.name.toString().equals("nonNullCheck") ){//&& m.sym.owner.toString().equals("java.lang.Object")) {
// log.getWriter(WriterKind.NOTICE).println(m.sym.owner + ":" + m.sym);
// }
// Setting relax to true keeps super.visitMethodDef from complaining
// that a method declaration in a spec file does not have a body
// FIXME - what else is relaxed? We should do the check under the right conditions?
if (m.sym == null) return; // Guards against specification method declarations that are not matched - FIXME
JmlMethodDecl jmethod = (JmlMethodDecl)m;
Map<Name,Env<AttrContext>> prevLabelEnvs = labelEnvs;
labelEnvs = new HashMap<Name,Env<AttrContext>>();
boolean oldrelax = relax;
relax = true;
VarSymbol previousSecretContext = currentSecretContext;
VarSymbol previousQueryContext = currentQueryContext;
JavaFileObject prevSource = null;
try {
if (jmethod.methodSpecsCombined == null) {
//log.getWriter(WriterKind.NOTICE).println("METHOD SPECS NOT COMBINED " + m.sym.owner + " " + m.sym);
// The following line does happen with anonymous classes implementing an interface, with no constructor given
// but what about FINISHING SPEC CLASS
// FIXME: this also causes the Java file's specs to be used when the specs AST has been set to null
jmethod.methodSpecsCombined = new JmlSpecs.MethodSpecs(jmethod); // BUG recovery? FIXME - i think this happens with default constructors
specs.putSpecs(m.sym,jmethod.methodSpecsCombined);
}
// Do this before we walk the method body
determineQueryAndSecret(jmethod,jmethod.methodSpecsCombined);
if (utils.jmlverbose >= Utils.JMLDEBUG) log.getWriter(WriterKind.NOTICE).println("ATTRIBUTING METHOD " + env.enclClass.sym + " " + m.sym);
prevSource = log.useSource(jmethod.source());
attribAnnotationTypes(m.mods.annotations,env); // This is needed at least for the spec files of binary classes
annotate.flush();
for (JCAnnotation a : m.mods.annotations) a.type = a.annotationType.type; // It seems we need this, but it seems this should happen while walking the tree - FIXME
JmlSpecs.MethodSpecs mspecs = specs.getSpecs(m.sym);
if (mspecs != null) { // FIXME - is mspecs allowed to be null?
currentSecretContext = mspecs.secretDatagroup;
currentQueryContext = mspecs.queryDatagroup;
if (currentQueryContext != null) currentSecretContext = currentQueryContext;
}
boolean prevRelax = relax;
// FIXME - need a better test than this
// Set relax to true if this method declaration is allowed to have no body
// because it is a model declaration or it is in a specification file.
boolean isJavaFile = jmethod.sourcefile != null && jmethod.sourcefile.getKind() == JavaFileObject.Kind.SOURCE;
boolean isJmlDecl = utils.isJML(m.mods);
relax = isJmlDecl || !isJavaFile;
boolean prevAllowJML = jmlresolve.allowJML();
if (isJmlDecl) prevAllowJML = jmlresolve.setAllowJML(true);
// boolean prevChk = ((JmlCheck)chk).noDuplicateWarn;
// ((JmlCheck)chk).noDuplicateWarn = false;
super.visitMethodDef(m);
// ((JmlCheck)chk).noDuplicateWarn = prevChk;
if (isJmlDecl) jmlresolve.setAllowJML(prevAllowJML);
relax = prevRelax;
if (jmethod.methodSpecsCombined != null) { // FIXME - should we get the specs to check from JmlSpecs?
if (m.body == null) {
currentSecretContext = mspecs.secretDatagroup;
currentQueryContext = null;
checkMethodSpecsDirectly(jmethod);
}
// If the body is null, the specs are checked in visitBlock
//else deSugarMethodSpecs(jmethod,jmethod.methodSpecs);
}
if (isJavaFile && !isJmlDecl) {
// Java methods in Java files must have a body (usually)
if (m.body == null) {
ClassSymbol owner = env.enclClass.sym;
// Empty bodies are only allowed for
// abstract, native, or interface methods, or for methods
// in a retrofit signature class.
if ((owner.flags() & INTERFACE) == 0 &&
(m.mods.flags & (ABSTRACT | NATIVE)) == 0 &&
!oldrelax)
log.error(m.pos(), "missing.meth.body.or.decl.abstract");
}
} else if (m.body != null && !isJmlDecl && !isJavaFile && !isInJmlDeclaration && (m.mods.flags & (Flags.GENERATEDCONSTR|Flags.SYNTHETIC)) == 0) {
// Java methods not in Java files may not have bodies (generated constructors do)
//log.error(m.pos(),"jml.no.body.allowed",m.sym);
// A warning is appropriate, but this is a duplicate.
// A warning is already given in JmlMemberEnter.checkMethodMatch
}
checkMethodModifiers(jmethod);
} finally {
currentSecretContext = previousSecretContext;
currentQueryContext = previousQueryContext;
relax = oldrelax;
if (prevSource != null) log.useSource(prevSource);
labelEnvs.clear();
labelEnvs = prevLabelEnvs;
}
}
/** The annotations allowed on non-model non-constructor methods */
public final JmlTokenKind[] allowedMethodAnnotations =
new JmlTokenKind[] {
MODEL, PURE, NONNULL, NULLABLE, OPTIONS, SPEC_PUBLIC, SPEC_PROTECTED, HELPER, EXTRACT, QUERY, SECRET, FUNCTION,
CODE_JAVA_MATH, CODE_SAFE_MATH, CODE_BIGINT_MATH, SPEC_JAVA_MATH, SPEC_SAFE_MATH, SPEC_BIGINT_MATH,
PEER, REP, READONLY, SKIP_ESC, SKIP_RAC // FIXME - allowing these until the rules are really implemented
};
/** The annotations allowed on non-model non-constructor methods in interfaces */
public final JmlTokenKind[] allowedInterfaceMethodAnnotations =
new JmlTokenKind[] {
MODEL, PURE, NONNULL, NULLABLE, OPTIONS, SPEC_PUBLIC, SPEC_PROTECTED, HELPER, QUERY, FUNCTION,
CODE_JAVA_MATH, CODE_SAFE_MATH, CODE_BIGINT_MATH, SPEC_JAVA_MATH, SPEC_SAFE_MATH, SPEC_BIGINT_MATH,
PEER, REP, READONLY // FIXME - allowing these until the rules are really implemented
};
/** The annotations allowed on model non-constructor methods */
public final JmlTokenKind[] allowedModelMethodAnnotations =
new JmlTokenKind[] {
MODEL, PURE, NONNULL, NULLABLE, OPTIONS, HELPER, EXTRACT, QUERY, SECRET, FUNCTION,
CODE_JAVA_MATH, CODE_SAFE_MATH, CODE_BIGINT_MATH, SPEC_JAVA_MATH, SPEC_SAFE_MATH, SPEC_BIGINT_MATH,
PEER, REP, READONLY, SKIP_ESC // FIXME - allowing these until the rules are really implemented
};
/** The annotations allowed on model non-constructor interface methods */
public final JmlTokenKind[] allowedInterfaceModelMethodAnnotations =
new JmlTokenKind[] {
MODEL, PURE, NONNULL, NULLABLE, OPTIONS, HELPER, QUERY, SECRET, FUNCTION,
CODE_JAVA_MATH, CODE_SAFE_MATH, CODE_BIGINT_MATH, SPEC_JAVA_MATH, SPEC_SAFE_MATH, SPEC_BIGINT_MATH,
PEER, REP, READONLY // FIXME - allowing these until the rules are really implemented
};
/** The annotations allowed on non-model constructors */
public final JmlTokenKind[] allowedConstructorAnnotations =
new JmlTokenKind[] {
MODEL, PURE, SPEC_PUBLIC, SPEC_PROTECTED, HELPER, EXTRACT,
CODE_JAVA_MATH, CODE_SAFE_MATH, CODE_BIGINT_MATH, SPEC_JAVA_MATH, SPEC_SAFE_MATH, SPEC_BIGINT_MATH,
PEER, REP, READONLY, OPTIONS, SKIP_ESC // FIXME - allowing these until the rules are really implemented
};
/** The annotations allowed on model constructors */
public final JmlTokenKind[] allowedModelConstructorAnnotations =
new JmlTokenKind[] {
MODEL, PURE, HELPER, EXTRACT ,
CODE_JAVA_MATH, CODE_SAFE_MATH, CODE_BIGINT_MATH, SPEC_JAVA_MATH, SPEC_SAFE_MATH, SPEC_BIGINT_MATH,
OPTIONS, PEER, REP, READONLY // FIXME - allowing these until the rules are really implemented
};
/** Does the various checks of method/constructor modifiers */
public void checkMethodModifiers(JmlMethodDecl javaMethodTree) {
if ((javaMethodTree.sym.flags() & SYNTHETIC) != 0) return;
JavaFileObject prev = log.currentSourceFile();
try {
JmlSpecs.MethodSpecs mspecs = specs.getSpecs(javaMethodTree.sym); //javaMethodTree.methodSpecsCombined;
JCModifiers mods = mspecs.mods;
boolean inJML = utils.isJML(mods);
boolean classIsModel = isModel(javaMethodTree.sym.owner);
boolean model = isModel(mods);
boolean synthetic = mods != null && (mods.flags & Flags.SYNTHETIC) != 0;
boolean anon = javaMethodTree.sym.owner.isAnonymous();
if (classIsModel && model && !synthetic) {
log.useSource(javaMethodTree.sourcefile);
log.error(javaMethodTree.pos,"jml.no.nested.model.type");
} else if (inJML && !model && !isInJmlDeclaration) {
if (!anon) {
log.useSource(javaMethodTree.sourcefile);
log.error(javaMethodTree.pos,"jml.missing.model");
}
} else if (!inJML && model) {
log.useSource(javaMethodTree.sourcefile);
log.error(javaMethodTree.pos,"jml.ghost.model.on.java");
}
// FIXME - this test is in the wrong place (NPE would happen above) and needs review inany case
if (mods == null) mods = javaMethodTree.mods; // FIXME - this can happen for JML synthesized methods, such as are added for RAC - perhaps we should properly initialize the modifiers, but for now we just say they are OK
// Check that any annotations are allowed and no conflicting pairs occur
if (javaMethodTree.getReturnType() != null) {
if (javaMethodTree.sym.enclClass().isInterface()) {
if (model) {
allAllowed(mods.annotations,allowedInterfaceModelMethodAnnotations,"interface model method declaration");
} else {
allAllowed(mods.annotations,allowedInterfaceMethodAnnotations,"interface method declaration");
}
} else {
if (model) {
allAllowed(mods.annotations,allowedModelMethodAnnotations,"model method declaration");
} else {
allAllowed(mods.annotations,allowedMethodAnnotations,"method declaration");
}
}
checkForConflict(mods,NONNULL,NULLABLE);
checkForConflict(mods,PURE,QUERY);
} else { // Constructor
if (model) {
allAllowed(mods.annotations,allowedModelConstructorAnnotations,"model constructor declaration");
} else {
allAllowed(mods.annotations,allowedConstructorAnnotations,"constructor declaration");
}
}
// Check rules about Function
JCAnnotation a=utils.findMod(mods,tokenToAnnotationSymbol.get(FUNCTION));
if (a != null && !utils.isJMLStatic(javaMethodTree.sym)) {
Symbol sym = javaMethodTree.sym.owner;
if (sym instanceof ClassSymbol && !isImmutable((ClassSymbol)sym)) {
log.error(a.pos,"jml.function.must.have.immutable",javaMethodTree.name.toString());
}
}
// Check rules about helper
if ( (a=utils.findMod(mods,tokenToAnnotationSymbol.get(HELPER))) != null &&
utils.findMod(mods,tokenToAnnotationSymbol.get(PURE)) == null &&
( (mods.flags & Flags.PRIVATE) == 0
|| utils.findMod(mods,tokenToAnnotationSymbol.get(SPEC_PUBLIC)) != null
|| utils.findMod(mods,tokenToAnnotationSymbol.get(SPEC_PROTECTED)) != null
)
) {
log.useSource(((JmlTree.JmlAnnotation)a).sourcefile);
log.error(a.pos,"jml.helper.must.be.private",javaMethodTree.name.toString());
}
if (!model) {
checkForConflict(mods,SPEC_PUBLIC,SPEC_PROTECTED);
checkForRedundantSpecMod(mods);
}
} finally {
log.useSource(prev);
}
}
protected void determineQueryAndSecret(JmlMethodDecl tree, JmlSpecs.MethodSpecs mspecs) {
// Query
// If no datagroup is named in a Query annotation, and the
// default datagroup does not exist for the method, then a datagroup
// is created for it. However, if a datagroup is named in a Query annotation
// then it is not created if it does not exist, even if it is the
// name of the default datagroup. The same rule applies to a
// Secret annotation for the method - but in some cases the Query
// annotation will have created it, making for a bit of an anomaly.
// TODO: clarify what the behavior should be and implement here and in the tests
JCAnnotation query = findMod(tree.mods,JmlTokenKind.QUERY);
VarSymbol queryDatagroup = null;
if (query != null) {
List<JCExpression> args = query.getArguments();
Name datagroup = null;
int pos = 0;
if (args.isEmpty()) {
// No argument - use the name of the method
datagroup = tree.name;
pos = query.pos;
} else {
datagroup = getAnnotationStringArg(query);
pos = args.get(0).pos;
}
if (datagroup != null) {
// Find the field with that name
boolean prev = jmlresolve.setAllowJML(true);
Symbol v = rs.findField(env,env.enclClass.type,datagroup,env.enclClass.sym); // FIXME - test that this does not look outside the class and supertypes
jmlresolve.setAllowJML(prev);
if (v instanceof VarSymbol) {
//OK
queryDatagroup = (VarSymbol)v;
// Don't require datagroups to be model fields
// if (!isModel(v)) {
// // TODO - ideally would like this to point to the declaration of the VarSymbol - but it might not exist and we have to find it
// log.error(pos,"jml.datagroup.must.be.model");
// }
} else if (args.size() == 0) {
// Create a default: public model secret JMLDataGroup
JmlTree.Maker maker = JmlTree.Maker.instance(context);
JCTree.JCModifiers nmods = maker.Modifiers(Flags.PUBLIC);
JCTree.JCAnnotation a = maker.Annotation(maker.Type(tokenToAnnotationSymbol.get(JmlTokenKind.MODEL).type),List.<JCExpression>nil());
JCTree.JCAnnotation aa = maker.Annotation(maker.Type(tokenToAnnotationSymbol.get(JmlTokenKind.SECRET).type),List.<JCExpression>nil());
nmods.annotations = List.<JCAnnotation>of(a,aa);
JCTree.JCExpression type = maker.Type(datagroupClass.type);
JCTree.JCVariableDecl vd = maker.VarDef(nmods,datagroup,type,null);
JmlMemberEnter.instance(context).memberEnter(vd,enclosingClassEnv);
JmlTree.JmlTypeClauseDecl td = maker.JmlTypeClauseDecl(vd);
utils.setJML(vd.mods);
vd.accept(this); // attribute it
queryDatagroup = vd.sym;
specs.getSpecs(enclosingClassEnv.enclClass.sym).clauses.append(td);
} else {
log.error(pos,"jml.no.such.field",datagroup);
}
}
}
mspecs.queryDatagroup = queryDatagroup;
// Secret
JCAnnotation secret = findMod(tree.mods,JmlTokenKind.SECRET);
VarSymbol secretDatagroup = null;
if (secret != null) {
List<JCExpression> args = secret.getArguments();
int pos = 0;
Name datagroup = null;
if (args.size()!=1) {
// No argument - error
log.error(secret.pos(),"jml.secret.method.one.arg");
datagroup = null;
} else {
// FIXME - what if the string is a qualified name?
datagroup = getAnnotationStringArg(secret);
pos = args.get(0).pos;
}
if (datagroup != null) {
// Find the model field with that name
boolean prev = jmlresolve.setAllowJML(true);
Symbol v = rs.findField(env,env.enclClass.type,datagroup,env.enclClass.sym);
jmlresolve.setAllowJML(prev);
if (v instanceof VarSymbol) {
secretDatagroup = (VarSymbol)v;
//OK
// if (!isModel(v)) {
// // TODO - ideally would like this to point to the declaration of the VarSymbol - but it might not exist and we have to find it
// log.error(pos,"jml.datagroup.must.be.model");
// }
} else {
log.error(pos,"jml.no.such.field",datagroup);
}
}
}
mspecs.secretDatagroup = secretDatagroup;
if (queryDatagroup != null && queryDatagroup.equals(secretDatagroup)) {
log.error(query.pos,"jml.not.both.query.and.secret");
}
}
public void checkMethodSpecsDirectly(JmlMethodDecl tree) {
// Copying what is done in super.visitMethodDef to setup an environment
// This is only done if the method body is null, otherwise, it is quicker
// to check the method specs with the body
boolean isInJml = ((JmlCheck)chk).setInJml(true);
MethodSymbol m = tree.sym;
Lint lint = env.info.lint.augment(m);
Lint prevLint = chk.setLint(lint);
MethodSymbol prevMethod = chk.setMethod(m);
try {
deferredLintHandler.flush(tree.pos());
chk.checkDeprecatedAnnotation(tree.pos(), m);
// Create a new environment with local scope
// for attributing the method.
Env<AttrContext> localEnv = memberEnter.methodEnv(tree, env);
localEnv.info.lint = lint;
// Enter all type parameters into the local method scope.
for (List<JCTypeParameter> l = tree.typarams; l.nonEmpty(); l = l.tail)
localEnv.info.scope.enterIfAbsent(l.head.type.tsym);
// Attribute all value parameters if needed and add them into the scope
for (List<JCVariableDecl> l = tree.params; l.nonEmpty(); l = l.tail) {
attribStat(l.head, localEnv);
}
{
// Both method specs and specs in statements in the method body use enclosingMethodEnv to resolve \old and \pre
// references against the environment present when the method is called. For method specs this does not change since
// there are no introduced declarations, but it is convenient to use the same variable anyway.
// In this case we don't need to dup it because it does not change.
Env<AttrContext> prevEnv = enclosingMethodEnv;
enclosingMethodEnv = localEnv;
// Note: we cannot just cache the current value of env for use later.
// This is because the envs are presumed to be nested and share their
// symbol tables. Access to scopes is presumed to be nested - in Java
// a reference to an identifier is always resolved in the current scope first,
// not in an enclosing scope. However, JML has the \old operator which gives
// access to the scope at method definition time from within other nestings.
boolean prevAllowJML = jmlresolve.setAllowJML(true);
Env<AttrContext> prevEnv2 = env;
try {
env = localEnv;
JmlSpecs.MethodSpecs sp = tree.methodSpecsCombined; // OR specs.getSpecs(env.enclMethod.sym); if we don't have a tree - FIXME
if (sp != null) sp.cases.accept(this);
deSugarMethodSpecs(tree,tree.methodSpecsCombined);
} finally {
env = prevEnv2;
jmlresolve.setAllowJML(prevAllowJML);
enclosingMethodEnv = prevEnv;
}
}
localEnv.info.scope.leave();
result = tree.type = m.type;
}
finally {
((JmlCheck)chk).setInJml(isInJml);
chk.setLint(prevLint);
}
}
/** Makes an attributed binary operation node
* @param optag the kind of operation (e.g. JCTree.AND)
* @param lhs the left-hand operand
* @param rhs the right-hand operand
* @param pos the soursce position of the node (e.g. of the operator)
* @return the constructed node
*/
protected JCExpression makeBinary(JCTree.Tag optag, JCExpression lhs, JCExpression rhs, int pos) {
if (optag == JCTree.Tag.OR && lhs == falseLit) return rhs;
if (optag == JCTree.Tag.AND && lhs == trueLit) return rhs;
JCBinary tree = make.at(pos).Binary(optag, lhs, rhs);
tree.operator = predefBinOp(optag, lhs.type);
tree.type = tree.operator.type.getReturnType();
return tree;
}
/** Makes an attributed JCIdent node
* @param decl the declaration whose variable is being used in the Ident node
* @param pos the source position at which to place the new node
* @return the constructed node
*/
protected JCIdent makeIdent(JCVariableDecl decl, int pos) {
JCIdent id = make.at(pos).Ident(decl.name);
id.sym = decl.sym;
id.type = decl.type;
return id;
}
/** Makes an attributed node representing a new variable declaration
* such as a local declaration within the body of a method;
* a new symbol is created, though not entered as a member in any scope;
* the sourcefile (if it is a JmlVariableDecl) is not filled in;
* @param name the name of the variable
* @param type the type of the variable
* @param init the (attributed) initialization expression or null
* @param pos the preferred position indication
* @return the AST node
*/
protected JCVariableDecl makeVariableDecl(Name name, Type type, /*@Nullable*/ JCExpression init, int pos) {
JmlTree.Maker factory = JmlTree.Maker.instance(context);
VarSymbol vsym = new VarSymbol(0, name, type, null);
vsym.pos = pos;
JCVariableDecl decl = factory.at(pos).VarDef(vsym,init);
return decl;
}
/** Make an attributed node representing a literal (no node position is set);
* the value is an Integer for boolean (0=false, 1=true) or char types
* @param type The literal's type.
* @param value The literal's value (0/1 for boolean)
*/
protected JCLiteral makeLit(Type type, Object value) {
return make.Literal(type.getTag(), value).setType(type);
}
/** Make an attributed node representing an literal of type int
* @param value the value of the literal
* @param pos the equivalent source location for the node
* @return the constructed node
*/
protected JCLiteral makeIntLiteral(int value, int pos) {
JmlTree.Maker factory = JmlTree.Maker.instance(context);
JCLiteral lit = factory.at(pos).Literal(TypeTag.INT,value);
lit.type = syms.intType;
return lit;
}
// FIXME - is there a faster way to do this?
/** Returns a Symbol (in the current compilation context) for the given operator
* with the given (lhs) type
* @param op the operator (e.g. JCTree.AND)
* @param type the type of the lhs, for disambiguation
* @return the method Symbol for the operation
*/
protected Symbol predefBinOp(JCTree.Tag op, Type type) {
Name n = TreeInfo.instance(context).operatorName(op);
Scope.Entry e = syms.predefClass.members().lookup(n);
while (e.sym != null) {
if (e.sym instanceof MethodSymbol) {
MethodSymbol msym = (MethodSymbol)e.sym;
Type t = msym.getParameters().head.type;
if (t == type || (!type.isPrimitive() && t == syms.objectType)) return e.sym;
}
e = e.next();
}
return null;
}
/** Does a custom desugaring of the method specs. It adds in the type
* restrictions (non_null) and purity, desugars lightweight and heavyweight
* to behavior cases, adds in defaults, desugars signals_only and denests.
* It does not merge multiple specification cases (so we can give better
* error messages) and it does not pull in inherited specs. So in the end
* the desugared specs consist of a set of specification cases, each of
* which consists of a series of clauses without nesting; there is no
* combining of requires or other clauses and no insertion of preconditions
* into the other clauses.
* <BR>
* A method specification can have
* some preliminary clauses and then fork into multiple specification cases
* or into the elements of a clause group. We accumulate a prefix - a list
* of clauses that are common to all subsequent specification cases. Then
* we replicate the list and append the subsequent clauses to the appropriate
* replicate. Note that the replication is shallow, so the replicates contain
* equal object references to the common clauses.
* @param decl the method declaration whose specs are being desugared
* @param msp the method specs being desugared
*/
// FIXME - base this on symbol rather than decl, but first check when all
// the modifiers are added into the symbol
// FIXME - check everything for position information
public void deSugarMethodSpecs(JmlMethodDecl decl, JmlSpecs.MethodSpecs msp) {
//log.getWriter(WriterKind.NOTICE).println("DESUGARING " + decl.sym.owner + " " + decl.sym + decl.toString());
if (msp == null) return;
JmlMethodSpecs methodSpecs = msp.cases;
Env<AttrContext> prevEnv = env;
Symbol owner = decl.sym.owner;
boolean specsCompletelyEmpty = methodSpecs.cases.isEmpty();
env = enter.getEnv((ClassSymbol)decl.sym.owner);
JCMethodDecl prevEnclMethod = env == null ? null : env.enclMethod;
if (env != null) env.enclMethod = decl; // This is here to handle the situation when deSugarMethodSPecs
// is called from JmlSpecs to provide on demand desugaring. In that case we are not within
// a tree hierarchy, so we have to set the enclosing method declaration directly. If we were only
// calling deSugarMethodSpecs during AST attribution, then we would not need to set env or adjust
// env.enclMethod.
if (env == null) env = enclosingMethodEnv;
// DO a defensive check
if (methodSpecs.decl != decl) {
// FIXME this fails
//log.error("jml.internal","UNEXPECTED MISMATCH when desugaring specs " + decl.sym + " " + methodSpecs.decl.sym);
}
JavaFileObject prevSource = log.useSource(decl.sourcefile);
EndPosTable endPosTable = log.currentSource().getEndPosTable();
try {
JmlTree.Maker jmlMaker = (JmlTree.Maker)make;
JCAnnotation pure = findMod(msp.mods,JmlTokenKind.PURE);
if (pure == null) pure = findMod(msp.mods,JmlTokenKind.FUNCTION);
desugaringPure = pure != null;
if (!desugaringPure) {
if (enclosingClassEnv != null) desugaringPure = (pure = findMod(enclosingClassEnv.enclClass.mods,JmlTokenKind.PURE)) != null;
}
if (specsCompletelyEmpty && !desugaringPure && !decl.sym.isConstructor()) {
// If the local specs are completely empty, then the desugaring depends on what is inherited:
// If the method at hand does not override anything, then we go on to add the default specs;
// If the method at hand does override some parent methods, then we add no additional specs here
if (utils.parents(decl.sym).size() > 1) { // The override list will always include the method itself
JmlMethodSpecs newspecs = jmlMaker.at(methodSpecs.pos).JmlMethodSpecs(List.<JmlTree.JmlSpecificationCase>nil());
newspecs.decl = methodSpecs.decl;
methodSpecs.deSugared = newspecs;
return;
}
}
JCLiteral nulllit = make.Literal(TypeTag.BOT, null).setType(syms.objectType.constType(null));
// A list in which to collect clauses
ListBuffer<JmlMethodClause> clauses = new ListBuffer<JmlMethodClause>();
// Add a precondition for each nonnull parameter
for (JCVariableDecl p : decl.params) {
if (p.type == null && p.sym != null) p.type = p.sym.type; // FIXME - A hack - why is p.type null - has the corresponding class not been Attributed?
if (!p.type.isPrimitive()) {
JmlVariableDecl jp = (JmlVariableDecl)p;
boolean isNonnull = specs.isNonNull(jp);
JCAnnotation nonnull = jp.specsDecl == null ? null : findMod(jp.specsDecl.mods,JmlTokenKind.NONNULL); // FIXME - this is not necessarily the correct position - could be in a .jml file
if (nonnull == null) nonnull = findMod(jp.mods,JmlTokenKind.NONNULL); // FIXME - this is not necessarily the correct position - could be in a .jml file
if (isNonnull) {
JCTree treeForPos = nonnull == null ? jp : nonnull;
int endPos = treeForPos.getEndPosition(endPosTable);
JCIdent id = makeIdent(p,treeForPos.pos);
endPosTable.storeEnd(id, endPos);
JCExpression e = makeBinary(JCTree.Tag.NE,id,nulllit,treeForPos.pos);
endPosTable.storeEnd(e, endPos);
JmlMethodClauseExpr clause = jmlMaker.JmlMethodClauseExpr(JmlTokenKind.REQUIRES,e);
clause.pos = e.pos;
clause.sourcefile = decl.source(); // FIXME - should be set by where the nonnull annotation is
endPosTable.storeEnd(clause,endPos);
clauses.append(clause);
}
}
}
// Add a nonnull postcondition on the return type
JCAnnotation nonnullAnnotation = findMod(decl.mods,JmlTokenKind.NONNULL);
int annotationPos = (nonnullAnnotation != null) ? nonnullAnnotation.getPreferredPosition() : decl.getPreferredPosition();
int annotationEnd = (nonnullAnnotation != null) ? nonnullAnnotation.getEndPosition(endPosTable) : decl.getPreferredPosition()+1;
// restype is null for constructors, possibly void for methods
if (decl.restype != null && decl.restype.type.getTag() != TypeTag.VOID && !decl.restype.type.isPrimitive()) {
boolean isNonnull = specs.isNonNull(decl.sym,decl.sym.enclClass());
if (isNonnull) {
JCExpression id = jmlMaker.JmlSingleton(JmlTokenKind.BSRESULT);
id.type = decl.restype.type;
JCExpression e = makeBinary(JCTree.Tag.NE,id,nulllit,0);
id.pos = annotationPos;
e.pos = annotationPos;
endPosTable.storeEnd(e,annotationEnd);
endPosTable.storeEnd(id,annotationEnd);
JmlTokenKind prev = currentClauseType;
currentClauseType = JmlTokenKind.ENSURES;
attribExpr(e,env);
currentClauseType = prev;
JmlMethodClauseExpr cl = jmlMaker.at(annotationPos).JmlMethodClauseExpr(JmlTokenKind.ENSURES,e);
cl.sourcefile = decl.source();
endPosTable.storeEnd(cl,annotationEnd);
clauses.append(cl);
}
}
// Add an assignable clause if the method is pure and has no assignable clause
JmlMethodClause clp = null;
if (desugaringPure) {
if (decl.sym.isConstructor()) {
JCIdent t = jmlMaker.Ident(names._this);
t.type = decl.sym.owner.type;
t.sym = decl.sym.owner;
clp = jmlMaker.JmlMethodClauseStoreRef(JmlTokenKind.ASSIGNABLE,
List.<JCExpression>of(jmlMaker.Select(t,(Name)null)));
} else {
clp = jmlMaker.JmlMethodClauseStoreRef(JmlTokenKind.ASSIGNABLE,
List.<JCExpression>of(jmlMaker.JmlStoreRefKeyword(JmlTokenKind.BSNOTHING)));
}
if (pure != null) {
clp.pos = pure.pos;
endPosTable.storeEnd(clp,pure.getEndPosition(endPosTable));
} else {
// This branch is defensive - should never happen
clp.pos = Position.NOPOS;
endPosTable.storeEnd(clp,Position.NOPOS);
}
// clauses.append(clp);
// clp = null;
}
// We already have some implicit method spec clauses in 'clauses'
// Desugar any specification cases
JmlMethodSpecs newspecs;
if (!methodSpecs.cases.isEmpty()) {
ListBuffer<JmlSpecificationCase> allcases = new ListBuffer<JmlSpecificationCase>();
ListBuffer<JmlSpecificationCase> allitcases = new ListBuffer<JmlSpecificationCase>();
ListBuffer<JmlSpecificationCase> allfecases = new ListBuffer<JmlSpecificationCase>();
for (JmlSpecificationCase c: methodSpecs.cases) {
ListBuffer<JmlMethodClause> cl = new ListBuffer<JmlMethodClause>();
cl.appendList(clauses);
JCModifiers mods = c.modifiers;
if (c.token == null) mods = decl.mods;
ListBuffer<JmlSpecificationCase> newcases = deNest(cl,List.<JmlSpecificationCase>of(c),null,decl,mods);
for (JmlSpecificationCase cs: newcases) {
// Note: a model program spec case has no clauses
if (cs.clauses != null) {
boolean hasAssignableClause = false;
boolean hasAccessibleClause = false;
for (JmlMethodClause clm: cs.clauses) {
if (clm.token == JmlTokenKind.ASSIGNABLE) {
hasAssignableClause = true;
}
if (clm.token == JmlTokenKind.ACCESSIBLE) {
hasAccessibleClause = true;
}
}
if (!hasAssignableClause && clp != null) {
cs.clauses = cs.clauses.append(clp);
}
if (!hasAccessibleClause) {
JmlMethodClause defaultClause;
if (decl.sym.isConstructor()) {
JCIdent t = jmlMaker.Ident(names._this);
t.type = decl.sym.owner.type;
t.sym = decl.sym.owner;
defaultClause = jmlMaker.JmlMethodClauseStoreRef(JmlTokenKind.ACCESSIBLE,
List.<JCExpression>of(t,jmlMaker.Select(t,(Name)null)));
} else {
defaultClause = jmlMaker.JmlMethodClauseStoreRef(JmlTokenKind.ACCESSIBLE,
List.<JCExpression>of(jmlMaker.JmlStoreRefKeyword(JmlTokenKind.BSEVERYTHING)));
}
cs.clauses = cs.clauses.append(defaultClause);
}
}
}
allcases.appendList(newcases);
}
for (JmlSpecificationCase c: methodSpecs.impliesThatCases) {
ListBuffer<JmlMethodClause> cl = new ListBuffer<JmlMethodClause>();
cl.appendList(clauses);
JCModifiers mods = c.modifiers;
if (c.token == null) mods = decl.mods;
ListBuffer<JmlSpecificationCase> newcases = deNest(cl,List.<JmlSpecificationCase>of(c),null,decl,mods);
for (JmlSpecificationCase cs: newcases) {
// Note: a model program spec case has no clauses
if (cs.clauses != null) {
boolean hasAssignableClause = false;
boolean hasAccessibleClause = false;
for (JmlMethodClause clm: cs.clauses) {
if (clm.token == JmlTokenKind.ASSIGNABLE) {
hasAssignableClause = true;
}
if (clm.token == JmlTokenKind.ACCESSIBLE) {
hasAccessibleClause = true;
}
}
if (!hasAssignableClause && clp != null) {
cs.clauses = cs.clauses.append(clp);
}
if (!hasAccessibleClause) {
JmlMethodClause defaultClause;
if (decl.sym.isConstructor()) {
JCIdent t = jmlMaker.Ident(names._this);
t.type = decl.sym.owner.type;
t.sym = decl.sym.owner;
defaultClause = jmlMaker.JmlMethodClauseStoreRef(JmlTokenKind.ACCESSIBLE,
List.<JCExpression>of(t,jmlMaker.Select(t,(Name)null)));
} else {
defaultClause = jmlMaker.JmlMethodClauseStoreRef(JmlTokenKind.ACCESSIBLE,
List.<JCExpression>of(jmlMaker.JmlStoreRefKeyword(JmlTokenKind.BSEVERYTHING)));
}
cs.clauses = cs.clauses.append(defaultClause);
}
}
}
allitcases.appendList(newcases);
}
for (JmlSpecificationCase c: methodSpecs.forExampleCases) {
ListBuffer<JmlMethodClause> cl = new ListBuffer<JmlMethodClause>();
cl.appendList(clauses);
JCModifiers mods = c.modifiers;
if (c.token == null) mods = decl.mods;
ListBuffer<JmlSpecificationCase> newcases = deNest(cl,List.<JmlSpecificationCase>of(c),null,decl,mods);
for (JmlSpecificationCase cs: newcases) {
// Note: a model program spec case has no clauses
if (cs.clauses != null) {
boolean hasAssignableClause = false;
boolean hasAccessibleClause = false;
for (JmlMethodClause clm: cs.clauses) {
if (clm.token == JmlTokenKind.ASSIGNABLE) {
hasAssignableClause = true;
}
if (clm.token == JmlTokenKind.ACCESSIBLE) {
hasAccessibleClause = true;
}
}
if (!hasAssignableClause && clp != null) {
cs.clauses = cs.clauses.append(clp);
}
if (!hasAccessibleClause) {
JmlMethodClause defaultClause;
if (decl.sym.isConstructor()) {
JCIdent t = jmlMaker.Ident(names._this);
t.type = decl.sym.owner.type;
t.sym = decl.sym.owner;
defaultClause = jmlMaker.JmlMethodClauseStoreRef(JmlTokenKind.ACCESSIBLE,
List.<JCExpression>of(t,jmlMaker.Select(t,(Name)null)));
} else {
defaultClause = jmlMaker.JmlMethodClauseStoreRef(JmlTokenKind.ACCESSIBLE,
List.<JCExpression>of(jmlMaker.JmlStoreRefKeyword(JmlTokenKind.BSEVERYTHING)));
}
cs.clauses = cs.clauses.append(defaultClause);
}
}
}
allfecases.appendList(newcases);
}
newspecs = jmlMaker.at(methodSpecs.pos).JmlMethodSpecs(allcases.toList());
newspecs.impliesThatCases = allitcases.toList();
newspecs.forExampleCases = allfecases.toList();
} else {
if (clp != null) clauses.append(clp);
if (!clauses.isEmpty()) {
JCModifiers mods = jmlMaker.at(decl.pos).Modifiers(decl.mods.flags & Flags.AccessFlags);
JmlSpecificationCase c = jmlMaker.JmlSpecificationCase(mods,false,null,null,clauses.toList());
newspecs = jmlMaker.JmlMethodSpecs(List.<JmlSpecificationCase>of(c));
} else {
newspecs = methodSpecs;
}
}
newspecs.decl = methodSpecs.decl;
methodSpecs.deSugared = newspecs;
} finally {
if (env != null) env.enclMethod = prevEnclMethod;
env = prevEnv;
log.useSource(prevSource);
}
}
// public void addDefaultClauses(List<JmlMethodClause> clauses) {
// ListBuffer<JmlMethodClause> cl = new ListBuffer<JmlMethodClause>();
// cl.appendList(clauses);
// JCModifiers mods = c.modifiers;
// if (c.token == null) mods = decl.mods;
// ListBuffer<JmlSpecificationCase> newcases = deNest(cl,List.<JmlSpecificationCase>of(c),null,decl,mods);
// for (JmlSpecificationCase cs: newcases) {
// // Note: a model program spec case has no clauses
// if (cs.clauses != null) {
// boolean hasAssignableClause = false;
// boolean hasAccessibleClause = false;
// for (JmlMethodClause clm: cs.clauses) {
// if (clm.token == JmlTokenKind.ASSIGNABLE) {
// hasAssignableClause = true;
// }
// if (clm.token == JmlTokenKind.ACCESSIBLE) {
// hasAccessibleClause = true;
// }
// }
// if (!hasAssignableClause && clp != null) {
// cs.clauses = cs.clauses.append(clp);
// }
// if (!hasAccessibleClause) {
// JmlMethodClause defaultClause = jmlMaker.JmlMethodClauseStoreRef(JmlTokenKind.ACCESSIBLE,
// List.<JCExpression>of(jmlMaker.JmlStoreRefKeyword(JmlTokenKind.BSEVERYTHING)));
// cs.clauses = cs.clauses.append(defaultClause);
// }
// }
// }
//
// }
boolean desugaringPure = false;
// FIXME - this ignores anything after a clause group. That is OK in strict JML. DO we want it? There is no warning.
public ListBuffer<JmlSpecificationCase> deNest(ListBuffer<JmlMethodClause> prefix, List<JmlSpecificationCase> cases, /*@ nullable */JmlSpecificationCase parent, JmlMethodDecl decl, JCModifiers mods) {
ListBuffer<JmlSpecificationCase> newlist = new ListBuffer<JmlSpecificationCase>();
if (cases.isEmpty()) {
if (parent != null) {
addDefaultSignalsOnly(prefix,parent,decl);
newlist.append(((JmlTree.Maker)make).at(parent.pos).JmlSpecificationCase(mods,parent.code,parent.token,parent.also,prefix.toList()));
}
else {
// ERROR - not allowed to have an empty collection of specification cases
// at the top level
log.error("jml.internal","Unexpected empty set of specification cases at the top-level in JmlAttr");
}
} else if (cases.size() == 1) {
// common case that just avoids copying the prefix
JmlSpecificationCase c = cases.get(0);
handleCase(parent, decl, newlist, c, prefix, mods);
} else {
for (JmlSpecificationCase cse: cases) {
ListBuffer<JmlMethodClause> pr = copy(prefix);
handleCase(parent, decl, newlist, cse, pr, mods);
}
}
return newlist;
}
// FIXME - document; remove parent
protected void addDefaultSignalsOnly(ListBuffer<JmlMethodClause> prefix, JmlSpecificationCase parent, JmlMethodDecl decl) {
boolean anySOClause = false;
for (JmlMethodClause cl: prefix) {
if (cl.token == JmlTokenKind.SIGNALS_ONLY) anySOClause = true;
}
if (!anySOClause) {
DiagnosticPosition p = decl.pos();
if (decl.thrown != null && !decl.thrown.isEmpty()) p = decl.thrown.get(0).pos();
ListBuffer<JCExpression> list = new ListBuffer<JCExpression>();
if (decl.thrown != null) list.addAll(decl.thrown);
list.add(factory.at(p).Type(syms.runtimeExceptionType));
JmlMethodClauseSignalsOnly cl = (factory.at(p).JmlMethodClauseSignalsOnly(JmlTokenKind.SIGNALS_ONLY, list.toList()));
cl.sourcefile = log.currentSourceFile();
prefix.add(cl);
}
}
/** Makes a copy of the list of clauses.
*/
public ListBuffer<JmlMethodClause> copy(ListBuffer<JmlMethodClause> p) {
ListBuffer<JmlMethodClause> copy = new ListBuffer<JmlMethodClause>();
copy.appendList(p);
return copy;
}
protected void handleCase(JmlSpecificationCase parent, JmlMethodDecl decl,
ListBuffer<JmlSpecificationCase> newlist, JmlSpecificationCase cse,
ListBuffer<JmlMethodClause> pr, JCModifiers mods) {
if (cse.token == JmlTokenKind.MODEL_PROGRAM) {
newlist.append(cse); // FIXME - check that model programs are only at the outer level
return;
}
if (parent == null) {
JmlTree.Maker jmlF = (JmlTree.Maker)make;
JmlTokenKind t = cse.token;
if (t == JmlTokenKind.NORMAL_BEHAVIOR || t == JmlTokenKind.NORMAL_EXAMPLE) {
JmlMethodClauseSignals cl = jmlF.at(cse.pos).JmlMethodClauseSignals(JmlTokenKind.SIGNALS,null,falseLit);
cl.sourcefile = cse.sourcefile;
pr.append(cl);
} else if (t == JmlTokenKind.EXCEPTIONAL_BEHAVIOR || t == JmlTokenKind.EXCEPTIONAL_EXAMPLE) {
JmlMethodClauseExpr cl = jmlF.at(cse.pos).JmlMethodClauseExpr(JmlTokenKind.ENSURES,falseLit);
cl.sourcefile = cse.sourcefile;
pr.append(cl);
}
}
newlist.appendList(deNestHelper(pr,cse.clauses,parent==null?cse:parent,decl,mods));
}
public ListBuffer<JmlSpecificationCase> deNestHelper(ListBuffer<JmlMethodClause> prefix, List<JmlMethodClause> clauses, JmlSpecificationCase parent, JmlMethodDecl decl, JCModifiers mods) {
for (JmlMethodClause m: clauses) {
if (m instanceof JmlMethodClauseGroup) {
return deNest(prefix,((JmlMethodClauseGroup)m).cases, parent,decl, mods);
}
JmlTokenKind t = m.token;
if (t == JmlTokenKind.ENSURES) {
if (parent.token == JmlTokenKind.EXCEPTIONAL_BEHAVIOR || parent.token == JmlTokenKind.EXCEPTIONAL_EXAMPLE) {
log.error(m.pos,"jml.misplaced.clause","Ensures","exceptional");
continue;
}
} else if (t == JmlTokenKind.SIGNALS) {
if (parent.token == JmlTokenKind.NORMAL_BEHAVIOR || parent.token == JmlTokenKind.NORMAL_EXAMPLE) {
log.error(m.pos,"jml.misplaced.clause","Signals","normal");
continue;
}
} else if (t == JmlTokenKind.SIGNALS_ONLY) {
if (parent.token == JmlTokenKind.NORMAL_BEHAVIOR || parent.token == JmlTokenKind.NORMAL_EXAMPLE) {
log.error(m.pos,"jml.misplaced.clause","Signals_only","normal");
continue;
}
int count = 0;
for (JmlMethodClause mc: prefix) {
if (mc.token == JmlTokenKind.SIGNALS_ONLY) count++;
}
if (count > 0) {
log.error(m.pos,"jml.multiple.signalsonly");
}
} else if (desugaringPure && t == JmlTokenKind.ASSIGNABLE) {
JmlMethodClauseStoreRef asg = (JmlMethodClauseStoreRef)m;
if (decl.sym.isConstructor()) {
// A pure constructor allows assigning to class member fields
// So if there is an assignable clause the elements of the clause
// may only be members of the class
for (JCTree tt: asg.list) {
if (tt instanceof JmlStoreRefKeyword) {
if (((JmlStoreRefKeyword)tt).token == JmlTokenKind.BSNOTHING) {
// OK
} else {
jmlerror(m,"jml.pure.constructor",tt);
}
} else if (tt instanceof JCIdent) {
// non-static Simple identifier is OK
// If the owner of the field is an interface, it
// is by default static. However, it might be a
// JML field marked as instance.
VarSymbol sym = (VarSymbol)((JCIdent)tt).sym;
if (utils.isJMLStatic(sym)) {
jmlerror(tt,"jml.pure.constructor",tt);
}
} else if (tt instanceof JCTree.JCFieldAccess) {
JCFieldAccess fa = (JCFieldAccess)tt;
boolean ok = false;
if (fa.selected instanceof JCIdent) {
Symbol sym = ((JCIdent)fa.selected).sym;
if (sym.name == names._this || sym.name == names._super) {
Symbol fasym = fa.sym;
if (fa.sym == null || !utils.isJMLStatic(fasym)) ok = true;
}
}
if (!ok) jmlerror(tt,"jml.pure.constructor",tt);
} else {
// FIXME - also allow this.* or super.* ?
jmlerror(tt,"jml.pure.constructor",tt);
}
}
} else {
for (JCTree tt: asg.list) {
if (tt instanceof JmlStoreRefKeyword &&
((JmlStoreRefKeyword)tt).token == JmlTokenKind.BSNOTHING) {
// OK
} else if (isFunction(decl.sym)) {
jmlerror(tt,"jml.function.method",tt);
} else {
jmlerror(tt,"jml.pure.method",tt);
}
}
}
}
prefix.append(m);
}
addDefaultSignalsOnly(prefix,parent,decl);
ListBuffer<JmlSpecificationCase> newlist = new ListBuffer<JmlSpecificationCase>();
JmlSpecificationCase sc = (((JmlTree.Maker)make).JmlSpecificationCase(parent,prefix.toList()));
sc.modifiers = mods;
newlist.append(sc);
return newlist;
}
// /** Determines the default nullability for the compilation unit
// * @return true if the default is non_null, false if the default is nullable
// */
// public boolean determineDefaultNullability() {
// return false; // FIXME
// }
protected void adjustVarDef(JCVariableDecl tree, Env<AttrContext> localEnv) {
if (!forallOldEnv) return;
// Undo the super class
if ((tree.mods.flags & STATIC) != 0 ||
(env.enclClass.sym.flags() & INTERFACE) != 0)
localEnv.info.staticLevel--;
// FIXME - actually, any initializer in an interface needs adjustment - might be instance
// Redo, just dependent on static
// FIXME - actually depends on whether the containing method is static
// FIXME - check whetehr all specs are properly checked for stastic/nonstatic use of fields and methods
// including material inherited from interfaces where the defaults are different
if ((localEnv.enclMethod.mods.flags & STATIC) != 0 ||
(env.enclClass.sym.flags() & INTERFACE) != 0)
localEnv.info.staticLevel++;
}
// /** This is overridden in order to check the JML modifiers of the variable declaration */
// @Override
// public void visitVarDef(JCVariableDecl tree) {
// attribAnnotationTypes(tree.mods.annotations,env); // FIXME - we should not need these two lines I think, but otherwise we get NPE faults on non_null field declarations
// for (JCAnnotation a: tree.mods.annotations) a.type = a.annotationType.type;
// super.visitVarDef(tree);
// // Anonymous classes construct synthetic members (constructors at least)
// // which are not JML nodes.
// FieldSpecs fspecs = specs.getSpecs(tree.sym);
// if (fspecs != null) {
// boolean prev = jmlresolve.setAllowJML(true);
// for (JmlTypeClause spec: fspecs.list) spec.accept(this);
// jmlresolve.setAllowJML(prev);
// }
//
// // These are checked later in visitJmlVariableDecl
//// // Check the mods after the specs, because the modifier checks depend on
//// // the specification clauses being attributed
//// if (tree instanceof JmlVariableDecl) {
//// checkVarMods((JmlVariableDecl)tree);
//// }
// }
// MAINTENANCE ISSUE - copied from super class
@Override
protected void checkInit(JCTree tree,
Env<AttrContext> env,
VarSymbol v,
boolean onlyWarning) {
if (env.tree instanceof JmlQuantifiedExpr) return;
// System.err.println(v + " " + ((v.flags() & STATIC) != 0) + " " +
// tree.pos + " " + v.pos + " " +
// Resolve.isStatic(env));//DEBUG
// A forward reference is diagnosed if the declaration position
// of the variable is greater than the current tree position
// and the tree and variable definition occur in the same class
// definition. Note that writes don't count as references.
// This check applies only to class and instance
// variables. Local variables follow different scope rules,
// and are subject to definite assignment checking.
if ((env.info.enclVar == v || v.pos > tree.pos) &&
v.owner.kind == TYP &&
enclosingInitEnv(env) != null && // FIXME - this line used to be a check for canOwnInitializer, which was only used here and defined in Attr.java
v.owner == env.info.scope.owner.enclClass() &&
((v.flags() & STATIC) != 0) == Resolve.isStatic(env) &&
(!env.tree.hasTag(ASSIGN) ||
TreeInfo.skipParens(((JCAssign) env.tree).lhs) != tree)) {
String suffix = (env.info.enclVar == v) ?
"self.ref" : "forward.ref";
if (!onlyWarning || isStaticEnumField(v)) {
// DRC - changed the following line to avoid complaints about forward references from invariants
if (currentClauseType == null || currentClauseType == JmlTokenKind.JMLDECL) {
log.error(tree.pos(), "illegal.forward.ref");
}
} else if (useBeforeDeclarationWarning) {
log.warning(tree.pos(), suffix, v);
}
}
v.getConstValue(); // ensure initializer is evaluated
checkEnumInitializer(tree, env, v);
}
public JmlTokenKind[] allowedFieldModifiers = new JmlTokenKind[] {
SPEC_PUBLIC, SPEC_PROTECTED, MODEL, GHOST,
NONNULL, NULLABLE, INSTANCE, MONITORED, SECRET,
PEER, REP, READONLY // FIXME - allowing these until the rules are really implemented
};
public JmlTokenKind[] allowedGhostFieldModifiers = new JmlTokenKind[] {
GHOST, NONNULL, NULLABLE, INSTANCE, MONITORED, SECRET,
PEER, REP, READONLY // FIXME - allowing these until the rules are really implemented
};
public JmlTokenKind[] allowedModelFieldModifiers = new JmlTokenKind[] {
MODEL, NONNULL, NULLABLE, INSTANCE, SECRET,
PEER, REP, READONLY // FIXME - allowing these until the rules are really implemented
};
public JmlTokenKind[] allowedFormalParameterModifiers = new JmlTokenKind[] {
NONNULL, NULLABLE, READONLY, REP, PEER, SECRET,
};
public JmlTokenKind[] allowedLocalVarModifiers = new JmlTokenKind[] {
NONNULL, NULLABLE, GHOST, UNINITIALIZED, READONLY, REP, PEER, SECRET
};
public void checkVarMods(JmlVariableDecl tree) {
if (tree.name == names.error || tree.type.isErroneous()) return;
JCModifiers mods = tree.mods;
String kind = (tree.mods.flags & Flags.PARAMETER) != 0 ? "parameter" : "field";
if (tree.specsDecl != null) {
JCModifiers jmlmods = tree.specsDecl.mods;
attribAnnotationTypes(jmlmods.annotations,env);
// for (JCAnnotation a: tree.mods.annotations) { // Iterating over annotations in .java file
// JCAnnotation aa = utils.findMod(jmlmods, a.type.tsym); // CHeck if it is used in .jml file
// if (aa == null) { // If not, report that it is ignored
// log.warning(a.pos(), "jml.java.annotation.superseded", kind , tree.name.toString(), a.toString());
// }
// }
mods = tree.specsDecl.mods;
log.useSource(tree.specsDecl.source());
}
boolean inJML = utils.isJML(mods);
boolean ownerInJML = utils.isJML(tree.sym.owner.flags());
boolean ghost = isGhost(mods);
boolean model = isModel(mods);
boolean modelOrGhost = model || ghost;
if (tree.sym.owner.kind == Kinds.TYP) { // Field declarations
if (ghost) {
allAllowed(mods.annotations, allowedGhostFieldModifiers, "ghost field declaration");
} else if (model) {
allAllowed(mods.annotations, allowedModelFieldModifiers, "model field declaration");
} else {
allAllowed(mods.annotations, allowedFieldModifiers, "field declaration");
}
if (!inJML && isInJmlDeclaration && modelOrGhost) {
if (ghost) utils.error(tree.sourcefile,tree.pos,"jml.no.nested.ghost.type");
else utils.error(tree.sourcefile,tree.pos,"jml.no.nested.model.type");
} else if (inJML && !modelOrGhost && !isInJmlDeclaration) {
utils.error(tree.sourcefile,tree,"jml.missing.ghost.model");
} else if (!inJML && modelOrGhost) {
utils.error(tree.sourcefile,tree.pos,"jml.ghost.model.on.java");
}
JmlAnnotation a;
if (!model) {
checkForConflict(mods,SPEC_PUBLIC,SPEC_PROTECTED);
checkForRedundantSpecMod(mods);
}
a = utils.findMod(mods,INSTANCE);
if (a != null && isStatic(tree.mods)) {
utils.error(a.sourcefile,a.pos(),"jml.conflicting.modifiers","instance","static");
}
if (model && ((tree.mods.flags & Flags.FINAL)!=0)) {
a = utils.findMod(tree.mods,MODEL);
utils.error(a.sourcefile,a.pos(),"jml.conflicting.modifiers","model","final");
}
checkForConflict(mods,NONNULL,NULLABLE);
if (tree.specsDecl != null) log.useSource(tree.source());
} else if ((tree.mods.flags & Flags.PARAMETER) != 0) { // formal parameters
JCModifiers pmods = tree.mods;
if (tree.specsDecl != null) {
JCModifiers jmlmods = tree.specsDecl.mods;
attribAnnotationTypes(jmlmods.annotations,env);
// Appropriate use of annotations is checked below.
// for (JCAnnotation a: tree.mods.annotations) {
// JmlAnnotation aa = utils.findMod(jmlmods, a.type.tsym);
// if (aa == null) { // FIXME _ check on sourcefile
// JavaFileObject psource = log.useSource(tree.source());
// log.warning(a.pos(), "jml.java.annotation.superseded", "parameter", tree.name.toString(), a.toString());
// log.useSource(psource);
// }
// }
kind = "parameter";
pmods = jmlmods;
}
if (tree.specsDecl != null) log.useSource(tree.specsDecl.source());
allAllowed(pmods.annotations, allowedFormalParameterModifiers, "formal parameter");
checkForConflict(pmods,NONNULL,NULLABLE);
if (tree.specsDecl != null) log.useSource(tree.source());
} else { // local declaration
kind = "local variable declaration";
allAllowed(tree.mods.annotations, allowedLocalVarModifiers, kind);
if (inJML && !ghost && !isInJmlDeclaration && !ownerInJML) {
utils.error(tree.source(),tree.pos,"jml.missing.ghost");
} else if (!inJML && ghost) {
utils.error(tree.source(),tree.pos,"jml.ghost.on.java");
}
checkForConflict(tree.mods,NONNULL,NULLABLE);
}
if (tree.specsDecl != null) {
JavaFileObject prev = log.useSource(tree.specsDecl.source());
checkSameAnnotations(tree.mods,tree.specsDecl.mods,kind,tree.name.toString());
log.useSource(prev);
}
// Check that types match
if (tree.specsDecl != null) { // tree.specsDecl can be null if there is a parsing error
JavaFileObject prev = log.useSource(tree.specsDecl.source());
Type specType = attribType(tree.specsDecl.vartype,env);
log.useSource(prev);
if (specType != null) {
if (!Types.instance(context).isSameType(tree.sym.type,specType)) {
if (specType.hasTag(TypeTag.TYPEVAR) && tree.sym.type.hasTag(TypeTag.TYPEVAR)) {
if (specType.tsym.name != tree.sym.type.tsym.name) {
utils.errorAndAssociatedDeclaration(tree.specsDecl.source(),tree.specsDecl.vartype.pos(),
tree.sourcefile, tree.pos(),
"jml.mismatched.field.types",tree.name,
tree.sym.enclClass().getQualifiedName()+"."+tree.sym.name,
specType.tsym,
tree.sym.type.tsym);
}
} else {
utils.errorAndAssociatedDeclaration(tree.specsDecl.source(),tree.specsDecl.vartype.pos(),
tree.sourcefile, tree.pos(),
"jml.mismatched.field.types",tree.name,
tree.sym.enclClass().getQualifiedName()+"."+tree.sym.name,
specType,
tree.sym.type);
}
}
}
}
}
/** This method checks specifications of field declarations that must be checked after all
* fields have been initially processed, because they might have forward declarations.
*/
public void checkVarMods2(JmlVariableDecl tree) {
if (tree.name == names.error || tree.type.isErroneous()) return;
JCModifiers mods = tree.mods;
boolean inJML = utils.isJML(mods);
boolean ownerInJML = utils.isJML(tree.sym.owner.flags());
boolean ghost = isGhost(mods);
boolean model = isModel(mods);
boolean modelOrGhost = model || ghost;
inVarDecl = tree;
// Secret
JmlAnnotation secret = findMod(mods,JmlTokenKind.SECRET);
VarSymbol secretDatagroup = null;
if (secret != null) {
List<JCExpression> args = secret.getArguments();
if (!args.isEmpty()) {
utils.error(secret.sourcefile,args.get(0).pos,"jml.no.arg.for.field.secret");
}
}
// Note that method parameters, which belong to Methods, have
// null FieldSpecs
if (tree.sym.owner.kind == Kinds.TYP) {
// Check all datagroups that the field is in
JmlSpecs.FieldSpecs fspecs = specs.getSpecs(tree.sym);
long prevVisibility = jmlVisibility;
JmlTokenKind prevClauseType = currentClauseType;
if (fspecs != null) try {
for (JmlTypeClause tc: fspecs.list) {
if (tc.token == JmlTokenKind.IN) {
JmlTypeClauseIn tcin = (JmlTypeClauseIn)tc;
currentClauseType = JmlTokenKind.IN;
jmlVisibility = tcin.parentVar.mods.flags & Flags.AccessFlags;
for (JmlGroupName g: tcin.list) {
attributeGroup(g);
if (g.sym == null) continue; // Happens if there was an error in finding g
if (hasAnnotation(g.sym,JmlTokenKind.SECRET) != (secret != null)) {
if (secret != null) {
log.error(tcin.pos,"jml.secret.field.in.nonsecret.datagroup");
} else {
log.error(tcin.pos,"jml.nonsecret.field.in.secret.datagroup");
}
}
}
}
}
} finally {
jmlVisibility = prevVisibility;
currentClauseType = prevClauseType;
inVarDecl = null;
}
// Allow Java fields to be datagroups
// if (secret != null && fspecs.list.isEmpty()) {
// // Not in any datagroups
// // Had better be model itself
// if (!isModel(fspecs.mods)) {
// log.error(tree.pos,"jml.secret.field.model.or.in.secret.datagroup");
// }
// }
}
}
// // FIXME - this should be done in MemberEnter, not here
// protected void checkSameAnnotations(Symbol sym, JCModifiers specsModifiers) {
// for (Compound a : sym.getAnnotationMirrors()) {
// if (a.type.tsym.owner.equals(annotationPackageSymbol) && utils.findMod(specsModifiers,a.type.tsym) == null) {
// log.error(specsModifiers.pos,"jml.missing.annotation",a);
// }
// }
// }
/** Overridden in order to be sure that the type specs are attributed. */
public Type attribType(JCTree tree, Env<AttrContext> env) { // FIXME _ it seems this will automatically happen - why not?
Type result = super.attribType(tree,env);
if (result.getTag() != TypeTag.VOID && result.isErroneous() &&
result.tsym instanceof ClassSymbol &&
!result.isPrimitive()) {
addTodo((ClassSymbol)result.tsym);
}
return result;
}
public void visitJmlGroupName(JmlGroupName tree) {
Type saved = result = attribExpr(tree.selection,env,Type.noType);
Symbol sym = null;
if (tree.selection.type.getTag() == TypeTag.ERROR) return;
else if (tree.selection instanceof JCIdent) sym = ((JCIdent)tree.selection).sym;
else if (tree.selection instanceof JCFieldAccess) sym = ((JCFieldAccess)tree.selection).sym;
else if (tree.selection instanceof JCErroneous) return;
if (!(sym instanceof VarSymbol)) {
// FIXME - does this happen? emit errors
sym = null;
}
tree.sym = (VarSymbol)sym;
if (sym == null) {
log.error(tree.pos,"jml.internal","Unexpectedly did not find a resolution for this data group expression");
return;
}
if (!isModel(sym)) {
log.error(tree.pos,"jml.datagroup.must.be.model.in.maps");
}
if (inVarDecl != null && utils.isJMLStatic(sym) && !utils.isJMLStatic(inVarDecl.sym)) {
log.error(tree.pos,"jml.instance.in.static.datagroup");
}
result = saved;
}
JmlVariableDecl inVarDecl = null;
public void visitJmlTypeClauseIn(JmlTypeClauseIn tree) {
boolean prev = jmlresolve.setAllowJML(true);
boolean prevEnv = pureEnvironment;
JmlTokenKind prevClauseType = currentClauseType;
JmlVariableDecl prevDecl = inVarDecl;
currentClauseType = tree.token;
pureEnvironment = true;
inVarDecl = tree.parentVar;
long prevVisibility = jmlVisibility;
try {
jmlVisibility = tree.parentVar.mods.flags & Flags.AccessFlags; // FIXME - don't thnk this is needed here
if (checkForCircularity(inVarDecl.sym)) {
log.error(inVarDecl.pos(),"jml.circular.datagroup.inclusion",inVarDecl.name);
}
// for (JmlGroupName n: tree.list) {
// n.accept(this);
// if (n.sym != null && n.sym != inVarDecl.sym && checkForCircularity(n.sym,inVarDecl.sym)) {
// log.error(inVarDecl.pos(),"jml.circular.datagroup.inclusion",inVarDecl.name);
// continue;
// }
// }
} finally {
jmlVisibility = prevVisibility;
inVarDecl = prevDecl;
pureEnvironment = prevEnv;
currentClauseType = prevClauseType;
jmlresolve.setAllowJML(prev);
result = null;
}
}
public void visitJmlTypeClauseMaps(JmlTypeClauseMaps tree) {
boolean prev = jmlresolve.setAllowJML(true);
boolean prevEnv = pureEnvironment;
JmlTokenKind prevClauseType = currentClauseType;
currentClauseType = tree.token;
pureEnvironment = true;
long prevVisibility = jmlVisibility;
// Also check that the member reference field matches the declaration FIXME
// FIXME - static environment?
// FIXME - check visibility
try {
// FIXME : jmlVisibility = tree.parentVar.mods.flags & Flags.AccessFlags;
attribExpr(tree.expression,env,Type.noType);
for (JmlGroupName n: tree.list) {
n.accept(this);
}
} finally {
jmlVisibility = prevVisibility;
pureEnvironment = prevEnv;
currentClauseType = prevClauseType;
jmlresolve.setAllowJML(prev);
}
}
public void annotate(final List<JCAnnotation> annotations,
final Env<AttrContext> localEnv) {
Set<TypeSymbol> annotated = new HashSet<TypeSymbol>();
for (List<JCAnnotation> al = annotations; al.nonEmpty(); al = al.tail) {
JCAnnotation a = al.head;
Attribute.Compound c = annotate.enterAnnotation(a,
syms.annotationType,
env);
if (c == null) continue;
if (!annotated.add(a.type.tsym))
log.error(a.pos, "duplicate.annotation");
}
}
/** Attributes invariant, axiom, initially clauses */
public void visitJmlTypeClauseExpr(JmlTypeClauseExpr tree) {
boolean prev = pureEnvironment;
pureEnvironment = true;
JavaFileObject old = log.useSource(tree.source);
VarSymbol previousSecretContext = currentSecretContext;
boolean prevAllowJML = jmlresolve.setAllowJML(true);
boolean isStatic = tree.modifiers != null && isStatic(tree.modifiers);
long prevVisibility = jmlVisibility;
try {
// invariant, axiom, initially
Env<AttrContext> localEnv = env; // FIXME - here and in constraint, should we make a new local environment?
//if (tree.token == JmlToken.AXIOM) isStatic = true; // FIXME - but have to sort out use of variables in axioms in general
if (isStatic) localEnv.info.staticLevel++;
if (tree.token == JmlTokenKind.INVARIANT) {
jmlVisibility = -1;
attribAnnotationTypes(tree.modifiers.annotations,env);
annotate(tree.modifiers.annotations,env);
JCAnnotation a = findMod(tree.modifiers,JmlTokenKind.SECRET);
jmlVisibility = tree.modifiers.flags & Flags.AccessFlags;
if (a != null) {
if (a.args.size() != 1) {
log.error(tree.pos(),"jml.secret.invariant.one.arg");
} else {
Name datagroup = getAnnotationStringArg(a);
if (datagroup != null) {
//Symbol v = rs.findField(env,env.enclClass.type,datagroup,env.enclClass.sym);
Symbol v = rs.resolveIdent(a.args.get(0).pos(),env,datagroup,VAR);
if (v instanceof VarSymbol) currentSecretContext = (VarSymbol)v;
else if (v instanceof PackageSymbol) {
log.error(a.args.get(0).pos(),"jml.annotation.arg.not.a.field",v.getQualifiedName());
}
}
}
}
}
attribExpr(tree.expression, localEnv, syms.booleanType);
if (isStatic) localEnv.info.staticLevel--; // FIXME - move this to finally, but does not screw up the checks on the next line?
checkTypeClauseMods(tree,tree.modifiers,tree.token.internedName() + " clause",tree.token);
} finally {
jmlVisibility = prevVisibility;
currentSecretContext = previousSecretContext;
jmlresolve.setAllowJML(prevAllowJML);
pureEnvironment = prev;
log.useSource(old);
}
}
protected Name getAnnotationStringArg(JCAnnotation a) {
// The expression is an assignment of a Literal string to an identifier
// We only care about the literal string
// FIXME - what if the string is a qualified name?
JCExpression e = a.args.get(0);
if (e instanceof JCAssign) {
e = ((JCAssign)e).rhs;
}
if (e instanceof JCLiteral) {
JCLiteral lit = (JCLiteral)e;
// Non-string cases will already have error messages
if (lit.value instanceof String)
return names.fromString(lit.value.toString());
}
// So will non-literal cases
return null;
}
protected VarSymbol getSecretSymbol(JCModifiers mods) {
JCAnnotation secret = findMod(mods,JmlTokenKind.SECRET);
if (secret == null) return null;
if (secret.args.size() == 0) {
log.error(secret.pos(),"jml.secret.requires.arg");
return null;
}
Name n = getAnnotationStringArg(secret);
boolean prev = jmlresolve.setAllowJML(true);
Symbol sym = rs.resolveIdent(secret.args.get(0).pos(),env,n,VAR);
jmlresolve.setAllowJML(prev);
if (sym instanceof VarSymbol) return (VarSymbol)sym;
log.error(secret.pos(),"jml.no.such.field",n.toString());
return null;
}
protected VarSymbol getQuerySymbol(JCMethodInvocation tree, JCModifiers mods) {
JCAnnotation query = findMod(mods,JmlTokenKind.QUERY);
if (query == null) return null;
List<JCExpression> args = query.getArguments();
Name datagroup = null;
DiagnosticPosition pos = tree.meth.pos();
if (args.isEmpty()) {
// No argument - use the name of the method
if (tree.meth instanceof JCIdent) datagroup = ((JCIdent)tree.meth).name;
else if (tree.meth instanceof JCFieldAccess) datagroup = ((JCFieldAccess)tree.meth).name;
pos = query.pos();
} else {
datagroup = getAnnotationStringArg(query);
pos = args.get(0).pos();
}
boolean prev = jmlresolve.setAllowJML(true);
Symbol sym = rs.resolveIdent(pos,env,datagroup,VAR);
jmlresolve.setAllowJML(prev);
if (sym instanceof VarSymbol) return (VarSymbol)sym;
log.error(pos,"jml.no.such.field",datagroup.toString());
return null;
}
JmlTokenKind[] clauseAnnotations = new JmlTokenKind[]{ INSTANCE };
JmlTokenKind[] invariantAnnotations = new JmlTokenKind[]{ SECRET, INSTANCE };
JmlTokenKind[] noAnnotations = new JmlTokenKind[]{ };
public void checkTypeClauseMods(JCTree tree, JCModifiers mods,String where, JmlTokenKind token) {
long f = 0;
if (token != JmlTokenKind.AXIOM) f = Flags.AccessFlags | Flags.STATIC;
long diff = utils.hasOnly(mods,f);
if (diff != 0) log.error(tree.pos,"jml.bad.mods",Flags.toString(diff));
JCAnnotation a = utils.findMod(mods,tokenToAnnotationSymbol.get(INSTANCE));
if (a != null && isStatic(mods) ) {
log.error(a.pos(),"jml.conflicting.modifiers","instance","static");
}
attribAnnotationTypes(mods.annotations,env);
switch (token) {
case AXIOM:
allAllowed(mods.annotations,noAnnotations,where);
break;
case INVARIANT:
case REPRESENTS:
allAllowed(mods.annotations,invariantAnnotations,where);
break;
default:
allAllowed(mods.annotations,clauseAnnotations,where);
break;
}
if (token != JmlTokenKind.AXIOM) {
Check.instance(context).checkDisjoint(tree.pos(),mods.flags,Flags.PUBLIC,Flags.PROTECTED|Flags.PRIVATE);
Check.instance(context).checkDisjoint(tree.pos(),mods.flags,Flags.PROTECTED,Flags.PRIVATE);
}
}
/** Attributes a constraint clause */
public void visitJmlTypeClauseConstraint(JmlTypeClauseConstraint tree) {
JavaFileObject old = log.useSource(tree.source);
boolean prev = pureEnvironment;
pureEnvironment = true;
boolean prevAllowJML = jmlresolve.setAllowJML(true);
long prevVisibility = jmlVisibility;
try {
// constraint
boolean isStatic = (tree.modifiers.flags & STATIC) != 0;
Env<AttrContext> localEnv = env;
jmlVisibility = tree.modifiers.flags & Flags.AccessFlags;
if (isStatic) localEnv.info.staticLevel++;
ResultInfo prevResultInfo = resultInfo;
resultInfo = new ResultInfo(VAL|TYP,Type.noType);
attribExpr(tree.expression, localEnv, syms.booleanType);
resultInfo = prevResultInfo;
if (isStatic) localEnv.info.staticLevel--;
checkTypeClauseMods(tree,tree.modifiers,"constraint clause",tree.token);
if (tree.sigs != null) for (JmlTree.JmlMethodSig sig: tree.sigs) {
if (sig.argtypes == null) {
// FIXME - not implemented
} else {
sig.accept(this);
Symbol s = sig.methodSymbol;
if (s != null && s.isConstructor()
&& !isStatic) {
log.error(sig.pos(),"jml.no.constructors.allowed");
}
if (s != null && s.kind != Kinds.ERR){
if (s.owner != localEnv.enclClass.sym) {
log.error(sig.pos(),"jml.incorrect.method.owner");
}
}
}
}
} finally {
jmlVisibility = prevVisibility;
jmlresolve.setAllowJML(prevAllowJML);
pureEnvironment = prev;
log.useSource(old);
}
}
/** Attributes a declaration within a JML annotation - that is, a
* model method, model type, or ghost or model field
*/
public void visitJmlTypeClauseDecl(JmlTypeClauseDecl tree) {
JavaFileObject old = log.useSource(tree.source);
boolean prevAllowJML = jmlresolve.setAllowJML(true);
long prevVisibility = jmlVisibility;
try {
jmlVisibility = tree.modifiers.flags & Flags.AccessFlags;
attribStat(tree.decl,env);
} finally {
jmlVisibility = prevVisibility;
jmlresolve.setAllowJML(prevAllowJML);
log.useSource(old);
}
}
/** Attributes a initializer or static_initializer declaration */
public void visitJmlTypeClauseInitializer(JmlTypeClauseInitializer tree) {
JavaFileObject old = log.useSource(tree.source);
boolean prevAllowJML = jmlresolve.setAllowJML(true);
try {
// FIXME - test declarations within specs
Env<AttrContext> localEnv = localEnv(env,tree);
localEnv.info.scope.owner =
new MethodSymbol(Flags.PRIVATE | BLOCK, names.empty, null,
env.info.scope.owner);
if (tree.token == JmlTokenKind.STATIC_INITIALIZER) localEnv.info.staticLevel++;
if (tree.specs != null) attribStat(tree.specs,localEnv);
} finally {
jmlresolve.setAllowJML(prevAllowJML);
log.useSource(old);
}
}
/** Attributes a represents clause */
public void visitJmlTypeClauseRepresents(JmlTypeClauseRepresents tree) {
boolean prev = pureEnvironment;
pureEnvironment = true;
JavaFileObject old = log.useSource(tree.source);
boolean prevAllowJML = jmlresolve.setAllowJML(true);
VarSymbol prevSecret = currentSecretContext;
long prevVisibility = jmlVisibility;
try {
//attribExpr(tree.ident,env,Type.noType);
// Do this by hand to avoid issues with secret
jmlVisibility = tree.modifiers.flags & Flags.AccessFlags;
Symbol sym = null;
if (tree.ident instanceof JCIdent) {
JCIdent id = (JCIdent)tree.ident;
ResultInfo prevResultInfo = resultInfo;
resultInfo = new ResultInfo(VAL,Type.noType);
super.visitIdent(id);
resultInfo = prevResultInfo; // FIXME - in finally block
sym = id.sym;
}
else if (tree.ident instanceof JCFieldAccess) {
// FIXME - this needs fixing
attribExpr(tree.ident,env,Type.noType);
sym = ((JCFieldAccess)tree.ident).sym;
}
else {
attribExpr(tree.ident,env,Type.noType);
// FIXME - error - not implemented
}
// FIXME check that sym and represents are both secret or both not
attribAnnotationTypes(tree.modifiers.annotations,env);
annotate(tree.modifiers.annotations,env);
JCAnnotation a = findMod(tree.modifiers,JmlTokenKind.SECRET);
boolean representsIsSecret = a != null;
if (a != null && a.args.size() != 0) {
log.error(a.args.get(0).pos(),"jml.no.arg.for.secret.represents");
}
if (sym != null) {
boolean symIsSecret = sym.attribute(tokenToAnnotationSymbol.get(JmlTokenKind.SECRET)) != null;
if (symIsSecret != representsIsSecret) {
log.error(tree.pos(),"jml.represents.does.not.match.id.secret");
}
}
if (representsIsSecret && sym instanceof VarSymbol) currentSecretContext = (VarSymbol)sym;
Env<AttrContext> localEnv = env; //envForClause(tree,sym);
if ((tree.modifiers.flags & STATIC) != 0) localEnv.info.staticLevel++;
if (tree.suchThat) {
attribExpr(tree.expression, localEnv, syms.booleanType);
} else if (tree.ident.type == null) {
// skip
} else if (tree.ident.type.getKind() == TypeKind.ERROR) {
// skip
} else if (tree.ident.type.getKind() == TypeKind.NONE) {
// ERROR - parser should not let us get here - FIXME
} else {
attribExpr(tree.expression, localEnv, tree.ident.type);
}
if ((tree.modifiers.flags & STATIC) != 0) localEnv.info.staticLevel--;
checkTypeClauseMods(tree,tree.modifiers,"represents clause",tree.token);
if (sym != null && !sym.type.isErroneous() && sym.type.getTag() != TypeTag.ERROR) {
if ( isStatic(sym.flags()) != isStatic(tree.modifiers)) {
// Note: we cannot use sym.isStatic() in the line above because it
// replies true when the flag is not set, if we are in an
// interface and not a method. Model fields do not obey that rule.
log.error(tree.pos,"jml.represents.bad.static");
// Presume that the model field is correct and proceed
if (isStatic(sym.flags())) tree.modifiers.flags |= Flags.STATIC;
else tree.modifiers.flags &= ~Flags.STATIC;
}
if (!isModel(sym)) {
log.error(tree.pos,"jml.represents.expected.model");
}
if (env.enclClass.sym != sym.owner && isStatic(sym.flags())) {
log.error(tree.pos,"jml.misplaced.static.represents");
}
}
// FIXME - need to check that ident refers to a model field
} finally {
jmlVisibility = prevVisibility;
jmlresolve.setAllowJML(prevAllowJML);
pureEnvironment = prev;
log.useSource(old);
currentSecretContext = prevSecret;
}
}
/** This creates a local environment (like the initEnv for a variable declaration
* that can be used to type-check the expression of a JmlTypeClause. It handles
* static correctly.
* @param tree
* @return the new environment
*/
public Env<AttrContext> envForClause(JmlTypeClause tree, Symbol owner) {
Env<AttrContext> localEnv = env.dupto(new AttrContextEnv(tree, env.info.dup()));
localEnv.info.scope = new Scope.DelegatedScope(env.info.scope);
localEnv.info.scope.owner = owner;
//localEnv.info.lint = lint; // FIXME - what about lint?
if ((tree.modifiers.flags & STATIC) != 0) // ||(env.enclClass.sym.flags() & INTERFACE) != 0) // FIXME - what about interfaces
localEnv.info.staticLevel++;
return localEnv;
}
/** Attributes the monitors_for clause */
public void visitJmlTypeClauseMonitorsFor(JmlTypeClauseMonitorsFor tree) {
boolean prev = pureEnvironment;
pureEnvironment = true;
JavaFileObject old = log.useSource(tree.source);
boolean prevAllowJML = jmlresolve.setAllowJML(true);
long prevVisibility = jmlVisibility;
try {
jmlVisibility = tree.modifiers.flags & Flags.AccessFlags;
attribExpr(tree.identifier,env,Type.noType);
Symbol sym = tree.identifier.sym;
if (sym.owner != env.enclClass.sym) {
log.error(tree.identifier.pos,"jml.ident.not.in.class",sym,sym.owner,env.enclClass.sym);
} else {
// FIXME - should this be done elsewhere?
specs.getSpecs((VarSymbol)sym).list.append(tree);
}
// static does not matter here - the expressions in the
// list do not need to be static just because the identifier is
for (JCExpression c: tree.list) {
attribExpr(c,env,Type.noType);
if (c.type.isPrimitive()) {
log.error(c.pos,"jml.monitors.is.primitive");
}
}
} finally {
jmlVisibility = prevVisibility;
jmlresolve.setAllowJML(prevAllowJML);
pureEnvironment = prev;
log.useSource(old);
}
}
/** Attributes the readable-if and writable-if clauses */
public void visitJmlTypeClauseConditional(JmlTypeClauseConditional tree) {
JavaFileObject old = log.useSource(tree.source);
boolean prev = pureEnvironment;
pureEnvironment = true;
boolean prevAllowJML = jmlresolve.setAllowJML(true);
long prevVisibility = jmlVisibility;
try {
attribExpr(tree.identifier,env,Type.noType);
Symbol sym = tree.identifier.sym;
jmlVisibility = sym.flags() & Flags.AccessFlags;
long clauseVisibility = tree.modifiers.flags & Flags.AccessFlags;
if (clauseVisibility != 0 && clauseVisibility != jmlVisibility) {
log.error(tree.identifier.pos,"jml.visibility.is.different",Flags.toString(clauseVisibility), Flags.toString(jmlVisibility));
}
if (sym.owner != env.enclClass.sym) {
log.error(tree.identifier.pos,"jml.ident.not.in.class",sym,sym.owner,env.enclClass.sym);
} else {
// FIXME _ should this be done elsewhere
VarSymbol vsym = (VarSymbol)sym;
JmlSpecs.FieldSpecs fs = specs.getSpecs(vsym);
//if (fs == null) specs.putSpecs(vsym,fs=new JmlSpecs.FieldSpecs(tree.sym.));
fs.list.append(tree);
}
boolean isStatic = sym.isStatic();
if (isStatic) // ||(env.enclClass.sym.flags() & INTERFACE) != 0) // FIXME - what about interfaces
env.info.staticLevel++;
attribExpr(tree.expression, env, syms.booleanType);
if (isStatic) // ||(env.enclClass.sym.flags() & INTERFACE) != 0) // FIXME - what about interfaces
env.info.staticLevel--;
} finally {
jmlVisibility = prevVisibility;
jmlresolve.setAllowJML(prevAllowJML);
pureEnvironment = prev;
log.useSource(old);
}
}
/** Attributes a method-clause group (the stuff within {| |} ) */
public void visitJmlMethodClauseGroup(JmlMethodClauseGroup tree) {
for (JmlSpecificationCase c: tree.cases) {
c.accept(this);
}
}
boolean forallOldEnv = false;
/** Attributes for and old clauses within the specs of a method */
public void visitJmlMethodClauseDecl(JmlMethodClauseDecl tree) {
JmlTokenKind t = tree.token;
for (JCTree.JCVariableDecl decl: tree.decls) {
if (decl instanceof JmlVariableDecl) {
int wasFlags = 0;
if (env.enclMethod.sym.isStatic()) {
wasFlags = Flags.STATIC; // old and forall decls are implicitly static for a static method
((JmlVariableDecl)decl).mods.flags |= Flags.STATIC; // old and forall decls are implicitly static for a static method
}
forallOldEnv = JmlCheck.instance(context).staticOldEnv;
JmlCheck.instance(context).staticOldEnv = wasFlags != 0;
try {
decl.accept(this);
if (decl.sym == null) {
if (toRemove == null) toRemove = new ListBuffer<>();
toRemove.add(tree);
}
} finally {
JmlCheck.instance(context).staticOldEnv = forallOldEnv;
forallOldEnv = false;
if (env.enclMethod.sym.isStatic()) {
((JmlVariableDecl)decl).mods.flags &= ~Flags.STATIC;
}
}
JCTree.JCExpression init = ((JmlVariableDecl)decl).init;
if (t == JmlTokenKind.FORALL) {
if (init != null) log.error(init.pos(),"jml.forall.no.init");
} else {
if (init == null) log.error(((JmlVariableDecl)decl).pos,"jml.old.must.have.init");
}
JCModifiers mods = ((JmlVariableDecl)decl).mods;
if (utils.hasOnly(mods,wasFlags)!=0) log.error(tree.pos,"jml.no.java.mods.allowed","method specification declaration");
// The annotations are already checked as part of the local variable declaration
//allAllowed(mods.annotations, JmlToken.typeModifiers, "method specification declaration");
} else {
log.error(decl.pos(),"jml.internal.notsobad","Unexpected " + decl.getClass() + " object type in a JmlMethodClauseDecl list");
}
}
}
// FIXME - test the name lookup with forall and old clauses
protected Env<AttrContext> savedMethodClauseOutputEnv = null;
/** This is an implementation that does the type attribution for
* method specification clauses
* @param tree the method specification clause being attributed
*/
public void visitJmlMethodClauseExpr(JmlMethodClauseExpr tree) {
savedMethodClauseOutputEnv = this.env;
switch (tree.token) {
case REQUIRES:
case ENSURES:
case DIVERGES:
case WHEN:
case RETURNS:
attribExpr(tree.expression, env, syms.booleanType);
break;
case CONTINUES:
case BREAKS:
// FIXME - what about the label
attribExpr(tree.expression, env, syms.booleanType);
break;
case CALLABLE:
// FIXME - should be implemented somewhere else
break;
default:
log.error(tree.pos,"jml.unknown.construct",tree.token.internedName(),"JmlAttr.visitJmlMethodClauseExpr");
break;
}
savedMethodClauseOutputEnv = null;
}
public void visitJmlMethodClauseCallable(JmlMethodClauseCallable tree) {
if (tree.methodSignatures != null) {
for (JmlMethodSig sig : tree.methodSignatures) {
visitJmlMethodSig(sig);
}
}
}
/** This is an implementation that does the type attribution for
* duration, working_space, measured_by clauses
* (clauses that have an expression and an optional predicate)
* @param tree the method specification clause being attributed
*/
public void visitJmlMethodClauseConditional(JmlMethodClauseConditional tree) {
if (tree.predicate != null) attribExpr(tree.predicate, env, syms.booleanType);
switch (tree.token) {
case DURATION:
case WORKING_SPACE:
attribExpr(tree.expression, env, syms.longType);
break;
case MEASURED_BY:
attribExpr(tree.expression, env, syms.intType);
break;
default:
log.error(tree.pos,"jml.unknown.construct",tree.token.internedName(),"JmlAttr.visitJmlMethodClauseConditional");
break;
}
}
/** This is an implementation that does the type attribution for
* a signals method specification clause
* @param tree the method specification clause being attributed
*/
@Override
public void visitJmlMethodClauseSignals(JmlMethodClauseSignals tree) {
if (tree.vardef.name == null) {
tree.vardef.name = names.fromString(Strings.syntheticExceptionID);
}
Env<AttrContext> localEnv = localEnv(env,tree);
// FIXME - is this assignment needed? Why not elsewhere?
tree.vardef.vartype.type = attribTree(tree.vardef.vartype, localEnv, new ResultInfo(TYP, syms.exceptionType));
attribTree(tree.vardef, localEnv, new ResultInfo(VAR, syms.exceptionType));
Type prev = currentExceptionType;
currentExceptionType = tree.vardef.vartype.type;
try {
attribExpr(tree.expression, localEnv, syms.booleanType);
} finally {
currentExceptionType = prev;
}
}
/** This is an implementation that does the type attribution for
* a signals_only method specification clause
* @param tree the method specification clause being attributed
*/
@Override
public void visitJmlMethodClauseSigOnly(JmlMethodClauseSignalsOnly tree) {
for (JCExpression e: tree.list) {
e.type = attribTree(e, env, new ResultInfo(TYP, syms.exceptionType));
}
// FIXME - need to compare these to the exceptions in the method declaration
}
protected void checkIfLocal(JCTree e) {
if (e instanceof JCIdent && ((JCIdent)e).sym.owner instanceof MethodSymbol) {
log.error(e,"jml.no.formals.in.assignable",e.toString());
}
}
/** This is an implementation that does the type attribution for
* assignable/accessible/captures method specification clauses
* @param tree the method specification clause being attributed
*/
@Override
public void visitJmlMethodClauseStoreRef(JmlMethodClauseStoreRef tree) {
for (JCTree e: tree.list) {
attribExpr(e, env, Type.noType);
checkIfLocal(e);
}
// FIXME - check the result
}
// FIXME - need JmlAttr implementation for CALLABLE clauses
@Override
public void visitJmlStoreRefListExpression(JmlStoreRefListExpression that) {
for (JCTree t: that.list) {
attribExpr(t,env,Type.noType);
}
if (!postClauses.contains(currentClauseType)) {
log.error(that.pos+1, "jml.misplaced.token", that.token.internedName(), currentClauseType == null ? "jml declaration" : currentClauseType.internedName());
}
result = check(that, syms.booleanType, VAL, resultInfo);
}
/** The JML modifiers allowed for a specification case */
JmlTokenKind[] specCaseAllowed = new JmlTokenKind[]{};
ListBuffer<JmlMethodClause> toRemove = null;
/** This implements the visiting of a JmlSpecificationCase, initiating
* a visit of each clause in the case, setting the currentClauseType field
* before visiting each one.
*/
public void visitJmlSpecificationCase(JmlSpecificationCase tree) {
JavaFileObject old = log.useSource(tree.sourcefile);
Env<AttrContext> localEnv = null;
Env<AttrContext> prevEnv = env;
JmlTokenKind prevClauseType = currentClauseType; // Just in case there is recursion
long prevVisibility = jmlVisibility;
try {
if (tree.modifiers != null) {
attribAnnotationTypes(tree.modifiers.annotations,env);
if (tree.token == null) {
if (!utils.hasNone(tree.modifiers)) {
log.error(tree.pos(),"jml.no.mods.lightweight");
}
} else {
long diffs = utils.hasOnly(tree.modifiers,Flags.AccessFlags);
if (diffs != 0) log.error(tree.pos(),"jml.bad.mods.spec.case",Flags.toString(diffs));
Check.instance(context).checkDisjoint(tree.pos(),tree.modifiers.flags,Flags.PUBLIC,Flags.PROTECTED|Flags.PRIVATE);
Check.instance(context).checkDisjoint(tree.pos(),tree.modifiers.flags,Flags.PROTECTED,Flags.PRIVATE);
// "code" is parsed specifically and is not considered a modifier
allAllowed(tree.modifiers.annotations,specCaseAllowed,"specification case");
}
}
if (tree.code && tree.token == null) {
// Lightweight specs may not be labeled "code"
log.error(tree.pos(),"jml.no.code.lightweight");
}
// Making this sort of local environment restricts the scope of new
// declarations but does not allow a declaration to hide earlier
// declarations. Thus old and forall declarations may rename
// class fields, but may not rename method parameters or earlier old
// or forall declarations.
if (enclosingMethodEnv != null) { // FIXME - change these back to dup()?
localEnv = localEnv(env,enclosingMethodEnv.tree);
} else { // For initializer declarations with specs
localEnv = localEnv(env,enclosingClassEnv.tree);
}
env = localEnv;
if (tree.token == null) {
if (enclosingMethodEnv != null)
jmlVisibility = enclosingMethodEnv.enclMethod.mods.flags & Flags.AccessFlags;
else
jmlVisibility = enclosingClassEnv.enclClass.mods.flags & Flags.AccessFlags; // FIXME - should this be the visibilty of the initializer block?
} else {
jmlVisibility = tree.modifiers == null ? 0 : (tree.modifiers.flags & Flags.AccessFlags);
}
if (tree.clauses == null) {
// model program
boolean oldPure = pureEnvironment;
pureEnvironment = false;
try {
tree.block.accept(this);
} finally {
pureEnvironment = oldPure;
}
} else {
toRemove = null;
for (JmlMethodClause c: tree.clauses) {
currentClauseType = c.token;
c.accept(this);
}
if (toRemove != null) {
for (JmlMethodClause mc: toRemove) {
tree.clauses = Utils.remove(tree.clauses, mc);
}
toRemove = null;
}
}
} finally {
env = prevEnv;
jmlVisibility = prevVisibility;
if (localEnv != null) localEnv.info.scope.leave();
currentClauseType = prevClauseType;
log.useSource(old);
}
}
/** Visits a JmlMethodSpecs by visiting each of its JmlSpecificationCases */
public void visitJmlMethodSpecs(JmlMethodSpecs tree) {
boolean prev = pureEnvironment;
pureEnvironment = true;
for (JmlSpecificationCase c: tree.cases) {
// This check is put here rather than inside visitJmlSpecificationCase
// so it is not checked for specification cases that are part of a
// refining statement
if (c.modifiers != null && tree.decl != null) { // tree.decl is null for initializers and refining statements
long methodMod = enclosingMethodEnv.enclMethod.mods.flags & Flags.AccessFlags;
long caseMod = c.modifiers.flags & Flags.AccessFlags;
if (methodMod == 0 && enclosingMethodEnv.enclClass.sym.isInterface()) methodMod = Flags.PUBLIC;
if (methodMod != caseMod && c.token != null) {
if (caseMod == Flags.PUBLIC ||
methodMod == Flags.PRIVATE ||
(caseMod == Flags.PROTECTED && methodMod == 0)) {
DiagnosticPosition p = c.modifiers.pos();
if (p.getPreferredPosition() == Position.NOPOS) p = tree.pos();
if (!enclosingMethodEnv.enclMethod.name.toString().equals("clone")) {
JavaFileObject prevsource = log.useSource(c.source());
log.warning(p,"jml.no.point.to.more.visibility");
log.useSource(prevsource);
}
}
}
}
c.accept(this);
}
pureEnvironment = prev;
}
// These are the annotation types in which \pre and \old with a label can be used (e.g. assert)
private EnumSet<JmlTokenKind> preTokens = JmlTokenKind.methodStatementTokens;
// These are the annotation, method and type spec clause types in which \old without a label can be used
private EnumSet<JmlTokenKind> oldNoLabelTokens = JmlTokenKind.methodStatementTokens;
{
oldNoLabelTokens = oldNoLabelTokens.clone();
oldNoLabelTokens.addAll(EnumSet.of(ENSURES,SIGNALS,CONSTRAINT,DURATION,WORKING_SPACE));
}
// FIXME - limit these to a method body
Map<Name,Env<AttrContext>> labelEnvs = new HashMap<Name,Env<AttrContext>>();
public void visitLabelled(JCLabeledStatement tree) {
Env<AttrContext> labelenv = env.dup(tree,env.info.dupUnshared());
labelEnvs.put(tree.label,labelenv);
super.visitLabelled(tree);
}
protected Name currentEnvLabel = null;
public void visitJmlMethodInvocation(JmlMethodInvocation tree) {
JmlTokenKind token = tree.token;
if (tree.typeargs != null && tree.typeargs.size() != 0) {
// At present the parser cannot produce anything with typeargs, but just in case
// one squeaks through by some means or another
log.error(tree.typeargs.head.pos(),"jml.no.typeargs.for.fcn",token.internedName());
}
// Env<AttrContext> localEnv = env.dup(tree, env.info.dup( env.info.scope.dup()));
// // This local environment is for good measure. It is normally needed as
// // a scope to hold type arguments. But there are not any of those for
// // these JML methods, so it should not technically be needed.
Env<AttrContext> localEnv = env;
ListBuffer<Type> argtypesBuf = new ListBuffer<Type>();
boolean jmlstrict = JmlOption.isOption(context, JmlOption.STRICT);
Type t = null;
int n;
switch (token) {
case BSPAST:
case BSOLD:
case BSPRE:
// The argument can be a JML spec-expression
// Expect one argument, result type is the same type
Name savedLabel = currentEnvLabel;
n = tree.args.size();
if (!(n == 1 || (token != JmlTokenKind.BSPRE && n == 2))) {
log.error(tree.pos(),"jml.wrong.number.args",token.internedName(),
token == BSPRE ? "1" : "1 or 2",n);
}
if (token == BSPRE) {
// pre
if (!preTokens.contains(currentClauseType)) {
log.error(tree.pos+1, "jml.misplaced.old", "\\pre token", currentClauseType.internedName());
t = syms.errType;
}
} else if (n == 1) {
// old with no label
if (currentClauseType == null) {
// OK
} else if (!oldNoLabelTokens.contains(currentClauseType)) {
log.error(tree.pos+1, "jml.misplaced.old", "\\old token with no label", currentClauseType.internedName());
t = syms.errType;
}
} else {
// old with label
if (!preTokens.contains(currentClauseType)) {
log.error(tree.pos+1, "jml.misplaced.old", "\\old token with a label", currentClauseType.internedName());
t = syms.errType;
}
}
Name label = null;
if (n == 2) {
JCTree tr = tree.args.get(1);
if (tr.getTag() != JCTree.Tag.IDENT) {
log.error(tr.pos(),"jml.bad.label");
} else {
label = ((JCTree.JCIdent)tr).getName();
}
}
// FIXME - is it possible for a variable to have a different type at a previous label?
// label == empty ==> pre state; label == null ==> current state
currentEnvLabel = label == null ? names.empty : label;
if (n == 0 || t == syms.errType) {
t = syms.errType;
} else if (env.enclMethod == null) { // FIXME - what about types declared within methods
// In an type clause
attribExpr(tree.args.get(0), localEnv, Type.noType);
attribTypes(tree.typeargs, localEnv);
t = tree.args.get(0).type;
} else {
// in a method clause
Env<AttrContext> oldenv = savedMethodClauseOutputEnv;
if (oldenv == null) oldenv = enclosingMethodEnv;
if (label != null) {
Env<AttrContext> labelenv = labelEnvs.get(label);
if (labelenv == null) {
Log.instance(context).error(tree.args.get(1).pos(),"jml.unknown.label",label);
} else {
oldenv = labelenv;
}
}
if (enclosingMethodEnv == null) {
// Just a precaution
Log.instance(context).error("jml.internal","Unsupported context for pre-state reference (anonymous class? initializer block?). Please report the program.");
oldenv = env;
//
}
Env<AttrContext> qOldEnv = oldenv;
for (JmlQuantifiedExpr qexpr: quantifiedExprs) {
qOldEnv = envForExpr(qexpr,qOldEnv);
Scope enclScope = enter.enterScope(qOldEnv);
for (JCVariableDecl decl: qexpr.decls) {
// FIXME - do we need to do these checks?
// if (chk.checkUnique(tree.pos(), v, enclScope)) {
// chk.checkTransparentVar(tree.pos(), v, enclScope);
enclScope.enter(decl.sym);
// }
}
}
attribExpr(tree.args.get(0), qOldEnv, Type.noType);
attribTypes(tree.typeargs, qOldEnv);
t = tree.args.get(0).type;
Scope scope = qOldEnv.info.scope;
for (JmlQuantifiedExpr qexpr: quantifiedExprs) {
scope = scope.leave();
}
}
result = check(tree, t, VAL, resultInfo);
currentEnvLabel = savedLabel;
break;
case BSMAX:
// Expect one argument of type JMLSetType, result type Lock
// FIXME - this should be one argument of type JMLObjectSet, result type is Object
// The argument expression may contain JML constructs
attribArgs(tree.args, localEnv, argtypesBuf); // We can't send in Lock as the requested type because Types does not know what to do with it - FIXME: perhaps make a JmlTypes that can handle the new primitives
//attribTypes(tree.typeargs, localEnv);
n = tree.args.size();
if (n != 1) {
log.error(tree.pos(),"jml.wrong.number.args",token.internedName(),1,n);
}
if (n == 0) t = syms.errType;
else {
if (!tree.args.get(0).type.equals(JMLSetType)) { // FIXME - use isSameType or check? what about errors?
log.error(tree.args.get(0).pos(),"jml.max.expects.lockset",JMLSetType,tree.args.get(0).type.toString());
}
t = Lock;
}
result = check(tree, t, VAL, resultInfo);
break;
case BSTYPEOF :
// Expect one argument of any type, result type \TYPE
// The argument expression may contain JML constructs
attribArgs(tree.args, localEnv, argtypesBuf);
//attribTypes(tree.typeargs, localEnv);
n = tree.args.size();
if (n != 1) {
log.error(tree.pos(),"jml.wrong.number.args",token.internedName(),1,n);
}
t = jmltypes.TYPE;
result = check(tree, t, VAL, resultInfo);
break;
// case BSNOTMODIFIED :
// // Expect any number of arguments of any type, result type is boolean
// // Can be used where an \old is used
// // FIXME - JML wants this to be a store-ref list
// if (!oldNoLabelTokens.contains(currentClauseType)) {
// log.error(tree.pos+1, "jml.misplaced.old", "\\not_modified token", currentClauseType.internedName());
// t = syms.errType;
// }
// attribArgs(tree.args, localEnv);
// attribTypes(tree.typeargs, localEnv);
// n = tree.args.size();
// t = syms.booleanType;
// result = check(tree, t, VAL, resultInfo);
// break;
case BSNONNULLELEMENTS :
// The argument can be a JML spec-expression
// Expect any number of arguments of any array type, result type is boolean
{
n = tree.args.size();
if (n != 1 && jmlstrict) {
log.error(tree.pos(),"jml.wrong.number.args",token.internedName(),1,n);
}
for (JCExpression arg: tree.args) {
Type argtype = attribExpr(arg, localEnv);
if (!(argtype instanceof Type.ArrayType) && !argtype.isErroneous()) {
log.error(arg.pos(),"jml.arraytype.required",token.internedName(),argtype.toString(),arg.toString());
}
}
result = check(tree, syms.booleanType, VAL, resultInfo);
break;
}
case BSISINITIALIZED :
// The argument is a type that is a reference type; the result is boolean
// The argument may contain JML constructs
{
n = tree.args.size();
if (n != 1) {
log.error(tree.pos(),"jml.wrong.number.args",token.internedName(),1,n);
}
for (JCExpression arg: tree.args) {
Type argtype = attribExpr(arg, localEnv);
if (!argtype.isNullOrReference() && !argtype.isErroneous()) {
log.error(arg.pos(),"jml.ref.arg.required",token.internedName());
}
}
result = check(tree, syms.booleanType, VAL, resultInfo);
break;
}
case BSTYPELC :
// Takes one argument which is a type (not an expression); the result is of type \TYPE
// The argument may contain JML constructs
attribTypes(tree.typeargs, localEnv);
n = tree.args.size();
if (n != 1) {
log.error(tree,"jml.wrong.number.args",token.internedName(),1,n);
}
if (n > 0) {
JCExpression arg = tree.args.get(0);
attribTree(arg, localEnv, new ResultInfo(TYP, Type.noType));
if (!tree.javaType && arg.type.tsym.getTypeParameters().size() > 0 &&
!arg.type.isParameterized()) {
log.error(tree,"jml.invalid.erasedtype",JmlPretty.write(arg));
}
if (!tree.javaType) checkForWildcards(arg,arg);
}
t = jmltypes.TYPE;
if (tree.javaType) t = syms.classType;
Type saved = check(tree, t, VAL, resultInfo);
addTodo(utilsClass);
result = saved;
break;
case BSDISTINCT :
// The result is boolean.
// Case 1) All types are reference types
// Case 2) Some or all are primitive - all must be convertible to
// a common primitive type, including through unboxing
attribArgs(tree.args, localEnv, argtypesBuf);
//attribTypes(tree.typeargs, localEnv);
boolean anyPrimitive = false;
Type maxPrimitiveType = null;
for (JCExpression arg : tree.args) {
Type tt = arg.type;
if (tt.isErroneous()) continue;
if (tt.isPrimitive()) {
anyPrimitive = true;
}
}
if (anyPrimitive) for (JCExpression arg : tree.args) {
Type tt = arg.type;
if (tt.isErroneous()) { continue; }
if (!tt.isPrimitive()) tt = types.unboxedType(tt);
if (tt.getTag() == TypeTag.VOID) {
// FIXME -error
} else if (maxPrimitiveType == null) {
maxPrimitiveType = tt;
} else if (types.isConvertible(tt,maxPrimitiveType)) {
// OK
} else if (types.isConvertible(maxPrimitiveType, tt)) {
maxPrimitiveType = tt;
} else {
// FIXME - error
}
}
if (anyPrimitive) {
for (JCExpression arg : tree.args) {
Type tt = arg.type;
if (tt.isErroneous()) continue;
if (!tt.isPrimitive()) tt = types.unboxedType(tt);
if (!types.isConvertible(tt,maxPrimitiveType)) {
// FIXME - ERROR
}
}
}
result = check(tree, syms.booleanType, VAL, resultInfo);
break;
case BSFRESH :
// The arguments can be JML spec-expressions
// The arguments can be any reference type; the result is boolean
attribArgs(tree.args, localEnv, argtypesBuf);
//attribTypes(tree.typeargs, localEnv);
for (int i=0; i<tree.args.size(); i++) {
JCExpression arg = tree.args.get(i);
if (arg.type.isPrimitive()) {
log.error(arg.pos(),"jml.ref.arg.required",token.internedName());
}
}
if (!postClauses.contains(currentClauseType)) {
// The +1 is to fool the error reporting mechanism into
// allowing other error reports about the same token
log.error(tree.pos+1, "jml.misplaced.token", token.internedName(), currentClauseType == null ? "jml declaration" : currentClauseType.internedName());
}
result = check(tree, syms.booleanType, VAL, resultInfo);
break;
case BSREACH :
// The argument can be a JML spec-expressions
// Expects one argument of reference type; result is of type JMLObjectSet
attribArgs(tree.args, localEnv, argtypesBuf);
//attribTypes(tree.typeargs, localEnv);
n = tree.args.size();
if (n != 1) {
log.error(tree.pos(),"jml.wrong.number.args",token.internedName(),1,n);
} else {
JCExpression arg = tree.args.get(0);
if (arg.type.isPrimitive()) {
log.error(arg.pos(),"jml.ref.arg.required",token.internedName());
}
}
result = check(tree, JMLSetType, VAL, resultInfo); // FXME - needs to be a settype of Object
break;
case BSINVARIANTFOR :
// The argument can be a JML spec-expression
// Expects one argument of reference type or a typename; result is of type boolean
n = tree.args.size();
if (n != 1 && jmlstrict) {
log.error(tree.pos(),"jml.wrong.number.args",token.internedName(),1,n);
}
for (JCExpression arg: tree.args) {
attribTree(arg, localEnv, new ResultInfo(TYP|VAR, Infer.anyPoly));
if (arg.type.isPrimitive()) {
log.error(arg.pos(),"jml.ref.arg.required",token.internedName());
} else if (jmlstrict && treeutils.isATypeTree(arg)) {
log.error(arg.pos(),"jml.ref.arg.required",token.internedName());
}
}
result = check(tree, syms.booleanType, VAL, resultInfo);
break;
case BSDURATION :
case BSWORKINGSPACE :
// The argument must be a Java expression
// Expects one argument that is an arbitrary expression; result is of type long
// Note: The JML reference manual puts constraints on the form of the expression; those seem to be unneeded
// Note also: the argument is not actually evaluated (but needs to be evaluatable),
// thus it may only contain Java constructs (FIXME - there is no check on that)
attribArgs(tree.args, localEnv, argtypesBuf);
//attribTypes(tree.typeargs, localEnv);
n = tree.args.size();
if (n != 1) {
log.error(tree.pos(),"jml.wrong.number.args",token.internedName(),1,n);
}
result = check(tree, syms.longType, VAL, resultInfo);
break;
case BSSPACE :
// The argument may be a JML spec-expression
// Expects one argument of reference type; result is of type long
attribArgs(tree.args, localEnv, argtypesBuf);
//attribTypes(tree.typeargs, localEnv);
n = tree.args.size();
if (n != 1) {
log.error(tree.pos(),"jml.wrong.number.args",token.internedName(),1,n);
}
// FIXME - there is no check that the argument is of reference type - can't this apply to primitives as well?
result = check(tree, syms.longType, VAL, resultInfo);
break;
case BSNOWARN:
case BSNOWARNOP:
case BSWARN:
case BSWARNOP:
case BSBIGINT_MATH:
case BSSAFEMATH:
case BSJAVAMATH:
// Expects one expression argument of any type; result is of the same type
// FIXME - does this allow any JML spec-expression?
// FIXME - the JMLb rules will require some numeric type promotions
attribArgs(tree.args, localEnv, argtypesBuf);
//attribTypes(tree.typeargs, localEnv);
n = tree.args.size();
if (n != 1) {
log.error(tree.pos(),"jml.wrong.number.args",token.internedName(),1,n);
}
if (n == 0) {
t = syms.errType;
} else {
t = tree.args.get(0).type;
}
result = check(tree, t, VAL, resultInfo);
break;
default:
ExpressionExtension ext = Extensions.instance(context).find(tree.pos,token,true);
Type ttt = ext.typecheck(this,tree,localEnv);
result = check(tree, ttt, VAL, resultInfo);
break;
case BSONLYCALLED: // FIXME - needs implementation
case BSONLYASSIGNED: // FIXME - needs implementation
case BSONLYACCESSED: // FIXME - needs implementation
case BSONLYCAPTURED: // FIXME - needs implementation
case BSNOTASSIGNED: // FIXME - needs implementation
case BSNOTMODIFIED: // FIXME - needs implementation
log.error(tree.pos,"jml.unknown.construct",token.internedName(),"JmlAttr.visitApply");
result = tree.type = syms.errType;
break;
}
}
protected void checkForWildcards(JCExpression e, JCExpression arg) {
if (e instanceof JCWildcard) {
log.error(e,"jml.no.wildcards.in.type",JmlPretty.write(arg));
}
if (!(e instanceof JCTypeApply)) return;
JCTypeApply t = (JCTypeApply)e;
for (JCExpression ee: t.arguments) {
checkForWildcards(ee,arg);
}
}
/** This is overridden to check that if we are in a pure environment,
* the method is declared pure. Also to make sure any specs are attributed.
*/
@Override
public void visitApply(JCTree.JCMethodInvocation tree) {
// Otherwise this is just a Java method application
super.visitApply(tree);
if (result.isErroneous()) return;
Type savedResult = result;
MethodSymbol msym = null;
JCExpression m = tree.meth;
Symbol sym = (m instanceof JCIdent ? ((JCIdent)m).sym : m instanceof JCFieldAccess ? ((JCFieldAccess)m).sym : null);
if (sym instanceof MethodSymbol) msym = (MethodSymbol)sym;
if (pureEnvironment && tree.meth.type != null && tree.meth.type.getTag() != TypeTag.ERROR) {
// Check that the method being called is pure
if (msym != null) {
boolean isPure = isPureMethod(msym);
if (!isPure && JmlOption.isOption(context,JmlOption.PURITYCHECK)) {
log.warning(tree.pos,"jml.non.pure.method",utils.qualifiedMethodSig(msym));
}
if (isPure && currentClauseType == JmlTokenKind.INVARIANT
&& msym.owner == enclosingClassEnv.enclClass.sym
&& !isHelper(msym)
&& utils.rac) {
log.warning(tree.pos,"jml.possibly.recursive.invariant",msym);
}
} else {
// We are expecting that the expression that is the method
// receiver (tree.meth) is either a JCIdent or a JCFieldAccess
// If it is something else we end up here.
if (sym == null) log.error("jml.internal.notsobad","Unexpected parse tree node for a method call in JmlAttr.visitApply: " + m.getClass());
else log.error("jml.internal.notsobad","Unexpected symbol type for method expression in JmlAttr.visitApply: ", sym.getClass());
}
// FIXME - could be a super or this call
}
if (msym != null) checkSecretCallable(tree,msym);
result = savedResult;
}
/** This handles JML statements such as assert and assume and unreachable and hence_by. */
public void visitJmlStatementExpr(JmlTree.JmlStatementExpr tree) {
if (tree.token == JmlTokenKind.COMMENT) { result = null; return; }
boolean prevAllowJML = jmlresolve.setAllowJML(true);
boolean prev = pureEnvironment;
pureEnvironment = true;
JmlTokenKind prevClauseType = currentClauseType;
currentClauseType = tree.token;
// unreachable statements have a null expression
if (tree.expression != null) attribExpr(tree.expression,env,syms.booleanType);
if (tree.optionalExpression != null) attribExpr(tree.optionalExpression,env,Type.noType);
currentClauseType = prevClauseType;
pureEnvironment = prev;
jmlresolve.setAllowJML(prevAllowJML);
result = null; // No type returned
}
public void visitLetExpr(LetExpr tree) {
if (env.info.scope.owner.kind == TYP) {
// Block is a static or instance initializer;
// let the owner of the environment be a freshly
// created BLOCK-method.
Env<AttrContext> localEnv =
env.dup(tree, env.info.dup(env.info.scope.dupUnshared()));
localEnv.info.scope.owner =
new MethodSymbol(BLOCK, names.empty, null, // FIXME - or'd other flags with BLOCK
env.info.scope.owner);
//if ((tree.mods.flags & STATIC) != 0) localEnv.info.staticLevel++;
attribStats(tree.defs,localEnv);
attribExpr(tree.expr,localEnv,Type.noType);
Type resultType = tree.expr.type;
if (resultType.constValue() != null) resultType = resultType.constType(null);
result = check(tree, resultType, VAL, resultInfo);
} else {
// Create a new local environment with a local scope.
Env<AttrContext> localEnv =
env.dup(tree, env.info.dup(env.info.scope.dup()));
attribStats(tree.defs,localEnv);
attribExpr(tree.expr,localEnv,Type.noType);
Type resultType = tree.expr.type;
if (resultType.constValue() != null) resultType = resultType.constType(null);
result = check(tree, resultType, VAL, resultInfo);
localEnv.info.scope.leave();
}
}
/** This handles JML statements such as assert and assume and unreachable and hence_by. */
public void visitJmlStatementHavoc(JmlTree.JmlStatementHavoc tree) {
boolean prevAllowJML = jmlresolve.setAllowJML(true);
boolean prev = pureEnvironment;
pureEnvironment = true;
if (tree.storerefs != null) {
for (JCTree e: tree.storerefs) {
attribExpr(e, env, Type.noType);
}
}
pureEnvironment = prev;
jmlresolve.setAllowJML(prevAllowJML);
result = null; // No type returned
}
boolean savedSpecOK = false; // FIXME - never read
public void attribLoopSpecs(List<JmlTree.JmlStatementLoop> loopSpecs, Env<AttrContext> loopEnv) {
savedSpecOK = false;
if (loopSpecs == null || loopSpecs.isEmpty()) return;
boolean prevAllowJML = jmlresolve.setAllowJML(true);
boolean prev = pureEnvironment;
pureEnvironment = true;
for (JmlTree.JmlStatementLoop tree: loopSpecs) {
JmlTokenKind prevClauseType = currentClauseType;
currentClauseType = tree.token;
if (tree.token == JmlTokenKind.LOOP_INVARIANT) {
attribExpr(tree.expression,loopEnv,syms.booleanType);
} else {
attribExpr(tree.expression,loopEnv,syms.longType); // FIXME - what type to use
}
currentClauseType = prevClauseType;
}
pureEnvironment = prev;
jmlresolve.setAllowJML(prevAllowJML);
}
/** This handles JML statements that give method-type specs for method body statements. */
public void visitJmlStatementSpec(JmlTree.JmlStatementSpec tree) {
boolean prevAllowJML = jmlresolve.setAllowJML(true);
JmlTokenKind prevClauseType = currentClauseType;
currentClauseType = null;
if (tree.statementSpecs != null) attribStat(tree.statementSpecs,env);
currentClauseType = prevClauseType;
jmlresolve.setAllowJML(prevAllowJML);
}
/** This handles JML declarations (method and ghost fields, methods, types) */
public void visitJmlStatementDecls(JmlTree.JmlStatementDecls tree) {
boolean prevAllowJML = jmlresolve.setAllowJML(true);
JmlTokenKind prevClauseType = currentClauseType;
currentClauseType = tree.token;
for (JCTree.JCStatement s : tree.list) {
attribStat(s,env);
}
currentClauseType = prevClauseType;
jmlresolve.setAllowJML(prevAllowJML);
}
/** This handles JML statements such as set and debug */
public void visitJmlStatement(JmlTree.JmlStatement tree) { // FIXME - need to test appropriately for purity
boolean prevAllowJML = jmlresolve.setAllowJML(true);
JmlTokenKind prevClauseType = currentClauseType;
currentClauseType = tree.token;
if (tree.statement != null) attribStat(tree.statement,env);
currentClauseType = prevClauseType;
jmlresolve.setAllowJML(prevAllowJML);
}
/** This handles JML primitive types */
public void visitJmlPrimitiveTypeTree(JmlPrimitiveTypeTree that) {
JmlType type = that.token == JmlTokenKind.BSTYPEUC ? jmltypes.TYPE :
that.token == JmlTokenKind.BSBIGINT ? jmltypes.BIGINT :
that.token == JmlTokenKind.BSREAL ? jmltypes.REAL :
null;
if (type == null) {
result = syms.errType;
log.error(that.pos,"jml.unknown.type.token",that.token.internedName(),"JmlAttr.visitJmlPrimitiveTypeTree");
return;
}
that.type = type;
that.repType = jmltypes.repType(that.pos(), type);
attribType(that.repType,env);
result = type;
// if (utils.rac) {
// result = type.repType.type;
// that.type = result;
// }
}
/** This set holds method clause types in which the \result token may appear
* (and \not_assigned \only_assigned \only_captured \only_accessible \not_modified) */
public EnumSet<JmlTokenKind> resultClauses = EnumSet.of(ENSURES,DURATION,WORKING_SPACE);
/** This set holds method clause types in which the \exception token may appear */
public EnumSet<JmlTokenKind> exceptionClauses = EnumSet.of(SIGNALS);
/** This set holds method clause types in which the these tokens may appear:
* \not_assigned \only_assigned \only_captured \only_accessible \not_modified */
public EnumSet<JmlTokenKind> postClauses = EnumSet.of(ENSURES,SIGNALS,DURATION,WORKING_SPACE,ASSERT,ASSUME);
/** This handles expression constructs with no argument list such as \\result */
public void visitJmlSingleton(JmlSingleton that) {
JmlTokenKind jt = that.token;
Type t = syms.errType;
switch (jt) {
case BSLOCKSET:
t = JMLSetType;
break;
case BSINDEX:
t = syms.intType;
if (loopStack.isEmpty()) {
log.error(that.pos,"jml.outofscope",jt.internedName());
} else {
that.info = loopStack.get(0).sym;
}
break;
case BSVALUES:
t = JMLValuesType;
if (foreachLoopStack.isEmpty()) {
log.error(that.pos,"jml.outofscope",jt.internedName());
} else {
JCVariableDecl d = foreachLoopStack.get(0).valuesDecl;
if (d == null) {
log.error(that.pos,"jml.notforthisloop",jt.internedName());
} else {
that.info = d.sym;
}
}
break;
case BSRESULT:
JCTree.JCMethodDecl md = enclosingMethodEnv.enclMethod;
JCTree res = md.getReturnType();
if (res == null || (!res.type.isErroneous() && types.isSameType(res.type,syms.voidType))) {
log.error(that.pos+1, "jml.void.result");
t = syms.errType;
} else {
t = res.type;
}
if (currentEnvLabel != null) {
log.error(that.pos, "jml.no.result.in.old");
}
if (!resultClauses.contains(currentClauseType)) {
// The +1 is to fool the error reporting mechanism into
// allowing other error reports about the same token
log.error(that.pos+1, "jml.misplaced.result", currentClauseType.internedName());
t = syms.errType;
}
break;
case BSEXCEPTION:
md = env.enclMethod;
if (!exceptionClauses.contains(currentClauseType)) {
// The +1 is to fool the error reporting mechanism into
// allowing other error reports about the same token
log.error(that.pos+1, "jml.misplaced.exception", currentClauseType.internedName());
t = syms.errType;
} else {
t = currentExceptionType;
}
break;
case BSSAME:
if (currentClauseType != REQUIRES) {
log.error(that.pos,"jml.misplaced.same");
}
t = syms.booleanType;
// Check that this is only used in a requires clause and not in conjunction with anything else - FIXME
break;
case BSNOTSPECIFIED:
t = syms.errType; // Use errType so it does not propagate error messages
break;
case BSNOTHING:
case BSEVERYTHING:
t = Type.noType;
break;
case INFORMAL_COMMENT:
t = syms.booleanType;
break;
default:
ExpressionExtension ext = Extensions.instance(context).find(that.pos,jt,true);
Type ttt = ext.typecheck(this,that,env);
result = check(that, ttt, VAL, resultInfo);
break;
// default:
// t = syms.errType;
// log.error(that.pos,"jml.unknown.type.token",that.token.internedName(),"JmlAttr.visitJmlSingleton");
// break;
}
result = check(that, t, VAL, resultInfo);
}
// public void visitJmlFunction(JmlFunction that) {
// // Actually, I don't think this gets called. It would get called through
// // visitApply.
// result = that.type = Type.noType;
// }
public void visitJmlImport(JmlImport that) {
visitImport(that);
// FIXME - ignoring model
}
public void visitJmlBlock(JmlBlock that) {
visitBlock(that);
if (that.cases != null) {
boolean isStatic = (that.flags & Flags.STATIC) != 0;
if (isStatic) env.info.staticLevel++;
try {
that.cases.accept(this);
} finally {
if (isStatic) env.info.staticLevel--;
}
}
}
public void visitJmlBinary(JmlBinary that) { // FIXME - how do we handle unboxing, casting
switch (that.op) {
case EQUIVALENCE:
case INEQUIVALENCE:
case IMPLIES:
case REVERSE_IMPLIES:
attribExpr(that.lhs,env,syms.booleanType);
attribExpr(that.rhs,env,syms.booleanType);
result = syms.booleanType;
break;
case LOCK_LT:
case LOCK_LE:
attribExpr(that.lhs,env,syms.objectType);
attribExpr(that.rhs,env,syms.objectType);
result = syms.booleanType;
break;
case SUBTYPE_OF:
// Note: the method of comparing types here ignores any type
// arguments. If we use isSameType, for example, then Class
// and Class<Object> are different. In this case, all we need
// to know is that the operands are some type of Class.
// FIXME - what about subclasses of Class
attribExpr(that.lhs,env,Type.noType);
Type t = that.lhs.type;
boolean errorAlready = false;
if (t.isErroneous()) errorAlready = true;
else if (!t.equals(jmltypes.TYPE)
&& !t.tsym.equals(syms.classType.tsym)) {
errorAlready = true;
log.error(that.lhs.pos(),"jml.subtype.arguments",that.lhs.type);
}
attribExpr(that.rhs,env,Type.noType);
Type tt = that.rhs.type;
if (tt.isErroneous()) errorAlready = true;
else if (!tt.equals(jmltypes.TYPE)
&& !tt.tsym.equals(syms.classType.tsym)) {
errorAlready = true;
log.error(that.rhs.pos(),"jml.subtype.arguments",that.rhs.type);
}
if ((t == jmltypes.TYPE) != (tt == jmltypes.TYPE) && !errorAlready) {
log.error(that.rhs.pos(),"jml.subtype.arguments.same",that.rhs.type);
}
if (t != jmltypes.TYPE) that.op = JmlTokenKind.JSUBTYPE_OF; // Java subtyping
result = syms.booleanType;
break;
default:
log.error(that.pos(),"jml.unknown.operator",that.op.internedName(),"JmlAttr");
break;
}
result = check(that, result, VAL, resultInfo);
}
/** Attributes a LBL expression. Note that OpenJML allows an arbitrary
* type LBL expression, e.g. (\lbl A expr) . This should report for the
* label A the value of the expr, whatever its type. For the standard
* lblpos and lblneg expressions, the expr must be boolean.
*/
public void visitJmlLblExpression(JmlLblExpression that) {
if (!quantifiedExprs.isEmpty()) {
// TODO _ COUld allow label expressions that do not contain quantified variables
// FIXME - does not check set comprehension
log.error(that.pos, "jml.lbl.in.quantified");
}
Type t = that.token == JmlTokenKind.BSLBLANY ? Type.noType : syms.booleanType;
attribExpr(that.expression, env, t);
Type resultType = that.expression.type;
if (resultType.constValue() != null) resultType = resultType.constType(null);
result = check(that, resultType, VAL, resultInfo);
}
/** This makes a new local environment that allows adding new declarations,
* but can see out into the enclosing environment; you need to call leave()
* when you leave this scope to get rid of new declarations.
* An initEnv for example,
* does not allow new declarations, and a raw new Scope or method env will not inherit
* the outer declarations. This is used in particular by quantified
* expressions and set comprehensions.
* @param that the expression that occasions this new scope
* @param env the current env
* @return the new env
*/
protected Env<AttrContext> envForExpr(JCTree tree, Env<AttrContext> env) {
Env<AttrContext> localEnv;
// We can't use a delegated scope - they are used for variable initializers
// and don;'t accept any new variable declarations.
Scope sco = env.info.scope;
while (sco instanceof Scope.DelegatedScope) sco = ((Scope.DelegatedScope)sco).next;
long flags = 0L;
if (sco.owner.kind != MTH) {
// Block is a static or instance initializer;
// let the owner of the environment be a freshly
// created BLOCK-method.
localEnv =
env.dup(tree, env.info.dup(sco.dupUnshared()));
localEnv.info.scope.owner =
new MethodSymbol(flags | BLOCK, names.empty, null,
sco.owner);
if ((flags & STATIC) != 0) localEnv.info.staticLevel++;
} else {
// Create a new local environment with a local scope.
localEnv =
env.dup(tree, env.info.dup(sco.dup()));
// For this kind of scope, you have to eventually call
// localEnv.info.scope.leave();
}
// // Previous
// Scope sco = env.info.scope;
// // DelegatedScopes are created for a variable initialization
// while (sco instanceof Scope.DelegatedScope) sco = ((Scope.DelegatedScope)sco).next;
// Scope sc = sco.dup(sco.owner);
// //sc.next = env.info.scope; // FIXME-FIXES - should this go back in?
// Env<AttrContext> localEnv = env.dup(that, env.info.dup(sc));
//// Env<AttrContext> localEnv = env.dup(that, env.info.dup(sc.dupUnshared()));
//// Env<AttrContext> localEnv =
//// env.dup(that, env.info.dup(env.info.scope.dupUnshared()));
return localEnv;
}
protected java.util.List<JmlQuantifiedExpr> quantifiedExprs = new LinkedList<JmlQuantifiedExpr>();
public void visitJmlQuantifiedExpr(JmlQuantifiedExpr that) {
Env<AttrContext> localEnv = envForExpr(that,env);
boolean b = ((JmlMemberEnter)memberEnter).setInJml(true);
for (JCVariableDecl decl: that.decls) {
JCModifiers mods = decl.getModifiers();
if (utils.hasOnly(mods,0)!=0) log.error(mods.pos,"jml.no.java.mods.allowed","quantified expression");
attribAnnotationTypes(mods.annotations,env);
allAllowed(mods.annotations, JmlTokenKind.typeModifiers, "quantified expression");
utils.setExprLocal(mods);
// if (utils.hasAny(mods,Flags.STATIC)) {
// log.error(that.pos,
// "mod.not.allowed.here", asFlagSet(Flags.STATIC));
// }
// //if (Resolve.isStatic(env)) mods.flags |= Flags.STATIC; // FIXME - this is needed for variables declared in quantified expressions in invariants - will need to ignore this when pretty printing?
memberEnter.memberEnter(decl, localEnv);
decl.type = decl.vartype.type; // FIXME not sure this is needed
}
((JmlMemberEnter)memberEnter).setInJml(b);
quantifiedExprs.add(that);
try {
if (that.range != null) attribExpr(that.range, localEnv, syms.booleanType);
Type resultType = syms.errType;
switch (that.op) {
case BSEXISTS:
case BSFORALL:
attribExpr(that.value, localEnv, syms.booleanType);
resultType = syms.booleanType;
break;
case BSNUMOF:
attribExpr(that.value, localEnv, syms.booleanType);
resultType = syms.intType; // FIXME - int? long? bigint?
break;
case BSMAX:
case BSMIN:
attribExpr(that.value, localEnv, Type.noType); // FIXME - int? long? numeric? bigint? double?
resultType = that.value.type;
break;
case BSSUM:
case BSPRODUCT:
attribExpr(that.value, localEnv, syms.longType); // FIXME - int? long? numeric? bigint? double?
resultType = that.value.type;
break;
default:
log.error(that.pos(),"jml.unknown.construct",that.op.internedName(),"JmlAttr.visitJmlQuantifiedExpr");
break;
}
result = check(that, resultType, VAL, resultInfo);
if (utils.rac) {
if (that.racexpr == null) createRacExpr(that,localEnv,resultType);
// if (that.racexpr == null) {
// System.out.println("NO QUANT");
// }
}
} finally {
quantifiedExprs.remove(quantifiedExprs.size()-1);
localEnv.info.scope.leave();
}
return;
}
public boolean implementationAllowed() {
return implementationAllowed;
}
public void createRacExpr(JmlQuantifiedExpr q, Env<AttrContext> localEnv, Type resultType) {
/* The purpose of this method is to create a fully-qualified executable expression to use
* in RAC. This is tricky. The primary JML quantified expression has already been attributed.
* That declarations and the range and value expressions are reused in creating this RAC equivalent.
* The problematic aspect is that one cannot attribute an expression twice and there is no check
* to prevent re-attribution; re-attributing simple expressions causes no trouble, but the
* reattribution logic can complain about, for example, duplicate declarations. Problems particularly
* arise if the value subexpression of the quantified expression includes a a nested quantified
* expression or method calls.
*
* So we construct our RAC expression carefully distinguishing between new portions and already
* attributed portions.
*
* The general approach is to replace a quantified expression that returns a value of type TT with
* an expression of this template:
* new org.jmlspecs.utils.Utils.ValueTT() {
* public TT value(Object args...) { ... expression ... }
* }.value( ... args ... )
* The complicated aspect of this formula is that any subexpressions that do not depend on the
* quantification variables have to be passed in as arguments. Also we want any new declarations
* to be attributed by the regular attribution mechanism. So the general approach is to construct
* the new part of the final expression (including new declarations), attribute it, and then
* piece in the subexpressions from the JML quantified statement that were already attributed.
* This approach means that the initial, unattributed expression has some holes in it that need
* filling in later.
*/
JCStatement update = null;
List<JCExpression> argslist = null;
JCNewArray newarray = null;
try {
// If there is no range, we will not try to execute the expression
if (q.range == null) {
return; // FIXME - is this executable?
}
JmlTree.Maker F = factory;
Type restype = q.type; // Result type of the expression
// Attributed statements that return true or false
JCReturn rettrue = F.Return(treeutils.trueLit); //F.Literal(TypeTag.BOOLEAN, 1).setType(syms.booleanType));
JCReturn retfalse = F.Return(treeutils.falseLit); //F.Literal(TypeTag.BOOLEAN, 0).setType(syms.booleanType));
// First assemble the portions that need attribution
// FIXME - include bigint support, real?
// Construct the fully qualified name of the class holding the methods we'll use
JCExpression constructName = F.Ident(names.fromString("org"));
constructName = F.Select(constructName, names.fromString("jmlspecs"));
constructName = F.Select(constructName, names.fromString("utils"));
constructName = F.Select(constructName, names.fromString("Utils"));
TypeTag tag = restype.getTag();
String s = tag == TypeTag.INT ? "ValueInt" :
tag == TypeTag.BOOLEAN ? "ValueBool" :
tag == TypeTag.LONG ? "ValueLong" :
tag == TypeTag.DOUBLE ? "ValueDouble" :
tag == TypeTag.FLOAT ? "ValueFloat" :
tag == TypeTag.SHORT ? "ValueShort" :
tag == TypeTag.BYTE ? "ValueByte" :
tag == TypeTag.CHAR ? "ValueChar" :
"ValueInt";
Name className = names.fromString(s);
constructName = F.Select(constructName, className);
// methodName is e.g., depending on the result type, org.jmlspecs.utils.Utils.ValueBool
JCVariableDecl initialDecl = null;
JCVariableDecl valueDecl = null;
JCVariableDecl firstDecl = null;
ListBuffer<JCStatement> bodyStats = new ListBuffer<JCStatement>();
if (q.op == JmlTokenKind.BSFORALL || q.op == JmlTokenKind.BSEXISTS) {
} else if (q.op == JmlTokenKind.BSNUMOF) {
initialDecl = F.VarDef(F.Modifiers(0), names.fromString("_count$$$"), F.Type(restype), F.Literal(restype.getTag(),0).setType(syms.intType));
} else if (q.op == JmlTokenKind.BSSUM) {
initialDecl = F.VarDef(F.Modifiers(0), names.fromString("_sum$$$"), F.Type(restype), F.Literal(restype.getTag(),0).setType(syms.intType));
} else if (q.op == JmlTokenKind.BSPRODUCT) {
initialDecl = F.VarDef(F.Modifiers(0), names.fromString("_prod$$$"), F.Type(restype), F.Literal(restype.getTag(),1).setType(syms.intType));
} else if (q.op == JmlTokenKind.BSMAX || q.op == JmlTokenKind.BSMIN) {
firstDecl = F.VarDef(F.Modifiers(0), names.fromString("_first$$$"), F.TypeIdent(TypeTag.BOOLEAN), F.Literal(TypeTag.BOOLEAN,1).setType(syms.booleanType));
initialDecl = F.VarDef(F.Modifiers(0), names.fromString(q.op == JmlTokenKind.BSMIN ? "_min$$$" : "_max$$$"), F.Type(restype), F.Literal(restype.getTag(),0).setType(restype));
valueDecl = F.VarDef(F.Modifiers(0), names.fromString("_val$$$"), F.Type(restype), null);
} else {
return;
}
if (initialDecl != null) bodyStats.add(initialDecl);
if (valueDecl != null) bodyStats.add(valueDecl);
if (firstDecl != null) bodyStats.add(firstDecl);
JCBlock body = null;
// We create a declaration of 'args' and add it to bodyStats so it is attributed,
// because we need an identifier that references the args declaration to sprinkle
// through the rewritten range and value expressions.
Name argsname = names.fromString("args");
JCVariableDecl argsdef = F.VarDef(F.Modifiers(Flags.FINAL),argsname,
F.TypeArray(F.Type(syms.objectType)),null);
JCIdent argsID = F.Ident(argsdef.name);
// argsdef is: Object[] args = null;
// We'd like to reuse the declarations that are actually within the JML quantifier
// expression. They are already attributed. But they seem to be attributed within
// a different environment. For example, if they are used, complaints occur that they
// are not final. Perhaps this can be worked around, but for now we create a
// parallel set of declarations. These will be attributed here and the ids are
// passed into the RACCopy.copy calls below, so that each identifier node is rewritten
// to refer to the new declaration.
Map<Symbol,JCVariableDecl> newdecls = new HashMap<Symbol,JCVariableDecl>();
Map<Symbol,JCIdent> newids = new HashMap<Symbol,JCIdent>();
for (JCVariableDecl v: q.decls) {
JCVariableDecl newdecl = F.VarDef(F.Modifiers(0), v.name, v.vartype, null);
JCIdent id = F.Ident(v.name);
newdecls.put(v.sym,newdecl);
newids.put(v.sym, id);
}
// We need to figure out the iterations that will occur within the body of the
// RAC expression. That involves some manipulation of the
List<JCVariableDecl> decls = q.decls;
ListBuffer<JCExpression> args = new ListBuffer<JCExpression>();
JCExpression newvalue = JmlAttr.RACCopy.copy(q.value,context,decls,args,argsID,newids);
JCExpression newrange = JmlAttr.RACCopy.copy(q.range,context,decls,args,argsID,newids);
java.util.List<Bound> bounds = new java.util.LinkedList<Bound>();
JCExpression innerexpr = determineRacBounds(decls,newrange,bounds);
if (innerexpr == null) {
return;
}
// Here we create declarations that will be needed. These are
// unattributed; they are attributed below, and then the attributed
// declarations are used in the second phase of RAC expression construction.
for (Bound bound: bounds) {
JCExpression vartype = bound.decl.vartype;
Name indexname = bound.decl.name;
String var = indexname.toString();
JCVariableDecl indexdef = newdecls.get(bound.decl.sym);
if (bound.decl.type.getTag() == TypeTag.BOOLEAN) {
indexdef.init = F.Literal(syms.booleanType.getTag(),0);
} else if (bound.decl.type.getTag() == TypeTag.CLASS) {
// nothing
} else {
Name loname = names.fromString("$$$lo_" + var);
Name hiname = names.fromString("$$$hi_" + var);
bound.lodef = F.VarDef(F.Modifiers(0),loname,vartype,null);
bound.hidef = F.VarDef(F.Modifiers(0),hiname,vartype,null);
bodyStats.add(bound.lodef);
bodyStats.add(bound.hidef);
//indexdef = F.VarDef(F.Modifiers(0), indexname, vartype, F.Ident(loname));
indexdef.init = F.Ident(loname);
}
// indexdef.sym = bound.decl.sym;
// indexdef.type = bound.decl.sym.type;
bound.indexdef = indexdef;
bodyStats.add(indexdef);
}
// Back to expressions that will need attributing.
JCStatement retStandin = F.Return(F.Literal(restype.getTag(), 0));
bodyStats.add(retStandin);
JCMethodDecl methodDecl = F.MethodDef(
F.Modifiers(Flags.PUBLIC),
names.fromString("value"),
F.Type(restype), // result type
List.<JCTypeParameter>nil(), // type parameters
List.<JCVariableDecl>of(argsdef), // parameters
List.<JCExpression>nil(), // thrown types
body=F.Block(0,bodyStats.toList()), // body - more to be added later
null); // default value
utils.setJML(methodDecl.mods);
methodDecl.mods.annotations = methodDecl.mods.annotations.append(utils.tokenToAnnotationAST(JmlTokenKind.PURE,0,0)); // FIXME- fix positions?
// methodDecl is (RT is the result type): public RT value(Object[] args) { ... decls... }
List<JCTree> defs = List.<JCTree>of(methodDecl);
JmlClassDecl classDecl = (JmlClassDecl)F.AnonymousClassDef(F.Modifiers(0), defs) ;
classDecl.specsDecl = classDecl;
classDecl.typeSpecs = new JmlSpecs.TypeSpecs(classDecl);
classDecl.toplevel = ((JmlClassDecl)enclosingClassEnv.enclClass).toplevel;
JCNewClass anon = F.NewClass(null,List.<JCExpression>nil(),constructName,List.<JCExpression>nil(),classDecl);
// anon.constructor = new MethodSymbol(0, names.init, syms.unknownType, syms.noSymbol);
// anon.constructorType = syms.unknownType;
// anon is, e.g., depending on the result type:
// new org.jmlspecs.utils.Utils.ValueBool() { public boolean value(Object[] args) { ... decls... } }
ListBuffer<JCExpression> standinargs = new ListBuffer<JCExpression>(); //F.TypeCast(Type.(syms.objectType), F.Literal(syms.objectType.tag,null));
// for (JCExpression ex: argslist) {
// JCLiteral lit = F.Literal(ex.type.tag,0);
// standinargs.add(lit);
// }
standinargs.add(F.Literal(TypeTag.INT,0));
newarray = F.NewArray(F.Type(syms.objectType),List.<JCExpression>nil(),standinargs.toList());
JCExpression call = F.Apply(List.<JCExpression>nil(),F.Select(anon,names.fromString("value")),List.<JCExpression>of(newarray));
// call is, e.g., depending on the result type:
// new org.jmlspecs.utils.Utils.ValueBool() { public boolean value(Object[] args) {} }.value(null,null);
// Need to fill in (a) the body of the method and (b) the araguments of the call
q.racexpr = call;
q.racexpr = treeutils.makeZeroEquivalentLit(q.pos, q.type);
// Attribute the unattributed expression
attribExpr(q.racexpr, localEnv, resultType); // This puts in the default constructor, which the check call does not
//check(that.racexpr,resultType, VAL, resultInfo); // But this has the right environment
// Now form the body of the expression from pieces that are already attributed.
argslist = args.toList();
// argsID is aliased with nodes inside newvalue and newrange, so the following
// assignments effectively attribute those nodes
argsID.type = argsdef.type;
argsID.sym = argsdef.sym;
// Determine core computation
// forall: if (range) if (!value) return false;
// exists: if (range) if (value) return true;
// numof: if (range) if (value) ++count;
// sum: if (range) sum += value;
JCStatement retStat;
JCExpression cond = newvalue;
if (q.op == JmlTokenKind.BSFORALL || q.op == JmlTokenKind.BSEXISTS) {
if (q.op == JmlTokenKind.BSFORALL) {
cond = treeutils.makeNot(cond.pos, cond);
// cond = F.Unary(JCTree.NOT, cond).setType(syms.booleanType);
// ((JCUnary)cond).operator = rs.resolveUnaryOperator(cond.pos(), JCTree.NOT, env, newvalue.type);
}
update = F.If(cond, q.op == JmlTokenKind.BSFORALL? retfalse : rettrue , null);
retStat = q.op == JmlTokenKind.BSFORALL ? rettrue : retfalse;
} else if (q.op == JmlTokenKind.BSNUMOF) {
JCIdent id = F.Ident(initialDecl.name);
id.setType(initialDecl.type);
id.sym = initialDecl.sym;
JCUnary op = (JCUnary)treeutils.makeUnary(id.pos, JCTree.Tag.PREINC, id);
// JCUnary op = F.Unary(JCTree.PREINC, id);
// op.setType(initialDecl.type);
// op.operator = rs.resolveUnaryOperator(op.pos(),op.getTag(),env,op.arg.type);
update = F.If(cond, F.Exec(op) , null);
retStat = F.Return(id); // Is it OK to reuse the node?
} else if (q.op == JmlTokenKind.BSSUM) {
JCIdent id = F.Ident(initialDecl.name);
id.setType(initialDecl.type);
id.sym = initialDecl.sym;
JCAssignOp asn = treeutils.makeAssignOp(Position.NOPOS, JCTree.Tag.PLUS_ASG, id, cond);
// JCAssignOp asn = F.Assignop(JCTree.PLUS_ASG, id, cond);
// asn.setType(initialDecl.type);
// asn.operator = rs.resolveBinaryOperator(asn.pos(), asn.getTag() - JCTree.ASGOffset, env, asn.lhs.type, asn.rhs.type);
update = F.Exec(asn);
retStat = F.Return(id); // Is it OK to reuse the node?
} else if (q.op == JmlTokenKind.BSPRODUCT) {
JCIdent id = F.Ident(initialDecl.name);
id.pos = Position.NOPOS;
id.setType(initialDecl.type);
id.sym = initialDecl.sym;
JCAssignOp asn = treeutils.makeAssignOp(Position.NOPOS, JCTree.Tag.MUL_ASG, id, cond);
// JCAssignOp asn = F.Assignop(JCTree.MUL_ASG, id, cond);
// asn.setType(initialDecl.type);
// asn.operator = rs.resolveBinaryOperator(asn.pos(), asn.getTag() - JCTree.ASGOffset, env, asn.lhs.type, asn.rhs.type);
update = F.Exec(asn);
retStat = F.Return(id);
} else if (q.op == JmlTokenKind.BSMAX || q.op == JmlTokenKind.BSMIN) {
JCIdent id = F.Ident(initialDecl.name);
id.setType(initialDecl.type);
id.sym = initialDecl.sym;
JCIdent vid = F.Ident(valueDecl.name);
vid.setType(valueDecl.type);
vid.sym = valueDecl.sym;
JCIdent fid = F.Ident(firstDecl.name);
fid.setType(firstDecl.type);
fid.sym = firstDecl.sym;
JCBinary op1,op2;
// FIXME - use treeutils here
update = F.If((op1=F.Binary(
JCTree.Tag.OR,
(op2=F.Binary(
( q.op == JmlTokenKind.BSMIN ? JCTree.Tag.LT : JCTree.Tag.GT),
F.Assign(vid, newvalue).setType(vid.type),
id
)).setType(syms.booleanType),
fid
)).setType(syms.booleanType)
,
F.Block(0,
List.<JCStatement>of(
F.Exec(F.Assign(id, vid).setType(id.type)),
F.Exec(F.Assign(fid, F.Literal(TypeTag.BOOLEAN,0).setType(syms.booleanType)).setType(fid.type))
)
),
null);
op1.operator = treeutils.orSymbol;
op2.operator = rs.resolveBinaryOperator(op2.pos(),op2.getTag(), env, op2.lhs.type, op2.rhs.type);
retStat = F.Return(id);
} else if (q.op == JmlTokenKind.BSMIN) {
JCIdent id = F.Ident(initialDecl.name);
id.setType(initialDecl.type);
id.sym = initialDecl.sym;
JCIdent vid = F.Ident(valueDecl.name);
vid.setType(valueDecl.type);
vid.sym = valueDecl.sym;
JCIdent fid = F.Ident(firstDecl.name);
fid.setType(firstDecl.type);
fid.sym = firstDecl.sym;
JCBinary op1,op2;
// FIXME - use treeutils here
update = F.If(
(op1=F.Binary(
JCTree.Tag.OR,
(op2=F.Binary(
JCTree.Tag.LT,
F.Assign(vid, newvalue).setType(vid.type),
id
)).setType(syms.booleanType),
fid
)).setType(syms.booleanType)
,
F.Block(0,
List.<JCStatement>of(
F.Exec(F.Assign(id,vid).setType(id.type)),
F.Exec(F.Assign(fid, F.Literal(TypeTag.BOOLEAN,0).setType(syms.booleanType).setType(fid.type)))
)
),
null);
op1.operator = treeutils.orSymbol;
op2.operator = rs.resolveBinaryOperator(op2.pos(),op2.getTag(), env, op2.lhs.type, op2.rhs.type);
retStat = F.Return(id);
} else {
return;
}
JCStatement innerStatement = F.If(innerexpr, update, null);
for (Bound bound: bounds) {
JCVariableDecl indexdef = bound.indexdef;
JCIdent indexid = newids.get(bound.decl.sym);
indexid.sym = indexdef.sym;
indexid.type = indexdef.type;
Name indexname = indexdef.name;
if (bound.decl.type.getTag() == TypeTag.BOOLEAN) {
JCIdent idx = F.at(Position.NOPOS).Ident(indexname);
idx.setType(indexdef.type);
idx.sym = indexdef.sym;
JCExpression neg = treeutils.makeNot(Position.NOPOS, idx);
JCAssign asgn = treeutils.makeAssign(Position.NOPOS, idx,neg);
JCStatement negate = F.at(Position.NOPOS).Exec(asgn);
JCDoWhileLoop dowhilestatement =
F.DoLoop(
F.Block(0,List.<JCStatement>of(innerStatement,negate)),
idx
);
innerStatement = F.Block(0,List.<JCStatement>of(indexdef,dowhilestatement));
} else if (bound.decl.type.getTag() == TypeTag.CLASS) {
JCEnhancedForLoop foreach =
F.ForeachLoop(
indexdef,
bound.lo,
innerStatement);
innerStatement = foreach;
} else {
JCVariableDecl lodef = bound.lodef;
JCVariableDecl hidef = bound.hidef;
Name hiname = hidef.name;
lodef.init = bound.lo;
hidef.init = bound.hi;
JCIdent id = indexid;
JCExpression op = treeutils.makeUnary(Position.NOPOS,JCTree.Tag.PREINC, id);
JCStatement inc = F.Exec(op);
JCIdent hi = F.Ident(hiname);
hi.sym = hidef.sym;
hi.type = hidef.type;
// FIXME - use treeutils
JCBinary bin = F.Binary(bound.hi_equal ? JCTree.Tag.LE : JCTree.Tag.LT, id, hi);
bin.operator = rs.resolveBinaryOperator(bin.pos(), bin.getTag(), env, id.type, hi.type);
bin.setType(syms.booleanType);
JCWhileLoop whilestatement =
F.WhileLoop(
bin,
F.Block(0,List.<JCStatement>of(innerStatement,inc)));
innerStatement = bound.lo_equal ?
F.Block(0,List.<JCStatement>of(lodef,hidef,indexdef,whilestatement))
: F.Block(0,List.<JCStatement>of(lodef,hidef,indexdef,inc,whilestatement));
}
}
List<JCStatement> methodBody =
initialDecl == null ? List.<JCStatement>of(innerStatement,retStat) :
valueDecl == null ? List.<JCStatement>of(initialDecl,innerStatement,retStat) :
List.<JCStatement>of(firstDecl,initialDecl,valueDecl,innerStatement,retStat);
// Fill in missing pieces
body.stats = methodBody;
newarray.elems = argslist;
} catch (Exception e) {
// If there is an exception, we just abort trying to produce a RAC expression
q.racexpr = null;
}
return;
}
protected static class Bound {
public JCVariableDecl decl;
public JCExpression lo;
public JCExpression hi;
boolean lo_equal;
boolean hi_equal;
JCVariableDecl indexdef;
/*@Nullable*/JCVariableDecl lodef;
/*@Nullable*/JCVariableDecl hidef;
}
/** If appropriate bounds can be determined for all defined variables, the method returns the
* remaining expression and fills in the Bound list (first element is innermost loop); if appropriate
* bounds cannot be determined, the method returns null.
*/
public JCExpression determineRacBounds(List<JCVariableDecl> decls, JCExpression range, java.util.List<Bound> bounds) {
// Some current assumptions
if (decls.length() != 1) return null; // FIXME - does only one declaration!!!!!!
if (decls.head.type.getTag() == TypeTag.DOUBLE) return null;
if (decls.head.type.getTag() == TypeTag.FLOAT) return null;
if (decls.head.type.getTag() == TypeTag.BOOLEAN) {
Bound b = new Bound();
b.decl = decls.head;
b.lo = null;
b.hi = null;
bounds.add(0,b);
return range;
} else if (decls.head.type.getTag() == TypeTag.CLASS) {
if (range instanceof JCBinary && ((JCBinary)range).getTag() != JCTree.Tag.AND) return null;
JCExpression check =
range instanceof JCBinary? ((JCBinary)range).lhs : range;
if (!(check instanceof JCMethodInvocation)) return null;
JCMethodInvocation mi = (JCMethodInvocation)check;
if (!(mi.meth instanceof JCFieldAccess)) return null;
JCFieldAccess fa = (JCFieldAccess)mi.meth;
if (!fa.name.toString().equals("contains") && !fa.name.toString().equals("has")) return null;
Bound b = new Bound();
b.decl = decls.head;
b.lo = fa.selected;
// FIXME - should check whether fa.selected is Iterable
b.hi = null;
bounds.add(0,b);
return check == range ? check : // FIXME - could be set to true
((JCBinary)range).rhs;
}
try {
// presume int
JCBinary locomp = (JCBinary)((JCBinary)range).lhs;
JCBinary hicomp = (JCBinary)((JCBinary)range).rhs;
if (locomp.getTag() == JCTree.Tag.AND) {
hicomp = (JCBinary)locomp.rhs;
locomp = (JCBinary)locomp.lhs;
} else if (hicomp.getTag() == JCTree.Tag.AND) {
hicomp = (JCBinary)hicomp.lhs;
}
Bound b = new Bound();
b.decl = decls.head;
b.lo = locomp.lhs;
b.hi = hicomp.rhs;
b.lo_equal = locomp.getTag() == JCTree.Tag.LE;
b.hi_equal = hicomp.getTag() == JCTree.Tag.LE;
bounds.add(0,b);
} catch (Exception e) {
return null;
}
return range;
}
public void visitJmlSetComprehension(JmlSetComprehension that) {
// that.type must be a subtype of JMLSetType FIXME - not checked
// that.variable must be a declaration of a reference type
// that.predicate must be boolean
// that.predicate must have a special form FIXME - not checked
// result has type that.type
// Generics: perhaps JMLSetType should have a type argument.
// If so, then in new T { TT i | ... }
// T is a subtype of JMLSetType<? super TT> (FIXME - is that right?)
// T must be allowed to hold elements of type TT
attribType(that.newtype,env);
attribType(that.variable.vartype,env);
attribAnnotationTypes(that.variable.mods.annotations,env);
Env<AttrContext> localEnv = envForExpr(that,env);
JCModifiers mods = that.variable.mods;
utils.setExprLocal(mods);
memberEnter.memberEnter(that.variable, localEnv);
attribExpr(that.predicate,localEnv,syms.booleanType);
localEnv.info.scope.leave();
if (utils.hasOnly(mods,0)!=0) log.error(that.pos,"jml.no.java.mods.allowed","set comprehension expression");
allAllowed(mods.annotations, JmlTokenKind.typeModifiers, "set comprehension expression");
result = check(that, that.newtype.type, VAL, resultInfo);
}
public String visibility(long f) {
return f == 0? "package" : f==Flags.PUBLIC? "public" :f==Flags.PROTECTED? "protected" : "private";
}
@Override
protected Type checkId(JCTree tree,
Type site,
Symbol sym,
Env<AttrContext> env,
ResultInfo resultInfo) {
if (checkingSignature) return sym.type;
return super.checkId(tree, site, sym, env, resultInfo);
}
@Override
public void visitIdent(JCIdent tree) {
// if (tree.name.toString().equals("TestJava")) org.jmlspecs.openjml.Utils.stop();
long prevVisibility = jmlVisibility;
JmlTokenKind prevClauseType = currentClauseType;
try {
// jmlVisibility = -1;
// currentClauseType = null;
// Visiting the ident itself in the super class should not need the
// jmlVisibility. However, visiting the ident can trigger loading
// and type-checking a whole new class - so we unset this field
// in the mean time.
super.visitIdent(tree);
} finally {
jmlVisibility = prevVisibility;
currentClauseType = prevClauseType;
}
// The above call erroneously does not set tree.type for method identifiers
// if the method failed to result, even though a symbol with an error
// type is set, so we patch that here. See also the comment at visitSelect.
if (tree.type == null) tree.type = tree.sym.type;
Type saved = result;
if (!justAttribute && tree.sym instanceof VarSymbol) {
checkSecretReadable(tree.pos(),(VarSymbol)tree.sym);
}// Could also be a method call, and error, a package, a class...
checkVisibility(tree, jmlVisibility, tree.sym);
result = saved;
}
protected void checkVisibility(DiagnosticPosition pos, long jmlVisibility, Symbol sym) {
if (jmlVisibility != -1) {
long v = (sym.flags() & Flags.AccessFlags);
if (sym instanceof ClassSymbol) {
// FIXME - the code below crashes for class symbols. What should we do?
// FIXME - we also get this case for annotations on a clause
} else {
// if (tree.sym.toString().equals("defaults")) {
// System.out.println("defaults");;
// }
JCModifiers mods = null;
if (sym.owner != null && sym.owner.kind == TYP) {
if (sym.kind == VAR) {
VarSymbol vsym = (VarSymbol)sym;
FieldSpecs sp = specs.getSpecs(vsym);
if (sp != null) mods = sp.mods;
}
if (sym.kind == MTH) {
MethodSpecs sp = specs.getSpecs((MethodSymbol)sym);
if (sp != null) mods = sp.mods;
}
}
if (mods != null && findMod(mods,SPEC_PROTECTED) != null) {
v = Flags.PROTECTED;
}
if (mods != null && findMod(mods,SPEC_PUBLIC) != null) {
v = Flags.PUBLIC;
}
}
if (currentClauseType == JmlTokenKind.INVARIANT || currentClauseType == JmlTokenKind.CONSTRAINT) {
// An ident used in an invariant must have the same visibility as the invariant clause - no more, no less
// Is the symbol more visible? OK if the symbol is not a modifiable variable
if (jmlVisibility != v && moreOrEqualVisibleThan(v,jmlVisibility)
&& sym instanceof VarSymbol && !utils.isExprLocal(sym.flags()) && !special(v,sym)
&& (sym.flags() & Flags.FINAL)==0 ) {
log.error(pos, "jml.visibility", visibility(v), visibility(jmlVisibility), currentClauseType.internedName());
}
// Is the symbol less visible? not OK
if (jmlVisibility != v && !moreOrEqualVisibleThan(v,jmlVisibility)
&& !utils.isExprLocal(sym.flags()) && !special(v,sym)) {
log.error(pos, "jml.visibility", visibility(v), visibility(jmlVisibility), currentClauseType.internedName());
}
} else if (currentClauseType == JmlTokenKind.REPRESENTS) {
//log.error(tree.pos,"jml.internal","Case not handled in JmlAttr.visitIdent: " + currentClauseType.internedName());
if (!moreOrEqualVisibleThan(v,jmlVisibility) && !special(v,sym)) {
log.error(pos, "jml.visibility", visibility(v), visibility(jmlVisibility), currentClauseType.internedName());
}
} else if (currentClauseType == JmlTokenKind.JMLDECL) {
// FIXME - not sure what rules to apply to this case
} else if (currentClauseType == JmlTokenKind.IN) {
// In V type x; //@ in y;
// identifier y must be at least as visible as x (i.e., as V)
if (!moreOrEqualVisibleThan(v,jmlVisibility)) {
log.error(pos, "jml.visibility", visibility(v), visibility(jmlVisibility), currentClauseType.internedName());
}
} else if (currentClauseType == JmlTokenKind.ENSURES || currentClauseType == JmlTokenKind.SIGNALS) {
// An identifier mentioned in a clause must be at least as visible as the clause itself.
if (!moreOrEqualVisibleThan(v,jmlVisibility) && !special(v,sym)) {
log.error(pos, "jml.visibility", visibility(v), visibility(jmlVisibility), currentClauseType.internedName());
}
if (currentEnvLabel != null && enclosingMethodEnv.enclMethod.sym.isConstructor()) {
if (!sym.isStatic()) log.error(pos, "jml.no.old.in.constructor", sym);
}
} else {
// Default case
// An identifier mentioned in a clause must be at least as visible as the clause itself.
if (!moreOrEqualVisibleThan(v,jmlVisibility) && !special(v,sym)) {
log.error(pos, "jml.visibility", visibility(v), visibility(jmlVisibility), currentClauseType.internedName());
}
}
}
}
// FIXME - not sure this is still needed
boolean special(long v, Symbol sym) {
if (sym instanceof TypeSymbol) return true;
if (sym instanceof VarSymbol && sym.owner instanceof MethodSymbol) return true; // FIXME - not sure how to handle these various special names
return sym.name.toString().equals("TYPE") || !(v != 0 || (!sym.name.equals(names._this) && !sym.name.equals(names._super) && !sym.name.equals(names.length) && !sym.name.equals(names._class)));
}
final static int order[] = { 2, 4, 1, 0, 3}; // package, public, private, -, protected
static boolean moreOrEqualVisibleThan(long v1, long v2) {
return order[(int)v1] >= order[(int)v2];
}
@Override
public void visitIndexed(JCArrayAccess tree) {
super.visitIndexed(tree);
Type saved = result;
if (tree.indexed instanceof JCIdent && ((JCIdent)tree.indexed).sym instanceof VarSymbol) {
checkSecretReadable(tree.pos(),(VarSymbol)((JCIdent)tree.indexed).sym);
} else if (tree.indexed instanceof JCFieldAccess && ((JCFieldAccess)tree.indexed).sym instanceof VarSymbol) {
checkSecretReadable(tree.pos(),(VarSymbol)((JCFieldAccess)tree.indexed).sym);
}
// FIXME - forbid everything else?
result = saved;
}
protected void checkSecretCallable(JCMethodInvocation tree, MethodSymbol msym) {
DiagnosticPosition pos = tree.meth.pos();
if (tree.meth instanceof JCFieldAccess) {
// FIXME - really want this from pos to endpos, not from startpos to endpos
pos = ((JCFieldAccess)tree.meth).pos();
}
JmlSpecs.MethodSpecs mspecs = specs.getSpecs(msym);
VarSymbol calledSecret = null;
VarSymbol calledQuery = null;
boolean calledPure = false;
if (mspecs != null) {
calledSecret = getSecretSymbol(mspecs.mods);
calledQuery = getQuerySymbol(tree,mspecs.mods);
calledPure = findMod(mspecs.mods,JmlTokenKind.PURE) != null;
}
if (currentQueryContext != null) {
// query method - may call query methods for a contained datagroup
// - may call secret methods for a contained datagroup
// - may call open pure method
if (calledSecret != null) {
if (!isContainedInDatagroup(calledSecret,currentQueryContext)) {
log.error(pos,"jml.incorrect.datagroup");
}
}
if (calledQuery != null) {
if (!isContainedInDatagroup(calledQuery,currentQueryContext)) {
log.error(pos,"jml.incorrect.datagroup");
}
}
if (calledSecret == null && calledQuery == null) {
if (!calledPure) {
log.error(pos,"jml.incorrect.datagroup");
}
}
}
if (currentSecretContext != null && currentSecretContext != currentQueryContext) {
if (calledSecret != null) {
if (!isContainedInDatagroup(calledSecret,currentSecretContext)) {
log.error(pos,"jml.incorrect.datagroup");
}
}
if (calledQuery != null) {
if (!isContainedInDatagroup(calledQuery,currentSecretContext)) {
log.error(pos,"jml.incorrect.datagroup");
}
}
if (calledSecret == null && calledQuery == null) {
if (!calledPure) {
log.error(pos,"jml.incorrect.datagroup");
}
} }
if (currentQueryContext == null && currentSecretContext == null) {
// open method - may call query methods, but no secret methods
if (calledSecret != null) {
log.error(pos,"jml.open.may.not.call.secret");
}
}
}
protected void checkSecretReadable(DiagnosticPosition pos, VarSymbol vsym) {
// If the variable is local to the method, then secret/query rules do not apply
if (vsym.owner instanceof MethodSymbol) return;
JmlSpecs.FieldSpecs fspecs = specs.getSpecs(vsym);
boolean identIsSecret = fspecs != null && findMod(fspecs.mods,JmlTokenKind.SECRET) != null;
// Rules:
// If method is open, then ident may not be secret
// If method is query and we are in the method specs, then ident may not be secret
// If method is query, then ident is open or is secret for the same datagroup
// If method is secret, then ident is open or is secret for the same datagroup
if (identIsSecret) {
boolean prevAllow = ((JmlResolve)rs).setAllowJML(true);
if (currentSecretContext != null && isContainedInDatagroup(vsym,currentSecretContext)) {
// OK - we are in a secret context and the variable is in that context
} else if (currentClauseType == JmlTokenKind.IN || currentClauseType == JmlTokenKind.MAPS) {
// OK - this is the target of an in or maps clause - secrecy restrictions are checked there
} else if (currentQueryContext != null && isContainedInDatagroup(vsym,currentQueryContext)) {
// OK - we are in a query context and the variable in secret for that context
} else if (currentSecretContext != null) {
log.error(pos,"jml.not.in.secret.context",vsym.getQualifiedName(),currentSecretContext.getQualifiedName());
} else {
// in open context
log.error(pos,"jml.no.secret.in.open.context",vsym.getQualifiedName());
}
((JmlResolve)rs).setAllowJML(prevAllow);
}
}
protected void checkSecretWritable(DiagnosticPosition pos, VarSymbol vsym) {
// If the variable is local to the method, then secret/query rules do not apply
if (vsym.owner instanceof MethodSymbol) return;
JmlSpecs.FieldSpecs fspecs = specs.getSpecs(vsym);
boolean identIsSecret = fspecs != null && findMod(fspecs.mods,JmlTokenKind.SECRET) != null;
// Rules:
// If method is open, then ident may not even be read
// If method is query, then ident must be secret for the same datagroup
// If method is secret, then ident must be secret for the same datagroup
boolean prevAllow = ((JmlResolve)rs).setAllowJML(true);
boolean error = false;
if (currentSecretContext != null) {
if (!identIsSecret || !isContainedInDatagroup(vsym,currentSecretContext)) {
// ERROR - may not write a non-secret field in a secret context
// ERROR - field is not in the correct secret context to be written
log.error(pos,"jml.not.writable.in.secret.context",vsym.getQualifiedName(),currentSecretContext.getQualifiedName());
error = true;
}
}
if (currentQueryContext != null && !error) {
if (!identIsSecret || !isContainedInDatagroup(vsym,currentQueryContext)) {
// ERROR - may not write a non-secret field in a secret context
// ERROR - field is not in the correct secret context to be written
log.error(pos,"jml.not.writable.in.secret.context",vsym.getQualifiedName(),currentQueryContext.getQualifiedName());
}
}
((JmlResolve)rs).setAllowJML(prevAllow);
}
boolean justAttribute = false;
protected void attributeGroup(JmlGroupName g) {
// Note that this.env should be the class env of the environment in which the group name is being resolved
if (g.sym == null) {
// Possibly not yet resolved - perhaps a forward reference, or perhaps does not exist
boolean prevj = justAttribute;
justAttribute = true;
boolean prev = JmlResolve.instance(context).allowJML();
JmlResolve.instance(context).setAllowJML(true);
try {
g.accept(this);
} finally {
JmlResolve.instance(context).setAllowJML(prev);
justAttribute = prevj;
}
}
}
// Returns true if contextSym is contained (transitively) in the varSym datagroup
protected boolean isContainedInDatagroup(@Nullable VarSymbol varSym, @Nullable VarSymbol contextSym) {
if (varSym == contextSym) return true;
JmlSpecs.FieldSpecs fspecs = specs.getSpecs(varSym);
for (JmlTypeClause t: fspecs.list) {
if (t.token == JmlTokenKind.IN) { // FIXME - relies on variable IN clauses being attributed before a method that uses them
for (JmlGroupName g: ((JmlTypeClauseIn)t).list) {
attributeGroup(g);
if (varSym == g.sym) { // Explicitly listed in self - should this be allowed? (FIXME)
continue;
}
if (g.sym != null) { // try again - if still null, it is because the datagroup mentioned in the
// in clause is not resolvable - perhaps does not exist
boolean b = isContainedInDatagroup(g.sym,contextSym);
if (b) return true;
} else if (g.selection instanceof JCIdent && ((JCIdent)g.selection).name == contextSym.name) {
return true; // Not resolvable - return true to avoid additional errors later
} else {
return false;
}
}
}
}
return false;
}
public boolean checkForCircularity(VarSymbol varsym) {
Collection<VarSymbol> roots = new LinkedList<>();
return checkForCircularity(varsym, roots);
}
public boolean checkForCircularity(VarSymbol varsym, Collection<VarSymbol> roots) {
if (roots.contains(varsym)) return true;
roots.add(varsym);
JmlSpecs.FieldSpecs fspecs = specs.getSpecs(varsym);
for (JmlTypeClause t: fspecs.list) {
if (t.token == JmlTokenKind.IN) {
for (JmlGroupName g: ((JmlTypeClauseIn)t).list) {
attributeGroup(g);
if (g.sym == null) {
continue;
}
if (g.sym == varsym) {
// FIXME - should we waran about listing oneslef in an IN clause?
} else if (checkForCircularity(g.sym,roots)) {
return true;
}
}
}
}
roots.remove(varsym);
return false;
}
/** Attributes a member select expression (e.g. a.b); also makes sure
* that the type of the selector (before the dot) will be attributed;
* that makes sure that the specifications of members are properly
* attributed when needed later in esc or rac.
*/
@Override
public void visitSelect(JCFieldAccess tree) {
// if (tree.name != null && tree.name.toString().equals("class")) {
// int i = 0;
// }
if (tree.name != null) {
if (!tree.toString().startsWith("java") && !tree.toString().startsWith("org.")) {
if (tree.toString().endsWith(".buf")) Utils.stop();
}
super.visitSelect(tree);
// if (tree.sym instanceof ClassSymbol) ((JmlCompiler)JmlCompiler.instance(context)).loadSpecsForBinary(null,(ClassSymbol)tree.sym);
// The super call does not always call check... (which assigns the
// determined type to tree.type, particularly if an error occurs,
// so we fill it in
if (tree.type == null) tree.type = result;
} else {
// This is a store-ref with a wild-card field
// FIXME - the following needs some review
attribTree(tree.selected, env, new ResultInfo(TYP|VAR, Infer.anyPoly));
result = tree.type = Type.noType;
}
Type saved = result;
Symbol s = tree.selected.type.tsym;
if (!(s instanceof PackageSymbol)) {
ClassSymbol c = null;
if (s instanceof ClassSymbol) c = (ClassSymbol)s;
else c = s.enclClass();
if (c != null) addTodo(c);
}
if (tree.sym != null) checkVisibility(tree, jmlVisibility, tree.sym);
// For selections that are fields with an enclosing class, we check whether it is readable
// The check on the enclosing class omits fields such as .class
if (tree.sym instanceof VarSymbol && tree.sym.enclClass() != null) {
checkSecretReadable(tree.pos(),(VarSymbol)tree.sym);
} // FIXME - what else could it be, besides an error?
// if (tree.sym instanceof ClassSymbol) ((JmlCompiler)JmlCompiler.instance(context)).loadSpecsForBinary(env,(ClassSymbol)tree.sym);
result = saved;
}
@Override
public void visitTypeCast(JCTypeCast tree) {
if (tree.clazz instanceof JmlPrimitiveTypeTree) {
// FIXME - this needs to be expanded to include real and bigint and
// arrays of such
JmlTokenKind t = ((JmlPrimitiveTypeTree)tree.clazz).token;
Type clazztype = attribType(tree.clazz, env);
if (t == JmlTokenKind.BSTYPEUC) {
chk.validate(tree.clazz, env);
Type exprtype = attribExpr(tree.expr, env, Infer.anyPoly);
// Only Class objects may be cast to TYPE
// Compare tsym instead of just the thpe because the
// exprtype is likely a Class<T> and syms.classType is a Class
// or Class<?>
if (exprtype.tsym == syms.classType.tsym) {
result = check(tree, clazztype, VAL, resultInfo);
} else {
log.error(tree.expr.pos,"jml.only.class.cast.to.type",exprtype);
result = tree.type = jmltypes.TYPE;
}
} else {
// For now do no checking // FIXME
Type exprtype = attribExpr(tree.expr, env, Infer.anyPoly);
result = tree.type = clazztype;
}
} else {
super.visitTypeCast(tree);
}
}
/** Attrbutes an array-element-range (a[1 .. 2]) store-ref expression */
public void visitJmlStoreRefArrayRange(JmlStoreRefArrayRange that) {
if (that.lo != null) attribExpr(that.lo,env,syms.intType); // FIXME - int or long or bigint
if (that.hi != null && that.hi != that.lo) attribExpr(that.hi,env,syms.intType); // FIXME - int or long or bigint
Type t = attribExpr(that.expression,env,Type.noType);
if (t.getKind() != TypeKind.ARRAY) {
if (t.getKind() != TypeKind.ERROR) log.error(that.expression.pos(),"jml.not.an.array",t);
t = syms.errType;
result = check(that, t, VAL, resultInfo);
} else {
t = ((ArrayType)t).getComponentType();
result = check(that, t, VAL, resultInfo);
}
}
public void visitJmlStoreRefKeyword(JmlStoreRefKeyword that) {
result = that.type = Type.noType;
// FIXME - call check
}
// @Override
// public void visitReturn(JCReturn that) {
//// if (addRac && that.expr != null) {
//// that.expr = make.Assign(make.Ident(resultName),that.expr);
//// }
// super.visitReturn(that);
// }
/** This is a map from token to Name. It has to be generated at runtime because
* Names are dependent on the Context. It is supposedly a very fast
* (i.e. array) lookup. The Names are the fully-qualified name of the type
* of the annotation that represents the given modifier token.
*/
public EnumMap<JmlTokenKind,Name> tokenToAnnotationName = new EnumMap<JmlTokenKind,Name>(JmlTokenKind.class);
/** A map from token to ClassSymbol, valid for tokens that have annotation equivalents. */
public EnumMap<JmlTokenKind,ClassSymbol> tokenToAnnotationSymbol = new EnumMap<JmlTokenKind,ClassSymbol>(JmlTokenKind.class);
/** A Name for the fully-qualified name of the package that the JML annotations are defined in. */
public Name annotationPackageName;
/** A Name for the fully-qualified name of the package that the JML annotations are defined in. */
public PackageSymbol annotationPackageSymbol;
/** For the given context, initializes the value of packageName and the
* content of the tokenToAnnotationName mapping; since Name objects are
* involved and they are defined per context, this initialization must be
* performed after a context is defined.
* @param context the compilation context in which to do this initialization
*/
public void initAnnotationNames(Context context) {
Names names = Names.instance(context);
annotationPackageName = names.fromString(Strings.jmlAnnotationPackage);
for (JmlTokenKind t: JmlTokenKind.modifiers) {
if (t.annotationType == null) {
// No class for this token, but we won't complain
// The result is to silently ignore the token (TODO)
} else {
String s = t.annotationType.getName();
Name n = names.fromString(s);
tokenToAnnotationName.put(t,n);
ClassSymbol sym = ClassReader.instance(context).enterClass(n);
tokenToAnnotationSymbol.put(t,sym);
}
}
annotationPackageSymbol = tokenToAnnotationSymbol.get(JmlTokenKind.PURE).packge();
}
/** Checks that all of the JML annotations present in the first argument
* are also present in the second argument, issuing error messages if they
* are not.
* @param annotations a list of annotations to check
* @param allowed the set of allowed annotations
* @param place a description of where the annotations came from, for error messages
*/
public void allAllowed(List<JCTree.JCAnnotation> annotations, JmlTokenKind[] allowed, String place) {
outer: for (JCTree.JCAnnotation a: annotations) {
for (JmlTokenKind c: allowed) {
if (a.annotationType.type.tsym.flatName().equals(tokenToAnnotationName.get(c))) continue outer; // Found it
}
// a is not is the list, but before we complain, check that it is
// one of our annotations
if (a.annotationType.type.tsym.packge().flatName().equals(annotationPackageName)) { // FIXME - change to comparing symbols instead of strings?
JavaFileObject prev = log.useSource(((JmlTree.JmlAnnotation)a).sourcefile);
log.error(a.pos,"jml.illegal.annotation",place);
log.useSource(prev);
}
}
}
/** This checks that the given modifier set does not have annotations for
* both of a pair of mutually exclusive annotations; it prints an error
* message if they are both present; returns true if an error happened
* @param mods the modifiers to check
* @param ta the first JML token
* @param tb the second JML token
*/
public boolean checkForConflict(JCModifiers mods, JmlTokenKind ta, JmlTokenKind tb) {
JCTree.JCAnnotation a,b;
a = utils.findMod(mods,tokenToAnnotationSymbol.get(ta));
b = utils.findMod(mods,tokenToAnnotationSymbol.get(tb));
if (a != null && b != null) {
JavaFileObject prev = log.useSource(((JmlTree.JmlAnnotation)b).sourcefile);
log.error(b.pos(),"jml.conflicting.modifiers",ta.internedName(),tb.internedName());
log.useSource(prev);
return true;
}
return false;
}
public boolean checkForRedundantSpecMod(JCModifiers mods) {
JCTree.JCAnnotation a;
boolean result = false;
if ((mods.flags & Flags.PROTECTED) != 0 &&
(a=utils.findMod(mods,tokenToAnnotationSymbol.get(SPEC_PROTECTED))) != null ) {
JavaFileObject prev = log.useSource(((JmlTree.JmlAnnotation)a).sourcefile);
log.warning(a.pos(),"jml.redundant.visibility","protected","spec_protected");
log.useSource(prev);
result = true;
}
if ((mods.flags & Flags.PUBLIC) != 0 &&
(a=utils.findMod(mods,tokenToAnnotationSymbol.get(SPEC_PROTECTED))) != null ) {
JavaFileObject prev = log.useSource(((JmlTree.JmlAnnotation)a).sourcefile);
log.warning(a.pos(),"jml.redundant.visibility","public","spec_protected");
log.useSource(prev);
result = true;
}
if ((mods.flags & Flags.PUBLIC) != 0 &&
(a=utils.findMod(mods,tokenToAnnotationSymbol.get(SPEC_PUBLIC))) != null ) {
JavaFileObject prev = log.useSource(((JmlTree.JmlAnnotation)a).sourcefile);
log.warning(a.pos(),"jml.redundant.visibility","public","spec_public");
log.useSource(prev);
result = true;
}
return result;
}
/** Finds the annotation in the modifiers corresponding to the given token
* @param mods the modifiers to check
* @param ta the token to look for
* @return a reference to the annotation AST node, or null if not found
*/
//@ nullable
public JmlAnnotation findMod(/*@nullable*/JCModifiers mods, JmlTokenKind ta) {
if (mods == null) return null;
return utils.findMod(mods,tokenToAnnotationSymbol.get(ta));
}
/** Returns true if the given modifiers includes model
* @param mods the modifiers to check
* @return true if the model modifier is present, false if not
*/
public boolean isModel(/*@nullable*/JCModifiers mods) {
return findMod(mods,JmlTokenKind.MODEL) != null;
}
/** Returns true if the given symbol has a given annotation
* @param symbol the symbol to check
* @return true if the symbol has a given annotation, false otherwise
*/
public boolean hasAnnotation(Symbol symbol, JmlTokenKind t) {
return symbol.attribute(tokenToAnnotationSymbol.get(t)) != null;
}
/** Returns true if the given symbol has a given annotation
* @param symbol the symbol to check
* @return true if the symbol has a given annotation, false otherwise
*/
public Attribute.Compound findAnnotation(Symbol symbol, JmlTokenKind t) {
return symbol.attribute(tokenToAnnotationSymbol.get(t));
}
/** Returns true if the given symbol has a model annotation
* @param symbol the symbol to check
* @return true if the symbol has a model annotation, false otherwise
*/
public boolean isModel(Symbol symbol) {
// if (modelAnnotationSymbol == null) {
// modelAnnotationSymbol = ClassReader.instance(context).enterClass(names.fromString("org.jmlspecs.annotation.Model"));
// }
return symbol.attribute(tokenToAnnotationSymbol.get(JmlTokenKind.MODEL))!=null;
}
// /** Returns true if the given symbol has a pure annotation
// * @param symbol the symbol to check
// * @return true if the symbol has a model annotation, false otherwise
// */
// public boolean isPureClass(ClassSymbol symbol) {
// return specs.isPure(symbol);
//// TypeSpecs tspecs = specs.getSpecs(symbol);
//// if (tspecs == null) return false;
//// return findMod(tspecs.modifiers,PURE) != null;
// }
public boolean isPureMethod(MethodSymbol symbol) {
for (MethodSymbol msym: Utils.instance(context).parents(symbol)) {
MethodSpecs mspecs = specs.getSpecs(msym);
if (mspecs == null) {
// FIXME - A hack - the .jml file should have been read for org.jmlspecs.lang.JMLList
if (msym.toString().equals("size()") && msym.owner.toString().equals(Strings.jmlSpecsPackage + ".JMLList")) return true;
// FIXME - check when this happens - is it because we have not attributed the relevant class (and we should) or just because there are no specs
continue;
}
boolean isPure = specs.isPure(symbol);
if (isPure) return true;
}
return false;
}
/** Returns true if the given symbol has a pure annotation
* @param symbol the symbol to check
* @return true if the symbol has a model annotation, false otherwise
*/
public boolean isHelper(MethodSymbol symbol) {
MethodSpecs mspecs = specs.getSpecs(symbol);
if (mspecs == null) {
// FIXME - check when this happens - is it because we have not attributed the relevant class (and we should) or just because there are no specs
return false;
}
return findMod(mspecs.mods,JmlTokenKind.HELPER) != null || findMod(mspecs.mods,JmlTokenKind.FUNCTION) != null;
//
// if (symbol.attributes_field == null) return false; // FIXME - should have the attributes - this is necessary but why?
// return symbol.attribute(tokenToAnnotationSymbol.get(JmlToken.HELPER))!=null;
}
public boolean isFunction(MethodSymbol symbol) {
MethodSpecs mspecs = specs.getSpecs(symbol);
if (mspecs == null) {
// FIXME - check when this happens - is it because we have not attributed the relevant class (and we should) or just because there are no specs
return false;
}
return findMod(mspecs.mods,JmlTokenKind.FUNCTION) != null;
}
public boolean isImmutable(ClassSymbol symbol) {
TypeSpecs mspecs = specs.getSpecs(symbol);
if (mspecs == null) {
// FIXME - check when this happens - is it because we have not attributed the relevant class (and we should) or just because there are no specs
return false;
}
return findMod(mspecs.modifiers,JmlTokenKind.IMMUTABLE) != null;
}
public JCAnnotation hasAnnotation(JmlVariableDecl decl, JmlTokenKind token) {
if (decl.specsDecl != null) {
return findMod(decl.specsDecl.mods, token);
} else {
return findMod(decl.mods, token);
}
}
/** Returns true if the given modifiers/annotations includes ghost
* @param mods the modifiers to check
* @return true if the ghost modifier is present, false if not
*/
public boolean isGhost(/*@nullable*/JCModifiers mods) {
return findMod(mods,JmlTokenKind.GHOST) != null;
}
/** Returns true if the given modifiers/annotations includes static
* @param mods the modifiers to check
* @return true if the static modifier is present, false if not
*/
public boolean isStatic(JCModifiers mods) {
return (mods.flags & Flags.STATIC)!=0;
}
/** Returns true if the given flags includes static
* @param flags the modifier flags to check
* @return true if the static modifier is present, false if not
*/
public boolean isStatic(long flags) {
return (flags & Flags.STATIC)!=0;
}
public JCFieldAccess findUtilsMethod(String n) {
Scope.Entry e = utilsClass.members().lookup(names.fromString("assertionFailure"));
Symbol ms = e.sym;
JCFieldAccess m = make.Select(utilsClassIdent,names.fromString("assertionFailure"));
m.sym = ms;
m.type = m.sym.type;
return m;
}
public JCStatement methodCallPre(String sp, JCExpression tree) {
// org.jmlspecs.utils.Utils.assertionFailure();
String s = sp + "precondition is false";
JCExpression lit = makeLit(syms.stringType,s);
JCFieldAccess m = findUtilsMethod("assertionFailure");
JCExpression nulllit = makeLit(syms.botType, null);
JCExpression c = make.Apply(null,m,List.<JCExpression>of(lit,nulllit));
c.setType(Type.noType);
return make.Exec(c);
}
public JCStatement methodCallPost(String sp, JCExpression tree) {
// org.jmlspecs.utils.Utils.assertionFailure();
String s = sp + "postcondition is false";
JCExpression lit = make.Literal(s);
JCFieldAccess m = findUtilsMethod("assertionFailure");
JCExpression nulllit = makeLit(syms.botType, null);
JCExpression c = make.Apply(null,m,List.<JCExpression>of(lit,nulllit));
c.setType(Type.noType);
return make.Exec(c);
}
// public JCStatement methodCall(JmlStatementExpr tree) {
// // org.jmlspecs.utils.Utils.assertionFailure();
//
// JmlToken t = tree.token;
// String s = t == JmlToken.ASSERT ? "assertion is false" : t == JmlToken.ASSUME ? "assumption is false" : "unreachable statement reached";
// s = tree.source.getName() + ":" + tree.line + ": JML " + s;
// JCExpression lit = make.Literal(s);
// JCFieldAccess m = findUtilsMethod("assertionFailure");
// JCExpression nulllit = makeLit(syms.botType, null);
// JCExpression c = make.Apply(null,m,List.<JCExpression>of(lit,nulllit));
// c.setType(Type.noType);
// return make.Exec(c);
// }
//
// public JCStatement methodCall(JmlStatementExpr tree, JCExpression translatedOptionalExpr) {
// // org.jmlspecs.utils.Utils.assertionFailure();
//
// JmlToken t = tree.token;
// String s = t == JmlToken.ASSERT ? "assertion is false" : t == JmlToken.ASSUME ? "assumption is false" : "unreachable statement reached";
// s = tree.source.getName() + ":" + tree.line + ": JML " + s;
// JCExpression lit = make.Literal(s);
// JCFieldAccess m = findUtilsMethod("assertionFailure");
// JCExpression c = make.Apply(null,m,List.<JCExpression>of(lit,translatedOptionalExpr));
// c.setType(Type.noType);
// return make.Exec(c);
// }
/** Returns a string combining the file name and line number, for RAC error messages
* @param source the file containing the source location being referenced
* @param pos the character position within the file [TODO - 0 or 1-based?]
*/
public String position(JavaFileObject source, int pos) {
JavaFileObject pr = log.currentSourceFile();
log.useSource(source);
String s = source.getName() + ":" + log.currentSource().getLineNumber(pos) + ": JML ";
log.useSource(pr);
return s;
}
protected int loopIndexCount = 0;
/** Attributes the specs for a do-while loop */
public void visitJmlDoWhileLoop(JmlDoWhileLoop that) {
loopStack.add(0,treeutils.makeIdent(that.pos, "loopIndex_" + (++loopIndexCount), syms.intType));
attribLoopSpecs(that.loopSpecs,env);
super.visitDoLoop(that);
loopStack.remove(0);
}
java.util.List<JCIdent> loopStack = new java.util.LinkedList<JCIdent>();
java.util.List<JmlEnhancedForLoop> foreachLoopStack = new java.util.LinkedList<JmlEnhancedForLoop>();
public void visitJmlEnhancedForLoop(JmlEnhancedForLoop tree) {
loopStack.add(0,treeutils.makeIdent(tree.pos, "loopIndex_" + (++loopIndexCount), syms.intType));
foreachLoopStack.add(0,tree);
Env<AttrContext> loopEnv =
env.dup(env.tree, env.info.dup(env.info.scope.dup()));
try {
// MAINTENANCE ISSUE: code duplicated mostly from the superclass
//the Formal Parameter of a for-each loop is not in the scope when
//attributing the for-each expression; we mimick this by attributing
//the for-each expression first (against original scope).
Type exprType = types.cvarUpperBound(attribExpr(tree.expr, loopEnv));
attribStat(tree.var, loopEnv);
chk.checkNonVoid(tree.pos(), exprType);
Type elemtype = types.elemtype(exprType); // perhaps expr is an array?
if (elemtype == null) {
// or perhaps expr implements Iterable<T>?
Type base = types.asSuper(exprType, syms.iterableType.tsym);
if (base == null) {
log.error(tree.expr.pos(),
"foreach.not.applicable.to.type",
exprType,
diags.fragment("type.req.array.or.iterable"));
elemtype = types.createErrorType(exprType);
} else {
List<Type> iterableParams = base.allparams();
elemtype = iterableParams.isEmpty()
? syms.objectType
: types.wildUpperBound(iterableParams.head);
}
}
chk.checkType(tree.expr.pos(), elemtype, tree.var.sym.type);
loopEnv.tree = tree; // before, we were not in loop!
trForeachLoop(tree,tree.var.sym.type); // DRC - added
attribStat(tree.body, loopEnv);
attribLoopSpecs(tree.loopSpecs,loopEnv); // DRC - added
result = null;
// Type exprType = types.cvarUpperBound(attribExpr(tree.expr, loopEnv));
// savedSpecOK = true;
// attribStat(tree.var, loopEnv);
// chk.checkNonVoid(tree.pos(), exprType);
// Type elemtype = types.elemtype(exprType); // perhaps expr is an array?
// if (elemtype == null) {
// // or perhaps expr implements Iterable<T>?
// Type base = types.asSuper(exprType, syms.iterableType.tsym);
// if (base == null) {
// log.error(tree.expr.pos(),
// "foreach.not.applicable.to.type",
// exprType,
// diags.fragment("type.req.array.or.iterable"));
// elemtype = types.createErrorType(exprType);
// } else {
// List<Type> iterableParams = base.allparams();
// elemtype = iterableParams.isEmpty()
// ? syms.objectType
// : types.wildUpperBound(iterableParams.head);
// }
// }
// chk.checkType(tree.expr.pos(), elemtype, tree.var.sym.type);
// loopEnv.tree = tree; // before, we were not in loop!
} finally {
loopEnv.info.scope.leave();
loopStack.remove(0);
foreachLoopStack.remove(0);
}
}
/** Returns an unattributed expression tree that boxes the given
* expression to the given type. It is the caller's responsibility to
* be sure that the type of the expression argument is consistent with the
* given target type.
* @param e the expression to box
* @param boxedtype the target type
* @return the boxed expression
*/
public JCExpression autobox(JCExpression e, Type boxedtype) {
factory.at(e.pos);
//Type boxed = Types.instance(context).boxedClass(vartype).type;
Name valueof = names.fromString("valueOf");
JCExpression s = factory.Select(factory.Type(boxedtype),valueof);
s = factory.Apply(null,s,List.<JCExpression>of(e));
return s;
}
/** Returns an unattributed expression tree that unboxes the given
* expression to the given type. It is the caller's responsibility to
* be sure that the type of the expression argument is consistent with the
* given target type.
* @param e the expression to unbox
* @param vartype the target unboxed type
* @return the unboxed expression
*/
public JCExpression autounbox(JCExpression e, Type vartype) {
factory.at(e.pos);
String name = null;
switch (vartype.getKind()) {
case BYTE: name = "byteValue"; break;
case BOOLEAN: name = "booleanValue"; break;
case INT: name = "intValue"; break;
case SHORT: name = "shortValue"; break;
case LONG: name = "longValue"; break;
case CHAR: name = "charValue"; break;
case FLOAT: name = "floatValue"; break;
case DOUBLE: name = "doubleValue"; break;
default:
log.error(e.pos,"jml.invalid.unboxing",vartype);
return factory.Erroneous();
}
Name value = names.fromString(name);
JCFieldAccess s = factory.Select(e,value);
s.type = vartype; // FIXME - no sym set? or is this a method type?
JCMethodInvocation ss = factory.Apply(null,s,List.<JCExpression>nil());
ss.type = vartype; // FIXME - typeargs, varargselement ?
return ss;
}
/** Translates an enhanced for loop into a traditional for loop so that
* we have access to loop variables for use in invariants.
* @param tree the enhanced for loop
* @param vartype the type of the loop variable
*/
// Translating: T elem : e
public void trForeachLoop(JmlEnhancedForLoop tree, Type vartype) {
// vartype is T ; boxedVarType is T' which is the boxed type (if necessary) of T
// Desugar the foreach loops in order to put in the JML auxiliary loop indices
factory.at(tree.pos); // Sets the position until reset
ListBuffer<JCStatement> stats = new ListBuffer<JCStatement>();
ListBuffer<JCExpressionStatement> step = new ListBuffer<JCExpressionStatement>();
// tree.indexDecl: int $$index$nnn = 0
Name name = names.fromString("$$index$"+tree.pos);
tree.indexDecl = makeVariableDecl(name,syms.intType,zeroLit,tree.pos);
tree.indexDecl.sym.owner = tree.var.sym.owner;
factory.at(tree.pos+1);
JCIdent ident = factory.Ident(tree.indexDecl.sym);
JCExpressionStatement st = factory.Exec(factory.Unary(JCTree.Tag.PREINC,ident)); // ++ $$index;
st.type = syms.intType;
stats.append(tree.indexDecl); // stats gets int $$index$nnn = 0;
step.append(st); // step gets ++ $$index$nnn;
factory.at(tree.pos);
Type boxedVarType = vartype;
if (vartype.isPrimitive()) {
boxedVarType = Types.instance(context).boxedClass(vartype).type;
}
Type elemtype = null;
if (tree.expr.type.getTag() == TypeTag.ARRAY) {
elemtype = ((ArrayType)tree.expr.type).elemtype;
} else {
elemtype = vartype; // FIXME - this should be the type returned by the iterator
}
{
Name defempty = names.fromString("defaultEmpty");
JCFieldAccess sel = factory.Select(factory.Type(utilsClass.type),defempty);
JCExpression e = factory.Apply(List.<JCExpression>of(factory.Type(boxedVarType)),sel,List.<JCExpression>nil());
// FIXME e.type = ?
// e: Utils.<T'>defaultEmpty()
int p = tree.pos;
name = names.fromString("$$values$"+p);
ClassType ct = new ClassType(JMLValuesType.getEnclosingType(),List.<Type>of(boxedVarType),JMLValuesType.tsym);
tree.valuesDecl = makeVariableDecl(name,ct,e,p);
tree.valuesDecl.sym.owner = tree.var.sym.owner;
// tree.valuesDecl : JMLValuesType<T'> $$values$ppp = Utils.<T'>defaultEmpty();
stats.append(tree.valuesDecl);
// // Add postconditions of defaultEmpty by hand
// // assume $$values$ppp != null;
// // assume $$values$ppp.length == 0;
// JCExpression nn = factory.at(p).Binary(JCTree.NE, factory.Ident(tree.valuesDecl), nullLit );
// stats.append( factory.at(p).JmlExpressionStatement(JmlToken.ASSUME, Label.POSTCONDITION, nn));
// nn = factory.at(p).Select(factory.Ident(tree.valuesDecl), names.fromString("size"));
// nn = factory.at(p).Apply(null,nn,List.<JCExpression>nil());
// nn = factory.at(p).Binary(JCTree.EQ, nn, zeroLit );
// stats.append( factory.at(p).JmlExpressionStatement(JmlToken.ASSUME, Label.POSTCONDITION, nn));
factory.at(tree.pos+2);
Name add = names.fromString("add");
sel = factory.Select(factory.Ident(tree.valuesDecl),add); // $$values$ppp.add
//sel.type =
JCExpression ev = factory.Ident(tree.var); // elem
if (vartype.isPrimitive()) ev = autobox(ev,boxedVarType);
JCMethodInvocation app = factory.Apply(null,sel,List.<JCExpression>of(ev)); // $$values$ppp.add(autobox(ev)); [autoboxing only if necessary]
//app.type = tree.valuesDecl.type; // FIXME _ check this
//
factory.at(tree.pos+3);
JCAssign asgn = factory.Assign(factory.Ident(tree.valuesDecl),app);
asgn.type = asgn.lhs.type;
step.append(factory.Exec(asgn));
factory.at(tree.pos);
}
stats.append(tree.var);
JCExpression cond = null;
ListBuffer<JCStatement> bodystats = new ListBuffer<JCStatement>();
JCExpression newvalue;
JmlStatementLoop inv = null;
if (tree.expr.type.getTag() == TypeTag.ARRAY) {
// Replace the foreach loop for (T t: a) body;
// by
// int $$index = 0;
// final non_null JMLList $$values = Utils.<T'>defaultEmpty();
// T t;
// for (; $$index < a.length; $$index++, $$values.add(t') ) {
// check loop invariant
// t = a[$$index];
// body
// }
JCExpression arraylen = factory.Select(tree.expr,syms.lengthVar);
cond = factory.Binary(JCTree.Tag.LT,ident,arraylen);
cond.type = syms.booleanType;
newvalue = factory.Indexed(tree.expr,ident); // newvalue :: expr[$$index]
// FIXME newvalue.type = ???
if (elemtype.isPrimitive() && !vartype.isPrimitive()) {
newvalue = autobox(newvalue,vartype);
} else if (!elemtype.isPrimitive() && vartype.isPrimitive()) {
newvalue = autounbox(newvalue,vartype);
}
JCBinary invexpr = factory.Binary(JCTree.Tag.AND,factory.Binary(JCTree.Tag.LE,zeroLit,ident),factory.Binary(JCTree.Tag.LE,ident,arraylen));
invexpr.type = invexpr.lhs.type = invexpr.rhs.type = syms.booleanType;
inv = factory.JmlStatementLoop(JmlTokenKind.LOOP_INVARIANT,invexpr);
} else {
// Replace the foreach loop for (T t: c) body;
// by
// int $$index = 0;
// JMLList $$values = Utils.<T>defaultEmpty();
// Iterator<T> it = c.iterator();
// T t = null;
// for (; it.hasNext(); $$index++, $$values.add(t)) {
// t = it.next();
// body
// }
name = names.fromString("$$iter$"+tree.pos);
ClassType ct = new ClassType(JMLIterType.getEnclosingType(),List.<Type>of(boxedVarType),JMLIterType.tsym);
tree.iterDecl = makeVariableDecl(name,ct,nullLit,tree.pos);
tree.iterDecl.sym.owner = tree.var.sym.owner;
stats.append(tree.iterDecl);
Name hasNext = names.fromString("hasNext");
JCFieldAccess sel = factory.Select(factory.Ident(tree.iterDecl),hasNext);
cond = factory.Apply(null,sel,List.<JCExpression>nil()); // cond :: $$iter . hasNext()
cond.type = syms.booleanType;
Name next = names.fromString("next");
sel = factory.Select(factory.Ident(tree.iterDecl),next);
newvalue = factory.Apply(null,sel,List.<JCExpression>nil()); // newvalue :: $$iter . next()
// FIXME - newvalue.type = ???
}
bodystats.append(factory.Exec(factory.Assign(factory.Ident(tree.var),newvalue))); // t = newvalue;
// FIXME - assign types
bodystats.append(tree.body);
factory.at(tree.pos+1);
Name sz = names.fromString("size");
JCFieldAccess sel = factory.Select(factory.Ident(tree.valuesDecl),sz);
// FIXME sel.type ??? invexpr2.type
JCExpression invexpr2 = factory.Apply(null,sel,List.<JCExpression>nil()); // invexpr2 :: $$values . size()
JCBinary invexpr3 = factory.Binary(JCTree.Tag.AND,factory.Binary(JCTree.Tag.NE,nullLit,factory.Ident(tree.valuesDecl)),factory.Binary(JCTree.Tag.EQ,ident,invexpr2));
invexpr3.type = invexpr3.lhs.type = invexpr3.rhs.type = syms.booleanType;
JmlStatementLoop inv2 = factory.JmlStatementLoop(JmlTokenKind.LOOP_INVARIANT,invexpr3);
factory.at(tree.pos);
JCBlock block = factory.Block(0,bodystats.toList());
block.endpos = (tree.body instanceof JCBlock) ? ((JCBlock)tree.body).endpos : tree.body.pos;
JCForLoop forstatement = factory.ForLoop(List.<JCStatement>nil(),cond,step.toList(),block);
JmlForLoop jmlforstatement = factory.JmlForLoop(forstatement,tree.loopSpecs);
{
ListBuffer<JmlStatementLoop> list = new ListBuffer<JmlStatementLoop>();
list.append(inv2);
if (inv != null) list.append(inv);
if (tree.loopSpecs != null) list.appendList(tree.loopSpecs);
jmlforstatement.loopSpecs = list.toList();
}
stats.append(jmlforstatement);
JCBlock blockk = factory.Block(0,stats.toList());
blockk.endpos = block.endpos;
tree.implementation = blockk;
tree.internalForLoop = jmlforstatement;
// tree.var = translate(tree.var);
// tree.expr = translate(tree.expr);
// tree.body = translate(tree.body);
// result = tree;
}
// MAINTENANCE ISSUE: code duplicated mostly from the superclass
public void visitJmlForLoop(JmlForLoop tree) {
loopStack.add(0,treeutils.makeIdent(tree.pos, "loopIndex_" + (++loopIndexCount), syms.intType));
Env<AttrContext> loopEnv =
env.dup(env.tree, env.info.dup(env.info.scope.dup()));
savedSpecOK = true;
attribStats(tree.init, loopEnv);
if (tree.cond != null) attribExpr(tree.cond, loopEnv, syms.booleanType);
loopEnv.tree = tree; // before, we were not in loop!
attribLoopSpecs(tree.loopSpecs, loopEnv);
// FIXME - should this be before or after the preceding statement
attribStats(tree.step, loopEnv);
attribStat(tree.body, loopEnv);
loopEnv.info.scope.leave();
result = null;
loopStack.remove(0);
}
public void visitJmlWhileLoop(JmlWhileLoop that) {
loopStack.add(0,treeutils.makeIdent(that.pos, "loopIndex_" + (++loopIndexCount), syms.intType));
attribLoopSpecs(that.loopSpecs,env);
super.visitWhileLoop(that);
loopStack.remove(0);
}
public void visitJmlStatementLoop(JmlStatementLoop that) {
attribExpr(that.expression,env);
}
public void visitJmlChoose(JmlChoose that) {
// FIXME - fill in
}
public void visitJmlClassDecl(JmlClassDecl that) {
// Typically, classes are attributed by calls to attribClass and
// then to attibClassBody and attribClassBodySpecs, but local
// classes do end up here.
that.toplevel = (JmlCompilationUnit)enclosingClassEnv.toplevel;
if (that.specsDecl == null) {
// A local class is its own specification , so we fill in the
// specification information. It might be nice to do this
// earlier and avoid this check, but there is no convenient place
// during parsing, and the Enter/MemberEnter steps do not handle
// local classes.
// There is also the case of anonymous classes in expressions that
// in class specs (e.g. invariants)
// if (enclosingMethodEnv == null && !isInJmlDeclaration) {
// // Warn for non-local classes
// log.error("jml.internal.notsobad","A non-local class's specsDecl field was unexpectedly null in JmlAtt.visitJmlClassDecl: " + that.name);
// }
that.specsDecl = that;
that.typeSpecs = new JmlSpecs.TypeSpecs(that);
}
// boolean prev = isInJmlDeclaration;
// if (implementationAllowed) isInJmlDeclaration = true;
// try {
visitClassDef(that);
// } finally {
// isInJmlDeclaration = prev;
// }
}
public void visitClassDef(JCClassDecl tree) {
// The superclass calls classEnter if the env is owned by a VAR or MTH.
// But JML has the case of an anonymous class that occurs in a class
// specification (e.g. an invariant), or in a method clause (so it is
// owned by the method)
if ((env.info.scope.owner.kind & (VAR | MTH)) == 0 && tree.sym == null) {
enter.classEnter(tree, env);
}
super.visitClassDef(tree);
}
public void visitJmlCompilationUnit(JmlCompilationUnit that) {
visitTopLevel(that);
}
@Override
public void visitJmlMethodDecl(JmlMethodDecl that) {
visitMethodDef(that);
}
public static class SpecialDiagnosticPosition extends com.sun.tools.javac.util.JCDiagnostic.SimpleDiagnosticPosition {
String message;
public SpecialDiagnosticPosition(String message) {
super(-1);
this.message = message;
}
}
// FIXME - is this only ghost declarations
// and don't the modifiers get checked twice - does the super.visitVarDef actually work?
/** Attributes a variable declared within a JML spec - that is a ghost
* variable declaration, along with checking any JML
* annotations and specifications of the declaration. The super call
* adds the declaration to the current scope.
* @param that the AST node to attribute
*/
@Override
public void visitJmlVariableDecl(JmlVariableDecl that) {
JavaFileObject prevSource = null;
try {
if (that.source() != null) prevSource = log.useSource(that.source());
// If there is a .jml file containing the specifications, then just use those instead of the annotations in the
// .java file. FIXME - really we should always be looking in the specs database, rather than modifying the ast
JCModifiers originalMods = that.mods;
JCModifiers newMods = originalMods;
if (that.specsDecl != null) newMods = that.mods = that.specsDecl.mods;
// FIXME - we should not need these two lines I think, but otherwise we get NPE faults on non_null field declarations
attribAnnotationTypes(that.mods.annotations,env); annotate.flush();
for (JCAnnotation a: that.mods.annotations) a.type = a.annotationType.type;
boolean prev = false;
if (utils.isJML(that.mods)) {
prev = ((JmlResolve)rs).setAllowJML(true);
}
if (that.vartype.type == null) attribType(that.vartype,env);
// if (that.name.toString().equals("objectState")) Utils.stop();
((JmlMemberEnter)memberEnter).dojml = true;
visitVarDef(that);
((JmlMemberEnter)memberEnter).dojml = false;
if (that.sym == null) return; // Duplicate to be removed
// Anonymous classes construct synthetic members (constructors at least)
// which are not JML nodes.
FieldSpecs fspecs = specs.getSpecs(that.sym);
// We do the checking of in and maps clauses after all fields and methods have been attributed
//if (fspecs != null) for (JmlTypeClause spec: fspecs.list) spec.accept(this);
if (!that.type.isPrimitive()) {
JCAnnotation snullness;
JmlTokenKind nullness = specs.defaultNullity(enclosingClassEnv.enclClass.sym);
if ((snullness=utils.findMod(that.mods,nonnullAnnotationSymbol)) != null) {
nullness = JmlTokenKind.NONNULL;
} else if ((snullness=utils.findMod(that.mods,nullableAnnotationSymbol)) != null) {
nullness = JmlTokenKind.NULLABLE;
} else {
Symbol s = (nullness == JmlTokenKind.NONNULL) ? nonnullAnnotationSymbol : nullableAnnotationSymbol;
Attribute.Compound a = new Attribute.Compound(s.type,List.<Pair<MethodSymbol,Attribute>>nil());
that.sym.appendAttributes(List.<Compound>of(a));
JCAnnotation an = factory.at(that).Annotation(a); // FIXME - needs a position and a source - we should get the NonNullByDefault if possible
((JmlTree.JmlAnnotation)an).sourcefile = that.sourcefile;
an.type = an.annotationType.type;
that.mods.annotations = that.mods.annotations.append(an);
snullness = an;
}
Symbol s = (nullness == JmlTokenKind.NONNULL) ? nonnullAnnotationSymbol : nullableAnnotationSymbol;
if (that.sym.attribute(s) == null) {
Attribute.Compound a = new Attribute.Compound(s.type,List.<Pair<MethodSymbol,Attribute>>nil());
that.sym.appendAttributes(List.<Compound>of(a));
}
}
// if (newMods != originalMods) for (JCAnnotation a: originalMods.annotations) { a.type = attribType(a,env); }
// Check the mods after the specs, because the modifier checks depend on
// the specification clauses being attributed
that.mods = originalMods;
if (!that.type.isErroneous()) checkVarMods(that);
that.mods = newMods;
if (utils.isJML(that.mods)) prev = ((JmlResolve)rs).setAllowJML(prev);
// if (that.specsDecl != null) {
// that.mods.annotations = that.specsDecl.mods.annotations;
// }
} finally {
if (prevSource != null) log.useSource(prevSource);
}
}
// These are here mostly to make them visible to extensions
/** This is here also to do some checking for missing implementations */
@Override
public Type attribExpr(JCTree tree, Env<AttrContext> env, Type pt) {
Type type = super.attribExpr(tree,env,pt);
return type;
}
public Symbol attribIdent(JCTree tree, JCCompilationUnit topLevel) {
Symbol s = super.attribIdent(tree,topLevel);
return s;
}
/** Attribute the arguments in a method call, returning a list of types;
* the arguments themselves have their types recorded.
*/
@Override
public int attribArgs(List<JCExpression> trees, Env<AttrContext> env, ListBuffer<Type> argtypes) {
return super.attribArgs(trees,env,argtypes);
}
/** Attribute the elements of a type argument list, returning a list of types;
* the type arguments themselves have their types recorded.
*/
@Override
public List<Type> attribTypes(List<JCExpression> trees, Env<AttrContext> env) {
return super.attribTypes(trees,env);
}
boolean checkingSignature = false;
public void visitJmlMethodSig(JmlMethodSig that) {
Env<AttrContext> localEnv = env.dup(that.expression, env.info.dup());
List<Type> argtypes = that.argtypes != null ? super.attribAnyTypes(that.argtypes, localEnv) : List.<Type>nil();
List<Type> typeargtypes = List.<Type>nil(); // attribAnyTypes(that.typeargs, localEnv);// FIXME - need to handle template arguments
Name methName = TreeInfo.name(that.expression);
boolean isConstructorCall =
methName == enclosingClassEnv.enclClass.sym.name;
// ... and attribute the method using as a prototype a methodtype
// whose formal argument types is exactly the list of actual
// arguments (this will also set the method symbol).
if (isConstructorCall) {
// Adapted from Attr.visitApply // FIXME - may not be general enough
Type clazztype = attribType(that.expression, env);
ClassType site = new ClassType(clazztype.getEnclosingType(),
clazztype.tsym.type.getTypeArguments(),
clazztype.tsym);
Env<AttrContext> diamondEnv = localEnv.dup(that);
diamondEnv.info.selectSuper = false;
diamondEnv.info.pendingResolutionPhase = null;
//if the type of the instance creation expression is a class type
//apply method resolution inference (JLS 15.12.2.7). The return type
//of the resolved constructor will be a partially instantiated type
Symbol constructor = rs.resolveDiamond(that.pos(),
diamondEnv,
site,
argtypes,
typeargtypes);
that.methodSymbol = (MethodSymbol)constructor.baseSymbol();
} else {
// Adapted from Attr.visitMethodDef // FIXME - may not be general enough
int kind = VAL; // FIXME - could also be POLY
Type mpt = newMethodTemplate(Type.noType, argtypes, typeargtypes);
localEnv.info.pendingResolutionPhase = null;
boolean prev = checkingSignature;
checkingSignature = true;
Type mtype = attribTree(that.expression, localEnv, new ResultInfo(kind, mpt, chk.basicHandler));
checkingSignature = prev;
Symbol sym = null;
if (that.expression instanceof JCFieldAccess) {
sym = ((JCFieldAccess)that.expression).sym;
} else { // JCIdent
sym = ((JCIdent)that.expression).sym;
}
if (sym instanceof MethodSymbol) that.methodSymbol = (MethodSymbol)sym;
else {
// Constructor ?
// FIXME
}
}
// // FIXME - need a better check that the expression is a constructor
// // This won't even work if the method has the class name as a suffix
// Name name;
// Type classType;
// if (that.expression instanceof JCIdent) {
// name = ((JCIdent)that.expression).name;
// classType = env.enclClass.type;
// } else {
// JCFieldAccess fa = (JCFieldAccess)that.expression;
// name = fa.name;
// if (fa.selected instanceof JCIdent && ((JCIdent)fa.selected).name == names._this) {
// attribExpr(fa.selected,localEnv);
// classType = fa.selected.type;
// } else {
// attribExpr(fa.selected,localEnv);
// classType = fa.selected.type;
// }
// }
// if (name.toString().equals(env.enclClass.name.toString())) {
// Symbol sym = jmlresolve.resolveConstructor(that.pos(),localEnv,
// env.enclClass.type,
// argtypes,
// typeargtypes);
// that.methodSymbol = (MethodSymbol)sym;
// } else {
// Symbol sym = jmlresolve.resolveMethod(that.pos(), localEnv, name, argtypes, typeargtypes);
// //Symbol sym = jmlresolve.findMethod(localEnv, classType, name, argtypes, typeargtypes,false,false,false);
//
// // If there was an error attributing the JmlMethodSig, then sym
// // will not be a MethodSymbol
//// if (!(sym instanceof MethodSymbol)) {
//// jmlerror(that,"internal.error","No method found with the given signature");
//// }
// that.methodSymbol = sym instanceof MethodSymbol ? (MethodSymbol)sym : null;
// }
}
public void visitJmlModelProgramStatement(JmlModelProgramStatement that) {
// TODO Auto-generated method stub
}
public void jmlerror(int begin, int end, String key, Object... args) {
log.error(new JmlTokenizer.DiagnosticPositionSE(begin,end-1),key,convertErrorArgs(args));
}
public void jmlerror(JCTree tree, String key, Object... args) {
log.error(new JmlTokenizer.DiagnosticPositionSE(tree.pos,tree.getEndPosition(log.currentSource().getEndPosTable())),key,convertErrorArgs(args));
}
public Object[] convertErrorArgs(Object[] args) {
Object[] out = new Object[args.length];
for (int i=0; i<args.length; i++) {
Object a = args[i];
out[i] = a instanceof JCTree ? JmlPretty.write((JCTree)a) : a;
}
return args;
}
public static class RACCopy extends JmlTreeCopier {
List<JCVariableDecl> decls;
Names names = Names.instance(context);
Symtab syms = Symtab.instance(context);
ListBuffer<JCExpression> arguments;
JCIdent argsID;
Map<Symbol,JCIdent> newids;
public RACCopy(Context context, List<JCVariableDecl> decls, ListBuffer<JCExpression> args, JCIdent argsID, Map<Symbol,JCIdent> newids) {
super(context, JmlTree.Maker.instance(context));
this.decls = decls;
this.arguments = args;
this.argsID = argsID;
this.newids = newids;
}
public static JCExpression copy(JCExpression that, Context context, List<JCVariableDecl> decls, ListBuffer<JCExpression> args, JCIdent argsID, Map<Symbol,JCIdent> newids) {
return new RACCopy(context,decls,args,argsID,newids).copy(that,(Void)null);
}
public <T extends JCTree> T copy(T tree, Void p) {
if (tree == null) return null;
if (!(tree instanceof JCIdent)) {
if (tree instanceof JCExpression) {
JCExpression expr = (JCExpression)tree;
if (RACCheck.allInternal(expr,decls)) {
return tree;
}
if (RACCheck.allExternal(expr,decls)) {
if (expr.type.getTag() != TypeTag.METHOD) {
int n = arguments.size();
arguments.add(expr);
JCExpression lit = M.Literal(TypeTag.INT,n).setType(syms.intType);
JCExpression arg = M.Indexed(argsID,lit).setType(syms.objectType);
arg = M.TypeCast(M.Type(expr.type),arg).setType(expr.type);
return (T)arg;
}
}
}
}
return (T) (tree.accept(this, p));
}
@Override
public JCTree visitIdentifier(IdentifierTree node, Void p) {
JCIdent expr = (JCIdent)node;
if (!(expr.sym instanceof Symbol.VarSymbol)) { //Method and class symbols can be internal or external
return super.visitIdentifier(node,p);
}
// if (RACCheck.allInternal(expr,decls)) {
// return expr;
// }
if (RACCheck.allExternal(expr,decls)) {
int n = arguments.size();
arguments.add(expr);
JCExpression lit = M.Literal(TypeTag.INT,n).setType(syms.intType);
JCExpression arg = M.Indexed(argsID,lit).setType(syms.objectType);
arg = M.TypeCast(M.Type(expr.type),arg).setType(expr.type); // M.Type sets its own type
return arg;
}
Symbol n = expr.sym;
JCIdent id = newids.get(n);
if (id == null) {
System.out.println("ERROR"); // FIXME
}
return id;
// Include this for symmetry, but we acually should never get to this point
// super.visitIdentifier(node,p);
}
@Override
public JCTree visitJmlQuantifiedExpr(JmlQuantifiedExpr that, Void p) {
return copy(that.racexpr);
// List<JCVariableDecl> prevList = decls;
// decls = decls.prependList(that.decls.toList());
// try {
// JCExpression newrange = copy(that.range);
// JCExpression newvalue = copy(that.value);
// return M.JmlQuantifiedExpr(that.op, that.decls, newrange, newvalue);
// } finally {
// decls = prevList;
// }
}
}
public static class RACCheck extends JmlTreeScanner {
public static class RCheckEx extends RuntimeException {}
boolean checkInternal;
List<JCVariableDecl> decls;
public RACCheck(List<JCVariableDecl> decls) {
this.decls = decls;
}
public static boolean allInternal(JCExpression expr, List<JCVariableDecl> decls) {
try {
RACCheck s = new RACCheck(decls);
s.checkInternal = true;
expr.accept(s);
return true;
} catch (RCheckEx e) {
return false;
}
}
public static boolean allExternal(JCExpression expr, List<JCVariableDecl> decls) {
try {
RACCheck s = new RACCheck(decls);
s.checkInternal = false;
expr.accept(s);
return true;
} catch (RCheckEx e) {
return false;
}
}
@Override
public void visitIdent(JCIdent that) {
Symbol n = that.sym;
if (!(n instanceof Symbol.VarSymbol)) return; //Method and class symbols can be internal or external
Iterator<JCVariableDecl> iter = decls.iterator();
while (iter.hasNext()) {
JCVariableDecl d = iter.next();
if (d.sym.equals(n)) {
// identifier matches a quantifier declaration
// so it is definitely internal
//if (checkInternal) return;
throw new RCheckEx();
}
}
// No matches found so the identifier is
// definitely external
if (checkInternal) throw new RCheckEx();
}
@Override
public void visitJmlSingleton(JmlSingleton that) {
if (that.token == JmlTokenKind.BSRESULT) {
// external
if (checkInternal) throw new RCheckEx();
}
}
@Override
public void visitJmlMethodInvocation(JmlMethodInvocation that) {
if (that.token == JmlTokenKind.BSOLD || that.token == JmlTokenKind.BSPAST || that.token == JmlTokenKind.BSPRE) {
// external
if (checkInternal) throw new RCheckEx();
}
}
}
@Override
protected boolean isBooleanOrNumeric(Env<AttrContext> env, JCExpression tree) {
if (tree instanceof JmlExpression) {
if (tree instanceof JmlQuantifiedExpr) return true; // At least for current quantifiers: forall, exists, sum, prod, num_of
if (tree instanceof JmlSingleton) {
JmlTokenKind kind = ((JmlSingleton)tree).token;
if (kind == JmlTokenKind.INFORMAL_COMMENT) return true;
if (kind == JmlTokenKind.BSRESULT) {
JCTree.JCMethodDecl md = enclosingMethodEnv.enclMethod;
JCTree res = md.getReturnType();
TypeTag t = res.type.getTag();
if (t == TypeTag.BOOLEAN || t == TypeTag.INT || t == TypeTag.LONG || t == TypeTag.SHORT || t == TypeTag.CHAR || t == TypeTag.BYTE) return true;
return false;
}
}
jmlerror(tree,"jml.internal", "Unimplemented option in JmlAttr:isBooleanOrNumeric -- " + tree.getClass());
return false;
}
return super.isBooleanOrNumeric(env,tree);
}
public MethodSymbol makeInitializerMethodSymbol(long flags, Env<AttrContext> env) {
JCTree tree = null;
Env<AttrContext> localEnv =
env.dup(tree, env.info.dup(env.info.scope.dupUnshared()));
MethodSymbol msym = new MethodSymbol(flags | BLOCK |
env.info.scope.owner.flags() & STRICTFP, names.empty, new Type.JCVoidType(),
env.info.scope.owner);
localEnv.info.scope.owner = msym;
if ((flags & STATIC) != 0) localEnv.info.staticLevel++;
return msym;
}
}