package org.checkerframework.checker.lock; /*>>> import org.checkerframework.checker.nullness.qual.Nullable; */ 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.dataflow.analysis.FlowExpressions; import org.checkerframework.dataflow.analysis.FlowExpressions.ArrayAccess; import org.checkerframework.dataflow.analysis.FlowExpressions.FieldAccess; import org.checkerframework.dataflow.analysis.FlowExpressions.LocalVariable; import org.checkerframework.dataflow.analysis.FlowExpressions.Receiver; import org.checkerframework.dataflow.cfg.CFGVisualizer; import org.checkerframework.dataflow.cfg.node.MethodInvocationNode; import org.checkerframework.framework.flow.CFAbstractStore; import org.checkerframework.framework.flow.CFValue; 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 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); 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; newStore.atypeFactory = this.atypeFactory; 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(FlowExpressions.Receiver r) { if (r.containsUnknown()) { // Expressions containing unknown expressions are not stored. return; } if (r instanceof FlowExpressions.LocalVariable) { FlowExpressions.LocalVariable localVar = (FlowExpressions.LocalVariable) r; CFValue current = localVariableValues.get(localVar); CFValue value = changeLockAnnoToTop(r, current); localVariableValues.put(localVar, value); } else if (r instanceof FlowExpressions.FieldAccess) { FlowExpressions.FieldAccess fieldAcc = (FlowExpressions.FieldAccess) r; CFValue current = fieldValues.get(fieldAcc); CFValue value = changeLockAnnoToTop(r, current); fieldValues.put(fieldAcc, value); } else if (r instanceof FlowExpressions.MethodCall) { FlowExpressions.MethodCall method = (FlowExpressions.MethodCall) r; CFValue current = methodValues.get(method); CFValue value = changeLockAnnoToTop(r, current); methodValues.put(method, value); } else if (r instanceof FlowExpressions.ArrayAccess) { FlowExpressions.ArrayAccess arrayAccess = (ArrayAccess) r; CFValue current = arrayValues.get(arrayAccess); CFValue value = changeLockAnnoToTop(r, current); arrayValues.put(arrayAccess, value); } else if (r instanceof FlowExpressions.ThisReference) { thisValue = changeLockAnnoToTop(r, thisValue); } else if (r instanceof FlowExpressions.ClassName) { FlowExpressions.ClassName className = (FlowExpressions.ClassName) r; CFValue current = classValues.get(className); CFValue value = changeLockAnnoToTop(r, current); 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(Receiver r, CFValue currentValue) { if (currentValue == null) { Set<AnnotationMirror> set = AnnotationUtils.createAnnotationSet(); set.add(atypeFactory.GUARDEDBYUNKNOWN); set.add(atypeFactory.LOCKPOSSIBLYHELD); return analysis.createAbstractValue(set, r.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; } /** {@inheritDoc} */ @Override public /*@Nullable*/ CFValue getValue(FlowExpressions.Receiver expr) { if (inConstructorOrInitializer) { // 'this' is automatically considered as being held in a constructor or initializer. // The class name, however, is not. if (expr instanceof FlowExpressions.ThisReference) { initializeThisValue(atypeFactory.LOCKHELD, expr.getType()); } else if (expr instanceof FlowExpressions.FieldAccess) { FlowExpressions.FieldAccess fieldAcc = (FlowExpressions.FieldAccess) expr; if (!fieldAcc.isStatic() && fieldAcc.getReceiver() instanceof FlowExpressions.ThisReference) { insertValue(fieldAcc.getReceiver(), atypeFactory.LOCKHELD); } } } return super.getValue(expr); } /** {@inheritDoc} */ @Override protected void internalVisualize(CFGVisualizer<CFValue, LockStore, ?> viz) { viz.visualizeStoreKeyVal("inConstructorOrInitializer", inConstructorOrInitializer); super.internalVisualize(viz); } @Override protected boolean isSideEffectFree( AnnotatedTypeFactory atypeFactory, ExecutableElement method) { LockAnnotatedTypeFactory lockAnnotatedTypeFactory = (LockAnnotatedTypeFactory) atypeFactory; return ((LockChecker) lockAnnotatedTypeFactory.getContext()) .hasOption("assumeSideEffectFree") || 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(); 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())) { fieldValues.put(field, changeLockAnnoToTop(field, fieldValues.get(field))); } // Local variables could also be unlocked via an alias for (LocalVariable var : new ArrayList<>(localVariableValues.keySet())) { CFValue newValue = changeLockAnnoToTop(var, localVariableValues.get(var)); localVariableValues.put(var, newValue); } } } 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(FlowExpressions.Receiver r, /*@Nullable*/ CFValue value) { if (value == null) { // No need to insert a null abstract value because it represents // top and top is also the default value. 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 (r instanceof FlowExpressions.FieldAccess) { FlowExpressions.FieldAccess fieldAcc = (FlowExpressions.FieldAccess) r; CFValue oldValue = fieldValues.get(fieldAcc); CFValue newValue = value.mostSpecific(oldValue, null); if (newValue != null) { fieldValues.put(fieldAcc, newValue); } } else if (r instanceof FlowExpressions.MethodCall) { FlowExpressions.MethodCall method = (FlowExpressions.MethodCall) r; CFValue oldValue = methodValues.get(method); CFValue newValue = value.mostSpecific(oldValue, null); if (newValue != null) { methodValues.put(method, newValue); } } } super.insertValue(r, value); } }