blob: 7bfd556030e169f8f0cbf03d2fb580b5b4ddb060 [file] [log] [blame]
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.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.dataflow.expression.ClassName;
import org.checkerframework.dataflow.expression.JavaExpression;
import org.checkerframework.framework.flow.CFAbstractTransfer;
import org.checkerframework.framework.flow.CFValue;
import org.checkerframework.javacutil.TreeUtils;
/**
* LockTransfer handles constructors, initializers, synchronized methods, and synchronized blocks.
*/
public class LockTransfer extends CFAbstractTransfer<CFValue, LockStore, LockTransfer> {
/** The type factory associated with this transfer function. */
private final LockAnnotatedTypeFactory atypeFactory;
/**
* Create a transfer function for the Lock Checker.
*
* @param analysis the analysis this transfer function belongs to
* @param checker the type-checker this transfer function belongs to
*/
public LockTransfer(LockAnalysis analysis, LockChecker checker) {
// Always run the Lock Checker with -AconcurrentSemantics turned on.
super(analysis, /*useConcurrentSemantics=*/ true);
this.atypeFactory = (LockAnnotatedTypeFactory) analysis.getTypeFactory();
}
/**
* Sets a given {@link Node} to @LockHeld in the given {@code store}.
*
* @param store the store to update
* @param node the node that should be @LockHeld
*/
protected void makeLockHeld(LockStore store, Node node) {
JavaExpression internalRepr = JavaExpression.fromNode(node);
store.insertValue(internalRepr, atypeFactory.LOCKHELD);
}
/**
* Sets a given {@link Node} to @LockPossiblyHeld in the given {@code store}.
*
* @param store the store to update
* @param node the node that should be @LockPossiblyHeld
*/
protected void makeLockPossiblyHeld(LockStore store, Node node) {
JavaExpression internalRepr = JavaExpression.fromNode(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, 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 = TreeUtils.typeOf(classTree);
if (methodElement.getModifiers().contains(Modifier.STATIC)) {
store.insertValue(new 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;
}
}