package com.sun.tools.javac.comp; import static com.sun.tools.javac.code.Flags.STATIC; 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.VAR; import static com.sun.tools.javac.code.TypeTags.CLASS; import static com.sun.tools.javac.code.TypeTags.TYPEVAR; import java.util.LinkedList; import com.sun.tools.javac.code.Effect; import com.sun.tools.javac.code.Effect.InvocationEffect; import com.sun.tools.javac.code.Effects; import com.sun.tools.javac.code.Lint; import com.sun.tools.javac.code.RPL; import com.sun.tools.javac.code.RPLs; import com.sun.tools.javac.code.Substitute; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Symbol.MethodSymbol; import com.sun.tools.javac.code.Symbol.OperatorSymbol; import com.sun.tools.javac.code.Symbol.VarSymbol; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.Type.ArrayType; import com.sun.tools.javac.code.Type.ClassType; import com.sun.tools.javac.code.Types; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.DPJAtomic; import com.sun.tools.javac.tree.JCTree.DPJCobegin; import com.sun.tools.javac.tree.JCTree.DPJFinish; import com.sun.tools.javac.tree.JCTree.DPJForLoop; import com.sun.tools.javac.tree.JCTree.DPJNegationExpression; import com.sun.tools.javac.tree.JCTree.DPJNonint; import com.sun.tools.javac.tree.JCTree.DPJSpawn; import com.sun.tools.javac.tree.JCTree.JCArrayAccess; import com.sun.tools.javac.tree.JCTree.JCAssert; import com.sun.tools.javac.tree.JCTree.JCAssign; import com.sun.tools.javac.tree.JCTree.JCAssignOp; import com.sun.tools.javac.tree.JCTree.JCBinary; import com.sun.tools.javac.tree.JCTree.JCBlock; import com.sun.tools.javac.tree.JCTree.JCCase; import com.sun.tools.javac.tree.JCTree.JCCatch; import com.sun.tools.javac.tree.JCTree.JCClassDecl; import com.sun.tools.javac.tree.JCTree.JCConditional; import com.sun.tools.javac.tree.JCTree.JCDoWhileLoop; import com.sun.tools.javac.tree.JCTree.JCEnhancedForLoop; import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.tree.JCTree.JCExpressionStatement; import com.sun.tools.javac.tree.JCTree.JCExpressionWithRPL; import com.sun.tools.javac.tree.JCTree.JCFieldAccess; import com.sun.tools.javac.tree.JCTree.JCForLoop; import com.sun.tools.javac.tree.JCTree.JCIdent; import com.sun.tools.javac.tree.JCTree.JCIf; import com.sun.tools.javac.tree.JCTree.JCInstanceOf; import com.sun.tools.javac.tree.JCTree.JCLabeledStatement; import com.sun.tools.javac.tree.JCTree.JCMethodDecl; import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; import com.sun.tools.javac.tree.JCTree.JCNewArray; import com.sun.tools.javac.tree.JCTree.JCNewClass; import com.sun.tools.javac.tree.JCTree.JCParens; import com.sun.tools.javac.tree.JCTree.JCReturn; import com.sun.tools.javac.tree.JCTree.JCStatement; import com.sun.tools.javac.tree.JCTree.JCSwitch; import com.sun.tools.javac.tree.JCTree.JCSynchronized; import com.sun.tools.javac.tree.JCTree.JCThrow; import com.sun.tools.javac.tree.JCTree.JCTreeWithEffects; import com.sun.tools.javac.tree.JCTree.JCTry; import com.sun.tools.javac.tree.JCTree.JCTypeApply; import com.sun.tools.javac.tree.JCTree.JCTypeCast; import com.sun.tools.javac.tree.JCTree.JCUnary; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import com.sun.tools.javac.tree.JCTree.JCWhileLoop; import com.sun.tools.javac.tree.JCTree.LetExpr; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.Log; import com.sun.tools.javac.util.Name; import com.sun.tools.javac.util.Pair; /** * Tree scanner that walks the AST, infers effects, and performs the following * checks: * * 1. Inferred method body effects are a subeffect of their declared effects * (violation = error) * * 2. Inferred effects of each statement in a cobegin block are pairwise * noninterfering (violation = warning) * * 3. Inferred effects of foreach body are noninterfering with themselves, after * replacing the index variable with its negation (violation = warning) * * @author Rob Bocchino * @author Jeff Overbey * @author Mohsen Vakilian */ public class CheckEffects extends EnvScanner { // DPJ protected static final Context.Key<CheckEffects> effectsKey = new Context.Key<CheckEffects>(); private final Name.Table names; private final Log log; private final Types types; private final Resolve rs; private final RPLs rpls; private final Attr attr; private Lint lint; public static CheckEffects instance(Context context) { CheckEffects instance = context.get(effectsKey); if (instance == null) instance = new CheckEffects(context); return instance; } protected CheckEffects(Context context) { super(context); context.put(effectsKey, this); names = Name.Table.instance(context); log = Log.instance(context); types = Types.instance(context); lint = Lint.instance(context); rs = Resolve.instance(context); rpls = RPLs.instance(context); attr = Attr.instance(context); } /** Are we in an atomic statement? */ private boolean inAtomic; /** Are we in a nonint statement? */ private boolean inNonint; /** * Compute interference between sets of statements * @param stats * @return */ private boolean statementsInterfere(List<JCStatement> stats, boolean atomicOK) { if (stats.size() > 1) return statementsInterfere(stats.head, stats.tail, atomicOK); return false; } private boolean statementsInterfere(JCStatement stat, List<JCStatement> stats, boolean atomicOK) { for (JCStatement stat2 : stats) { Effects effects1 = stat.effects.inEnvironment(rs, childEnvs.head, false); Effects effects2 = stat2.effects.inEnvironment(rs, childEnvs.head, false); if (!Effects.noninterferingEffects(effects1, effects2, childEnvs.head.info.constraints, atomicOK)) return true; } if (statementsInterfere(stats, atomicOK)) return true; return false; } /** * Utility class to compute the accessed RPL of an expression * that accesses a location. The expression itself doesn't tell us whether * the access is a read or write; we need the surrounding context (parent * in the AST) to tell us that. */ protected RPL accessedRPL(JCExpression tree, boolean inConstructor) { RPL result = (new RPLAccessVisitor()).accessed(tree, inConstructor); if (tree instanceof JCExpressionWithRPL && inConstructor == false) { ((JCExpressionWithRPL)tree).rpl = result; } return result; } private class RPLAccessVisitor extends JCTree.Visitor { public RPL result = null; public boolean inConstructor = false; public RPL accessed(JCExpression tree, boolean inConstructor) { this.inConstructor = inConstructor; tree.accept(this); return result; } /** * Determine whether a variable symbol is an instance field of the * enclosing class or a superclass. * @param v * @return */ private boolean isInstanceField(VarSymbol v) { Type site = parentEnv.enclClass.sym.type; if (v.owner.kind == TYP && (v.flags() & STATIC) == 0 && (site.tag == CLASS || site.tag == TYPEVAR)) { if (types.asOuterSuper(site, v.owner) != null) { return true; } } return false; } public void visitSelect(JCFieldAccess tree) { VarSymbol v = attr.getVarSymbolFor(tree, parentEnv); if (v != null) { if (!inConstructor || !isInstanceField(v)) { result = attr.getRPLFor(tree,parentEnv); result = attr.asMemberOf(result, tree, parentEnv); result = attr.substOwnerRPL(result, tree, parentEnv); } } } public void visitIdent(JCIdent tree) { VarSymbol v = attr.getVarSymbolFor(tree, parentEnv); if (v != null) { if (!inConstructor || !isInstanceField(v)) { result = attr.getRPLFor(tree, parentEnv); result = attr.asMemberOf(result, tree, parentEnv); } } } public void visitIndexed(JCArrayAccess tree) { result = attr.getRPLFor(tree,parentEnv); result = attr.asMemberOf(result, tree, parentEnv); result = attr.substIndex(result, tree, parentEnv); result = attr.substOwnerRPL(result, tree, parentEnv); } @Override public void visitTree(JCTree tree) { // Default: Do nothing (result = null) } }; /** * Add the effects of from into the effects of to * @param from * @param to */ private void addAll(JCTreeWithEffects from, JCTreeWithEffects to) { to.effects.addAll(from.effects); if (attr.inConstructor(parentEnv)) to.getConstructorEffects().addAll(from.getConstructorEffects()); } private void addAll(List<? extends JCTreeWithEffects> from, JCTreeWithEffects to) { for (List<? extends JCTreeWithEffects> l = from; l.tail != null; l = l.tail) { to.effects.addAll(l.head.effects); if (attr.inConstructor(parentEnv)) to.getConstructorEffects().addAll(l.head.getConstructorEffects()); } } /** * Add the effects of from into the effects of to * If the tree accessed an RPL, add a read effect for it * @param from * @param to */ private void addAllWithRead(JCExpression from, JCTreeWithEffects to) { addAll(from, to); addReadEffect(from, to); } /** * If from accessed an RPL, add a read effect for it to the * effects of to. * @param from * @param to */ private void addReadEffect(JCExpression from, JCTreeWithEffects to) { RPL access = accessedRPL(from, false); if (access != null) to.effects.add(new Effect.ReadEffect(rpls, access, inAtomic, inNonint)); access = accessedRPL(from, true); if (access != null && attr.inConstructor(parentEnv)) to.getConstructorEffects().add(new Effect.ReadEffect(rpls, access, inAtomic, inNonint)); } /** * Add the effects of from into the effects of to. * If the tree accessed an RPL, add a write effect for it. * @param from * @param to */ private void addAllWithWrite(JCExpression from, JCTreeWithEffects to) { to.effects.addAll(from.effects); if (attr.inConstructor(parentEnv)) to.getConstructorEffects().addAll(from.getConstructorEffects()); addWriteEffect(from, to); } /** * If from accessed an RPL, add a write effect for it to the * effects of to. * @param from * @param to */ private void addWriteEffect(JCExpression from, JCTreeWithEffects to) { RPL access = accessedRPL(from, false); if (access != null) to.effects.add(new Effect.WriteEffect(rpls, access, inAtomic, inNonint)); access = accessedRPL(from, true); if (access != null) to.getConstructorEffects().add(new Effect.WriteEffect(rpls, access, inAtomic, inNonint)); } /////////////////////////////////////////////////////////////////////////// // Visitor Methods /////////////////////////////////////////////////////////////////////////// private Effects initEffects; private LinkedList<Pair<Effects, DiagnosticPosition>> ctorEffects; @Override public void visitClassDef(JCClassDecl tree) { Effects savedInitEffects = initEffects; LinkedList<Pair<Effects, DiagnosticPosition>> savedCtorEffects = ctorEffects; initEffects = new Effects(); ctorEffects = new LinkedList<Pair<Effects, DiagnosticPosition>>(); super.visitClassDef(tree); // Check declared constructor effects against initializers Env<AttrContext> env = childEnvs.head; if (env != null) { initEffects = initEffects.inEnvironment(rs, env, true); for (Pair<Effects, DiagnosticPosition> pair : ctorEffects) { Effects declaredEffects = pair.fst.inEnvironment(rs, env, true); if (!initEffects.areSubeffectsOf(declaredEffects)) { log.error(pair.snd, "bad.effect.summary"); System.err.println("Missing " + initEffects.missingFrom(declaredEffects).trim()); } } } initEffects = savedInitEffects; ctorEffects = savedCtorEffects; } @Override public void visitMethodDef(JCMethodDecl tree) { super.visitMethodDef(tree); MethodSymbol m = tree.sym; Effects actualEffects = Effects.UNKNOWN; Effects declaredEffects = m.effects; DiagnosticPosition pos = (tree.effects == null) ? tree.pos() : tree.effects.pos(); if (tree.body != null) { if (!attr.inConstructor(childEnvs.head)) { actualEffects = tree.body.effects.inEnvironment(rs, childEnvs.head, true); } else { actualEffects = tree.body.getConstructorEffects().inEnvironment(rs, childEnvs.head, true); // Add in constructor effects for later checking against initializers ctorEffects.add(new Pair<Effects, DiagnosticPosition>(declaredEffects,pos)); } } if (!actualEffects.areSubeffectsOf(declaredEffects)) { System.err.println("Effect summary does not cover " + actualEffects.missingFrom(declaredEffects)); log.error(pos, "bad.effect.summary"); } } @Override public void visitAssert(JCAssert tree) { super.visitAssert(tree); addAll(tree.cond, tree); if (tree.detail != null) addAll(tree.detail, tree); } @Override public void visitDoLoop(JCDoWhileLoop tree) { super.visitDoLoop(tree); addAllWithRead(tree.cond, tree); addAll(tree.body, tree); } @Override public void visitDPJForLoop(DPJForLoop tree) { super.visitDPJForLoop(tree); if (tree.var.init != null) addAllWithRead(tree.var.init, tree); if (tree.start != null) addAllWithRead(tree.start, tree); if (tree.length != null) addAllWithRead(tree.length, tree); if (tree.stride != null) addAllWithRead(tree.stride, tree); addAll(tree.body, tree); Env<AttrContext> env = parentEnv.dup(tree, parentEnv.info.dup()); env.info.scope.enter(tree.var.sym); Effects effects = tree.body.effects.inEnvironment(rs, env, false); env.info.scope.leave(); Effects negatedEffects = effects.substIndices(List.of(tree.var.sym), List.<JCExpression>of(new DPJNegationExpression(tree.var.sym))); if (!Effects.noninterferingEffects(effects, negatedEffects, env.info.constraints, tree.isNondet)) { log.warning(tree.pos(), "interference.foreach"); } } @Override public void visitFinish(DPJFinish tree) { super.visitFinish(tree); addAll(tree.body, tree); } @Override public void visitAtomic(DPJAtomic tree) { boolean savedInAtomic = inAtomic; inAtomic = true; super.visitAtomic(tree); addAll(tree.body, tree); inAtomic = savedInAtomic; } @Override public void visitNonint(DPJNonint tree) { boolean savedInNonint = inNonint; inNonint = true; super.visitNonint(tree); addAll(tree.body, tree); inNonint = savedInNonint; } @Override public void visitForeachLoop(JCEnhancedForLoop tree) { super.visitForeachLoop(tree); if (tree.var.init != null) addAllWithRead(tree.var.init, tree); addAllWithRead(tree.expr, tree); addAll(tree.body, tree); } @Override public void visitForLoop(JCForLoop tree) { super.visitForLoop(tree); addAll(tree.init, tree); if (tree.cond != null) addAllWithRead(tree.cond, tree); if (tree.step != null) addAll(tree.step, tree); addAll(tree.body, tree); } @Override public void visitIf(JCIf tree) { super.visitIf(tree); addAllWithRead(tree.cond, tree); addAll(tree.thenpart, tree); if (tree.elsepart != null) addAll(tree.elsepart, tree); } @Override public void visitIndexed(JCArrayAccess tree) { super.visitIndexed(tree); addAllWithRead(tree.indexed, tree); addAllWithRead(tree.index, tree); } @Override public void visitLabelled(JCLabeledStatement tree) { super.visitLabelled(tree); addAll(tree.body, tree); } @Override public void visitNewArray(JCNewArray tree) { // TODO Constructor effects super.visitNewArray(tree); } @Override public void visitSpawn(DPJSpawn tree) { super.visitSpawn(tree); addAll(tree.body, tree); } @Override public void visitSwitch(JCSwitch tree) { super.visitSwitch(tree); addAllWithRead(tree.selector, tree); addAll(tree.cases, tree); } @Override public void visitCase(JCCase tree) { super.visitCase(tree); if (tree.pat != null) addAllWithRead(tree.pat, tree); addAll(tree.stats, tree); } @Override public void visitSynchronized(JCSynchronized tree) { super.visitSynchronized(tree); addAllWithRead(tree.lock, tree); addAll(tree.body, tree); } @Override public void visitThrow(JCThrow tree) { super.visitThrow(tree); addAllWithRead(tree.expr, tree); } @Override public void visitTry(JCTry tree) { super.visitTry(tree); addAll(tree.body, tree); addAll(tree.catchers, tree); if (tree.finalizer != null) addAll(tree.finalizer, tree); } @Override public void visitCatch(JCCatch tree) { super.visitCatch(tree); addAll(tree.body, tree); } @Override public void visitTypeCast(JCTypeCast tree) { super.visitTypeCast(tree); addAllWithRead(tree.expr, tree); } @Override public void visitWhileLoop(JCWhileLoop tree) { super.visitWhileLoop(tree); addAllWithRead(tree.cond, tree); addAll(tree.body, tree); } @Override public void visitLetExpr(LetExpr tree) { super.visitLetExpr(tree); addAll(tree.defs, tree); if (tree.expr instanceof JCTreeWithEffects) addAll((JCTreeWithEffects) tree.expr, tree); } @Override public void visitSelect(JCFieldAccess tree) { super.visitSelect(tree); addAllWithRead(tree.selected, tree); } @Override public void visitAssign(JCAssign tree) { super.visitAssign(tree); accumulateAssignEffects(tree.lhs, tree.rhs, tree); } private void accumulateAssignEffects(JCExpression lhs, JCExpression rhs, JCTreeWithEffects to) { addAllWithRead(rhs, to); addAllWithWrite(lhs, to); } @Override public void visitExec(JCExpressionStatement tree) { super.visitExec(tree); addAllWithRead(tree.expr, tree); } @Override public void visitAssignop(JCAssignOp tree) { super.visitAssignop(tree); accumulateAssignEffects(tree.lhs, tree.rhs, tree); } @Override public void visitVarDef(JCVariableDecl tree) { super.visitVarDef(tree); if (tree.init != null) { addAllWithRead(tree.init, tree); if (attr.inClassDef(parentEnv) && !tree.mods.areStatic()) { // Record field initializer effects for checking against // constructors initEffects.addAll(tree.init.effects); } } } @Override public void visitParens(JCParens tree) { super.visitParens(tree); addAllWithRead(tree.expr, tree); } @Override public void visitTypeTest(JCInstanceOf tree) { super.visitTypeTest(tree); addAllWithRead(tree.expr, tree); } @Override public void visitUnary(JCUnary tree) { super.visitUnary(tree); switch (((OperatorSymbol)tree.operator).opcode) { case JCTree.PREINC: case JCTree.PREDEC: case JCTree.POSTINC: case JCTree.POSTDEC: addAllWithWrite(tree.arg, tree); break; default: addAllWithRead(tree.arg, tree); break; } } @Override public void visitBinary(JCBinary tree) { super.visitBinary(tree); addAllWithRead(tree.lhs, tree); addAllWithRead(tree.rhs, tree); } @Override public void visitConditional(JCConditional tree) { super.visitConditional(tree); addAllWithRead(tree.cond, tree); addAllWithRead(tree.truepart, tree); addAllWithRead(tree.falsepart, tree); } @Override public void visitApply(JCMethodInvocation tree) { super.visitApply(tree); // We are accumulating effects for e.m(e1, ..., en) // Accumulate the effects from evaluating e addAll(tree.meth, tree); // Accumulate any effects from evaluating e1, ..., en for (JCExpression arg : tree.args) { addAllWithRead(arg, tree); } // Accumulate the effect of invoking m MethodSymbol sym = tree.getMethodSymbol(); if (sym != null) { Effects effects = sym.effects.translateMethodEffects(tree, types, attr, parentEnv); InvocationEffect ie = new InvocationEffect(rpls, sym, effects); if (inNonint) { ie = (InvocationEffect) ie.inNonint(); } else if (inAtomic) { ie = (InvocationEffect) ie.inAtomic(); } tree.effects.add(ie); if (attr.inConstructor(parentEnv)) tree.getConstructorEffects().add(ie); } } @Override public void visitReturn(JCReturn tree) { super.visitReturn(tree); // Effects for 'return' or 'return e' if (tree.expr != null) { // 'return e' // Accumulate effects of evaluating e addAllWithRead(tree.expr, tree); } } @Override public void visitNewClass(JCNewClass tree) { super.visitNewClass(tree); // Effects for new T(e1, ... en) // Only effects are those of evaluating e1, ..., en for (JCExpression arg : tree.args) { // TODO: Handle RPL arguments to constructor addAllWithRead(arg, tree); } } @Override public void visitTypeApply(JCTypeApply tree) { super.visitTypeApply(tree); } @Override public void visitBlock(JCBlock tree) { super.visitBlock(tree); for (JCTree.JCStatement stat : tree.stats) { addAll(stat, tree); } if (attr.inClassDef(parentEnv) && !tree.isStatic()) { // Record instance initializer effects for checking against // constructors initEffects.addAll(tree.effects); } } @Override public void visitCobegin(DPJCobegin tree) { super.visitCobegin(tree); tree.effects = tree.body.effects; if (attr.inConstructor(parentEnv)) tree.setConstructorEffects(tree.body.getConstructorEffects()); boolean interfere = false; if (tree.body instanceof JCBlock) { interfere = statementsInterfere(((JCBlock) tree.body).stats, tree.isNondet); } if (interfere) { log.warning(tree.pos(), "interference.cobegin"); } } }