blob: 9f9ac013726eeefbff33e0507b066198e10b948b [file] [log] [blame]
package org.checkerframework.checker.lock;
import java.util.ArrayList;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement;
import org.checkerframework.checker.lock.LockAnnotatedTypeFactory.SideEffectAnnotation;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
import org.checkerframework.dataflow.cfg.visualize.CFGVisualizer;
import org.checkerframework.dataflow.expression.ArrayAccess;
import org.checkerframework.dataflow.expression.ClassName;
import org.checkerframework.dataflow.expression.FieldAccess;
import org.checkerframework.dataflow.expression.JavaExpression;
import org.checkerframework.dataflow.expression.LocalVariable;
import org.checkerframework.dataflow.expression.MethodCall;
import org.checkerframework.dataflow.expression.ThisReference;
import org.checkerframework.framework.flow.CFAbstractStore;
import org.checkerframework.framework.flow.CFValue;
import org.checkerframework.framework.source.SourceChecker;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.QualifierHierarchy;
import org.checkerframework.javacutil.AnnotationUtils;
/**
* The Lock Store behaves like CFAbstractStore but requires the ability to insert exact annotations.
* This is because we want to be able to insert @LockPossiblyHeld to replace @LockHeld, which
* normally is not possible in CFAbstractStore since @LockHeld is more specific.
*/
public class LockStore extends CFAbstractStore<CFValue, LockStore> {
/**
* If true, indicates that the store refers to a point in the code inside a constructor or
* initializer. This is useful because constructors and initializers are special with regard to
* the set of locks that is considered to be held. For example, 'this' is considered to be held
* inside a constructor.
*/
protected boolean inConstructorOrInitializer = false;
private final LockAnnotatedTypeFactory atypeFactory;
public LockStore(LockAnalysis analysis, boolean sequentialSemantics) {
super(analysis, sequentialSemantics);
this.atypeFactory = (LockAnnotatedTypeFactory) analysis.getTypeFactory();
}
/** Copy constructor. */
public LockStore(LockAnalysis analysis, CFAbstractStore<CFValue, LockStore> other) {
super(other);
this.inConstructorOrInitializer = ((LockStore) other).inConstructorOrInitializer;
this.atypeFactory = ((LockStore) other).atypeFactory;
}
@Override
public LockStore leastUpperBound(LockStore other) {
LockStore newStore = super.leastUpperBound(other);
// Least upper bound of a boolean
newStore.inConstructorOrInitializer =
this.inConstructorOrInitializer && other.inConstructorOrInitializer;
return newStore;
}
/*
* Insert an annotation exactly, without regard to whether an annotation was already present.
* This is only done for @LockPossiblyHeld. This is not sound for other type qualifiers.
*/
public void insertLockPossiblyHeld(JavaExpression je) {
if (je.containsUnknown()) {
// Expressions containing unknown expressions are not stored.
return;
}
if (je instanceof LocalVariable) {
LocalVariable localVar = (LocalVariable) je;
CFValue current = localVariableValues.get(localVar);
CFValue value = changeLockAnnoToTop(je, current);
if (value != null) {
localVariableValues.put(localVar, value);
}
} else if (je instanceof FieldAccess) {
FieldAccess fieldAcc = (FieldAccess) je;
CFValue current = fieldValues.get(fieldAcc);
CFValue value = changeLockAnnoToTop(je, current);
if (value != null) {
fieldValues.put(fieldAcc, value);
}
} else if (je instanceof MethodCall) {
MethodCall method = (MethodCall) je;
CFValue current = methodValues.get(method);
CFValue value = changeLockAnnoToTop(je, current);
if (value != null) {
methodValues.put(method, value);
}
} else if (je instanceof ArrayAccess) {
ArrayAccess arrayAccess = (ArrayAccess) je;
CFValue current = arrayValues.get(arrayAccess);
CFValue value = changeLockAnnoToTop(je, current);
if (value != null) {
arrayValues.put(arrayAccess, value);
}
} else if (je instanceof ThisReference) {
thisValue = changeLockAnnoToTop(je, thisValue);
} else if (je instanceof ClassName) {
ClassName className = (ClassName) je;
CFValue current = classValues.get(className);
CFValue value = changeLockAnnoToTop(je, current);
if (value != null) {
classValues.put(className, value);
}
} else {
// No other types of expressions need to be stored.
}
}
/**
* Makes a new CFValue with the same annotations as currentValue except that the annotation in the
* LockPossiblyHeld hierarchy is set to LockPossiblyHeld. If currentValue is null, then a new
* value is created where the annotation set is LockPossiblyHeld and GuardedByUnknown
*/
private CFValue changeLockAnnoToTop(JavaExpression je, CFValue currentValue) {
if (currentValue == null) {
Set<AnnotationMirror> set = AnnotationUtils.createAnnotationSet();
set.add(atypeFactory.GUARDEDBYUNKNOWN);
set.add(atypeFactory.LOCKPOSSIBLYHELD);
return analysis.createAbstractValue(set, je.getType());
}
QualifierHierarchy hierarchy = atypeFactory.getQualifierHierarchy();
Set<AnnotationMirror> currentSet = currentValue.getAnnotations();
AnnotationMirror gb =
hierarchy.findAnnotationInHierarchy(currentSet, atypeFactory.GUARDEDBYUNKNOWN);
Set<AnnotationMirror> newSet = AnnotationUtils.createAnnotationSet();
newSet.add(atypeFactory.LOCKPOSSIBLYHELD);
if (gb != null) {
newSet.add(gb);
}
return analysis.createAbstractValue(newSet, currentValue.getUnderlyingType());
}
public void setInConstructorOrInitializer() {
inConstructorOrInitializer = true;
}
@Override
public @Nullable CFValue getValue(JavaExpression expr) {
if (inConstructorOrInitializer) {
// 'this' is automatically considered as being held in a constructor or initializer.
// The class name, however, is not.
if (expr instanceof ThisReference) {
initializeThisValue(atypeFactory.LOCKHELD, expr.getType());
} else if (expr instanceof FieldAccess) {
FieldAccess fieldAcc = (FieldAccess) expr;
if (!fieldAcc.isStatic() && fieldAcc.getReceiver() instanceof ThisReference) {
insertValue(fieldAcc.getReceiver(), atypeFactory.LOCKHELD);
}
}
}
return super.getValue(expr);
}
@Override
protected String internalVisualize(CFGVisualizer<CFValue, LockStore, ?> viz) {
return viz.visualizeStoreKeyVal("inConstructorOrInitializer", inConstructorOrInitializer)
+ viz.getSeparator()
+ super.internalVisualize(viz);
}
@Override
protected boolean isSideEffectFree(AnnotatedTypeFactory atypeFactory, ExecutableElement method) {
LockAnnotatedTypeFactory lockAnnotatedTypeFactory = (LockAnnotatedTypeFactory) atypeFactory;
SourceChecker checker = lockAnnotatedTypeFactory.getChecker();
return checker.hasOption("assumeSideEffectFree")
|| checker.hasOption("assumePure")
|| lockAnnotatedTypeFactory.methodSideEffectAnnotation(method, false)
== SideEffectAnnotation.RELEASESNOLOCKS
|| super.isSideEffectFree(atypeFactory, method);
}
@Override
public void updateForMethodCall(
MethodInvocationNode n, AnnotatedTypeFactory atypeFactory, CFValue val) {
super.updateForMethodCall(n, atypeFactory, val);
ExecutableElement method = n.getTarget().getMethod();
// The following behavior is similar to setting the sideEffectsUnrefineAliases field of
// Lockannotatedtypefactory, but it affects only one of the two type hierarchies, so it
// cannot use that logic.
if (!isSideEffectFree(atypeFactory, method)) {
// After the call to super.updateForMethodCall, only final fields are left in fieldValues (if
// the method called is side-effecting). For the LockPossiblyHeld hierarchy, even a final
// field might be locked or unlocked by a side-effecting method. So, final fields must be set
// to @LockPossiblyHeld, but the annotation in the GuardedBy hierarchy should not be changed.
for (FieldAccess field : new ArrayList<>(fieldValues.keySet())) {
CFValue newValue = changeLockAnnoToTop(field, fieldValues.get(field));
if (newValue != null) {
fieldValues.put(field, newValue);
} else {
fieldValues.remove(field);
}
}
// Local variables could also be unlocked via an alias
for (LocalVariable var : new ArrayList<>(localVariableValues.keySet())) {
CFValue newValue = changeLockAnnoToTop(var, localVariableValues.get(var));
if (newValue != null) {
localVariableValues.put(var, newValue);
}
}
if (thisValue != null) {
thisValue = changeLockAnnoToTop(null, thisValue);
}
}
}
boolean hasLockHeld(CFValue value) {
return AnnotationUtils.containsSame(value.getAnnotations(), atypeFactory.LOCKHELD);
}
boolean hasLockPossiblyHeld(CFValue value) {
return AnnotationUtils.containsSame(value.getAnnotations(), atypeFactory.LOCKPOSSIBLYHELD);
}
@Override
public void insertValue(
JavaExpression je, @Nullable CFValue value, boolean permitNondeterministic) {
if (!shouldInsert(je, value, permitNondeterministic)) {
return;
}
// Even with concurrent semantics enabled, a @LockHeld value must always be
// stored for fields and @Pure method calls. This is sound because:
// * Another thread can never release the lock on the current thread, and
// * Locks are assumed to be effectively final, hence another thread will not
// side effect the lock expression that has value @LockHeld.
if (hasLockHeld(value)) {
if (je instanceof FieldAccess) {
FieldAccess fieldAcc = (FieldAccess) je;
CFValue oldValue = fieldValues.get(fieldAcc);
CFValue newValue = value.mostSpecific(oldValue, null);
if (newValue != null) {
fieldValues.put(fieldAcc, newValue);
}
} else if (je instanceof MethodCall) {
MethodCall method = (MethodCall) je;
CFValue oldValue = methodValues.get(method);
CFValue newValue = value.mostSpecific(oldValue, null);
if (newValue != null) {
methodValues.put(method, newValue);
}
}
}
super.insertValue(je, value, permitNondeterministic);
}
}