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;
}
}