package org.checkerframework.checker.lock; import com.sun.source.tree.ClassTree; import com.sun.source.tree.MethodTree; import java.util.List; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.type.TypeMirror; import org.checkerframework.dataflow.analysis.FlowExpressions; import org.checkerframework.dataflow.analysis.FlowExpressions.Receiver; import org.checkerframework.dataflow.analysis.TransferInput; import org.checkerframework.dataflow.analysis.TransferResult; import org.checkerframework.dataflow.cfg.UnderlyingAST; import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGMethod; import org.checkerframework.dataflow.cfg.UnderlyingAST.Kind; import org.checkerframework.dataflow.cfg.node.LocalVariableNode; import org.checkerframework.dataflow.cfg.node.Node; import org.checkerframework.dataflow.cfg.node.SynchronizedNode; import org.checkerframework.framework.flow.CFAbstractTransfer; import org.checkerframework.framework.flow.CFValue; import org.checkerframework.javacutil.InternalUtils; import org.checkerframework.javacutil.TreeUtils; /* * LockTransfer handles constructors, initializers, synchronized methods, and synchronized blocks. */ public class LockTransfer extends CFAbstractTransfer<CFValue, LockStore, LockTransfer> { /** Type-specific version of super.analysis. */ protected LockAnalysis analysis; protected LockChecker checker; private LockAnnotatedTypeFactory atypeFactory; public LockTransfer(LockAnalysis analysis, LockChecker checker) { // Always run the Lock Checker with -AconcurrentSemantics turned on. super(analysis, true /* useConcurrentSemantics */); // This assignment is needed (even though the super constructor is called) because // LockTransfer.analysis shadows CFAbstractTransfer.analysis, this.analysis = analysis; this.checker = checker; this.atypeFactory = (LockAnnotatedTypeFactory) analysis.getTypeFactory(); } /** Sets a given {@link Node} to @LockHeld in the given {@code store}. */ protected void makeLockHeld(LockStore store, Node node) { Receiver internalRepr = FlowExpressions.internalReprOf(atypeFactory, node); store.insertValue(internalRepr, atypeFactory.LOCKHELD); } /** Sets a given {@link Node} to @LockPossiblyHeld in the given {@code store}. */ protected void makeLockPossiblyHeld(LockStore store, Node node) { Receiver internalRepr = FlowExpressions.internalReprOf(atypeFactory, node); // insertValue cannot change an annotation to a less // specific type (e.g. LockHeld to LockPossiblyHeld), // so insertLockPossiblyHeld is called. store.insertLockPossiblyHeld(internalRepr); } /** Sets a given {@link Node} {@code node} to LockHeld in the given {@link TransferResult}. */ protected void makeLockHeld(TransferResult<CFValue, LockStore> result, Node node) { if (result.containsTwoStores()) { makeLockHeld(result.getThenStore(), node); makeLockHeld(result.getElseStore(), node); } else { makeLockHeld(result.getRegularStore(), node); } } /** * Sets a given {@link Node} {@code node} to LockPossiblyHeld in the given {@link * TransferResult}. */ protected void makeLockPossiblyHeld(TransferResult<CFValue, LockStore> result, Node node) { if (result.containsTwoStores()) { makeLockPossiblyHeld(result.getThenStore(), node); makeLockPossiblyHeld(result.getElseStore(), node); } else { makeLockPossiblyHeld(result.getRegularStore(), node); } } @Override public LockStore initialStore( UnderlyingAST underlyingAST, /*@Nullable */ List<LocalVariableNode> parameters) { LockStore store = super.initialStore(underlyingAST, parameters); Kind astKind = underlyingAST.getKind(); // Methods with the 'synchronized' modifier are // holding the 'this' lock. // There is a subtle difference between synchronized methods // and constructors/initializers. A synchronized method is only // taking the intrinsic lock of the current object. It says nothing // about any fields of the current object. // Furthermore, since the current object already exists, // other objects may be guarded by the current object. So // a synchronized method can affect the locking behavior of other // objects. // A constructor/initializer behaves as if the current object // and all its non-static fields were held as locks. But in // reality no locks are held. // Furthermore, since the current object is being constructed, // no other object can be guarded by it or any of its non-static // fields. // Handle synchronized methods and constructors. if (astKind == Kind.METHOD) { CFGMethod method = (CFGMethod) underlyingAST; MethodTree methodTree = method.getMethod(); ExecutableElement methodElement = TreeUtils.elementFromDeclaration(methodTree); if (methodElement.getModifiers().contains(Modifier.SYNCHRONIZED)) { final ClassTree classTree = method.getClassTree(); TypeMirror classType = InternalUtils.typeOf(classTree); if (methodElement.getModifiers().contains(Modifier.STATIC)) { store.insertValue( new FlowExpressions.ClassName(classType), atypeFactory.LOCKHELD); } else { store.insertThisValue(atypeFactory.LOCKHELD, classType); } } else if (methodElement.getKind() == ElementKind.CONSTRUCTOR) { store.setInConstructorOrInitializer(); } } else if (astKind == Kind.ARBITRARY_CODE) { // Handle initializers store.setInConstructorOrInitializer(); } return store; } @Override public TransferResult<CFValue, LockStore> visitSynchronized( SynchronizedNode n, TransferInput<CFValue, LockStore> p) { TransferResult<CFValue, LockStore> result = super.visitSynchronized(n, p); // Handle the entering and leaving of the synchronized block if (n.getIsStartOfBlock()) { makeLockHeld(result, n.getExpression()); } else { makeLockPossiblyHeld(result, n.getExpression()); } return result; } }