use of org.checkerframework.checker.lock.LockAnnotatedTypeFactory.SideEffectAnnotation in project checker-framework by typetools.
the class LockVisitor method visitSynchronized.
/**
* When visiting a synchronized block, issue an error if the expression has a type that
* implements the java.util.concurrent.locks.Lock interface. This prevents explicit locks from
* being accidentally used as built-in (monitor) locks. This is important because the Lock
* Checker does not have a mechanism to separately keep track of the explicit lock and the
* monitor lock of an expression that implements the Lock interface (i.e. there is a @LockHeld
* annotation used in dataflow, but there are not distinct @MonitorLockHeld
* and @ExplicitLockHeld annotations). It is assumed that both kinds of locks will never be held
* for any expression that implements Lock.
*
* <p>Additionally, a synchronized block may not be present in a method that has a @LockingFree
* guarantee or stronger. An error is issued in this case.
*
* @param node the SynchronizedTree for the synchronized block being visited
*/
@Override
public Void visitSynchronized(SynchronizedTree node, Void p) {
ProcessingEnvironment processingEnvironment = checker.getProcessingEnvironment();
javax.lang.model.util.Types types = processingEnvironment.getTypeUtils();
// TODO: make a type declaration annotation for this rather than looking for Lock.class
// explicitly.
TypeMirror lockInterfaceTypeMirror = TypesUtils.typeFromClass(Lock.class, types, processingEnvironment.getElementUtils());
ExpressionTree synchronizedExpression = node.getExpression();
ensureExpressionIsEffectivelyFinal(synchronizedExpression);
TypeMirror expressionType = types.erasure(atypeFactory.getAnnotatedType(synchronizedExpression).getUnderlyingType());
if (types.isSubtype(expressionType, lockInterfaceTypeMirror)) {
checker.report(Result.failure("explicit.lock.synchronized"), node);
}
MethodTree enclosingMethod = TreeUtils.enclosingMethod(atypeFactory.getPath(node));
ExecutableElement methodElement = null;
if (enclosingMethod != null) {
methodElement = TreeUtils.elementFromDeclaration(enclosingMethod);
SideEffectAnnotation seaOfContainingMethod = atypeFactory.methodSideEffectAnnotation(methodElement, false);
if (!seaOfContainingMethod.isWeakerThan(SideEffectAnnotation.LOCKINGFREE)) {
checker.report(Result.failure("synchronized.block.in.lockingfree.method", seaOfContainingMethod), node);
}
}
return super.visitSynchronized(node, p);
}
use of org.checkerframework.checker.lock.LockAnnotatedTypeFactory.SideEffectAnnotation in project checker-framework by typetools.
the class LockVisitor method visitMethodInvocation.
/**
* When visiting a method invocation, issue an error if the side effect annotation on the called
* method causes the side effect guarantee of the enclosing method to be violated. For example,
* a method annotated with @ReleasesNoLocks may not call a method annotated
* with @MayReleaseLocks. Also check that matching @GuardSatisfied(index) on a method's formal
* receiver/parameters matches those in corresponding locations on the method call site.
*
* @param node the MethodInvocationTree of the method call being visited
*/
@Override
public Void visitMethodInvocation(MethodInvocationTree node, Void p) {
ExecutableElement methodElement = TreeUtils.elementFromUse(node);
SideEffectAnnotation seaOfInvokedMethod = atypeFactory.methodSideEffectAnnotation(methodElement, false);
MethodTree enclosingMethod = TreeUtils.enclosingMethod(atypeFactory.getPath(node));
ExecutableElement enclosingMethodElement = null;
if (enclosingMethod != null) {
enclosingMethodElement = TreeUtils.elementFromDeclaration(enclosingMethod);
}
if (enclosingMethodElement != null) {
SideEffectAnnotation seaOfContainingMethod = atypeFactory.methodSideEffectAnnotation(enclosingMethodElement, false);
if (seaOfInvokedMethod.isWeakerThan(seaOfContainingMethod)) {
checker.report(Result.failure("method.guarantee.violated", seaOfContainingMethod.getNameOfSideEffectAnnotation(), enclosingMethodElement.toString(), methodElement.toString(), seaOfInvokedMethod.getNameOfSideEffectAnnotation()), node);
}
}
if (methodElement != null) {
// Handle releasing of explicit locks. Verify that the lock expression is effectively
// final.
ExpressionTree recvTree = getReceiverTree(node);
ensureReceiverOfExplicitUnlockCallIsEffectivelyFinal(node, methodElement, recvTree);
// Handle acquiring of explicit locks. Verify that the lock expression is effectively
// final.
// If the method causes expression "this" or "#1" to be locked, verify that those
// expressions are effectively final. TODO: generalize to any expression. This is
// currently designed only to support methods in ReentrantLock and
// ReentrantReadWriteLock (which use the "this" expression), as well as Thread.holdsLock
// (which uses the "#1" expression).
AnnotationMirror ensuresLockHeldAnno = atypeFactory.getDeclAnnotation(methodElement, EnsuresLockHeld.class);
List<String> expressions = new ArrayList<>();
if (ensuresLockHeldAnno != null) {
expressions.addAll(AnnotationUtils.getElementValueArray(ensuresLockHeldAnno, "value", String.class, false));
}
AnnotationMirror ensuresLockHeldIfAnno = atypeFactory.getDeclAnnotation(methodElement, EnsuresLockHeldIf.class);
if (ensuresLockHeldIfAnno != null) {
expressions.addAll(AnnotationUtils.getElementValueArray(ensuresLockHeldIfAnno, "expression", String.class, false));
}
for (String expr : expressions) {
if (expr.equals("this")) {
// are also final. So nothing to be checked for them.
if (recvTree != null) {
ensureExpressionIsEffectivelyFinal(recvTree);
}
} else if (expr.equals("#1")) {
ExpressionTree firstParameter = node.getArguments().get(0);
if (firstParameter != null) {
ensureExpressionIsEffectivelyFinal(firstParameter);
}
}
}
}
// Check that matching @GuardSatisfied(index) on a method's formal receiver/parameters
// matches
// those in corresponding locations on the method call site.
Pair<AnnotatedExecutableType, List<AnnotatedTypeMirror>> mfuPair = atypeFactory.methodFromUse(node);
AnnotatedExecutableType invokedMethod = mfuPair.first;
List<AnnotatedTypeMirror> requiredArgs = AnnotatedTypes.expandVarArgs(atypeFactory, invokedMethod, node.getArguments());
// Index on @GuardSatisfied at each location. -1 when no @GuardSatisfied annotation was
// present.
// Note that @GuardSatisfied with no index is normally represented as having index -1.
// We would like to ignore a @GuardSatisfied with no index for these purposes, so if it is
// encountered we leave its index as -1.
// The first element of the array is reserved for the receiver.
int[] guardSatisfiedIndex = // + 1 for the receiver parameter type
new int[requiredArgs.size() + 1];
// Retrieve receiver types from method definition and method call
guardSatisfiedIndex[0] = -1;
AnnotatedTypeMirror methodDefinitionReceiver = null;
AnnotatedTypeMirror methodCallReceiver = null;
ExecutableElement invokedMethodElement = invokedMethod.getElement();
if (!ElementUtils.isStatic(invokedMethodElement) && invokedMethod.getElement().getKind() != ElementKind.CONSTRUCTOR) {
methodDefinitionReceiver = invokedMethod.getReceiverType();
if (methodDefinitionReceiver != null && methodDefinitionReceiver.hasAnnotation(checkerGuardSatisfiedClass)) {
guardSatisfiedIndex[0] = atypeFactory.getGuardSatisfiedIndex(methodDefinitionReceiver);
methodCallReceiver = atypeFactory.getReceiverType(node);
}
}
for (int i = 0; i < requiredArgs.size(); i++) {
guardSatisfiedIndex[i + 1] = -1;
AnnotatedTypeMirror arg = requiredArgs.get(i);
if (arg.hasAnnotation(checkerGuardSatisfiedClass)) {
guardSatisfiedIndex[i + 1] = atypeFactory.getGuardSatisfiedIndex(arg);
}
}
// Combine all of the actual parameters into one list of AnnotationMirrors
ArrayList<AnnotationMirror> passedArgAnnotations = new ArrayList<>(guardSatisfiedIndex.length);
passedArgAnnotations.add(methodCallReceiver == null ? null : methodCallReceiver.getAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN));
for (ExpressionTree tree : node.getArguments()) {
passedArgAnnotations.add(atypeFactory.getAnnotatedType(tree).getAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN));
}
for (int i = 0; i < guardSatisfiedIndex.length; i++) {
if (guardSatisfiedIndex[i] != -1) {
for (int j = i + 1; j < guardSatisfiedIndex.length; j++) {
if (guardSatisfiedIndex[i] == guardSatisfiedIndex[j]) {
// The @GuardedBy/@GuardSatisfied/@GuardedByUnknown/@GuardedByBottom
// annotations must be identical on the corresponding actual parameters.
AnnotationMirror arg1Anno = passedArgAnnotations.get(i);
AnnotationMirror arg2Anno = passedArgAnnotations.get(j);
if (arg1Anno != null && arg2Anno != null) {
boolean bothAreGSwithNoIndex = false;
if (AnnotationUtils.areSameByClass(arg1Anno, checkerGuardSatisfiedClass) && AnnotationUtils.areSameByClass(arg2Anno, checkerGuardSatisfiedClass)) {
if (atypeFactory.getGuardSatisfiedIndex(arg1Anno) == -1 && atypeFactory.getGuardSatisfiedIndex(arg2Anno) == -1) {
// Generally speaking, two @GuardSatisfied annotations with no
// index are incomparable.
// TODO: If they come from the same variable, they are
// comparable. Fix and add a test case.
bothAreGSwithNoIndex = true;
}
}
if (bothAreGSwithNoIndex || !(atypeFactory.getQualifierHierarchy().isSubtype(arg1Anno, arg2Anno) || atypeFactory.getQualifierHierarchy().isSubtype(arg2Anno, arg1Anno))) {
// TODO: allow these strings to be localized
String formalParam1 = null;
if (i == 0) {
formalParam1 = "The receiver type";
} else {
formalParam1 = "Parameter #" + // i, not i-1, so the index is 1-based
i;
}
String formalParam2 = // j, not j-1, so the index is 1-based
"parameter #" + j;
checker.report(Result.failure("guardsatisfied.parameters.must.match", formalParam1, formalParam2, invokedMethod.toString(), guardSatisfiedIndex[i], arg1Anno, arg2Anno), node);
}
}
}
}
}
}
return super.visitMethodInvocation(node, p);
}
use of org.checkerframework.checker.lock.LockAnnotatedTypeFactory.SideEffectAnnotation in project checker-framework by typetools.
the class LockVisitor method visitMethod.
/**
* Issues an error if a method (explicitly or implicitly) annotated with @MayReleaseLocks has a
* formal parameter or receiver (explicitly or implicitly) annotated with @GuardSatisfied. Also
* issues an error if a synchronized method has a @LockingFree, @SideEffectFree or @Pure
* annotation.
*
* @param node the MethodTree of the method definition to visit
*/
@Override
public Void visitMethod(MethodTree node, Void p) {
ExecutableElement methodElement = TreeUtils.elementFromDeclaration(node);
issueErrorIfMoreThanOneLockPreconditionMethodAnnotationPresent(methodElement, node);
SideEffectAnnotation sea = atypeFactory.methodSideEffectAnnotation(methodElement, true);
if (sea == SideEffectAnnotation.MAYRELEASELOCKS) {
boolean issueGSwithMRLWarning = false;
VariableTree receiver = node.getReceiverParameter();
if (receiver != null) {
if (atypeFactory.getAnnotatedType(receiver).hasAnnotation(checkerGuardSatisfiedClass)) {
issueGSwithMRLWarning = true;
}
}
if (!issueGSwithMRLWarning) {
// Skip loop if we already decided to issue the warning.
for (VariableTree vt : node.getParameters()) {
if (atypeFactory.getAnnotatedType(vt).hasAnnotation(checkerGuardSatisfiedClass)) {
issueGSwithMRLWarning = true;
break;
}
}
}
if (issueGSwithMRLWarning) {
checker.report(Result.failure("guardsatisfied.with.mayreleaselocks"), node);
}
}
// @GuardSatisfied without an index.
if (methodElement != null && methodElement.getKind() != ElementKind.CONSTRUCTOR) {
AnnotatedTypeMirror returnTypeATM = atypeFactory.getAnnotatedType(node).getReturnType();
if (returnTypeATM != null && returnTypeATM.hasAnnotation(GuardSatisfied.class)) {
int returnGuardSatisfiedIndex = atypeFactory.getGuardSatisfiedIndex(returnTypeATM);
if (returnGuardSatisfiedIndex == -1) {
checker.report(Result.failure("guardsatisfied.return.must.have.index"), node);
}
}
}
if (!sea.isWeakerThan(SideEffectAnnotation.LOCKINGFREE) && methodElement.getModifiers().contains(Modifier.SYNCHRONIZED)) {
checker.report(Result.failure("lockingfree.synchronized.method", sea), node);
}
return super.visitMethod(node, p);
}
use of org.checkerframework.checker.lock.LockAnnotatedTypeFactory.SideEffectAnnotation in project checker-framework by typetools.
the class LockVisitor method checkOverride.
/**
* Ensures that subclass methods are annotated with a stronger or equally strong side effect
* annotation than the parent class method.
*/
@Override
protected boolean checkOverride(MethodTree overriderTree, AnnotatedDeclaredType enclosingType, AnnotatedExecutableType overridden, AnnotatedDeclaredType overriddenType) {
boolean isValid = true;
SideEffectAnnotation seaOfOverriderMethod = atypeFactory.methodSideEffectAnnotation(TreeUtils.elementFromDeclaration(overriderTree), false);
SideEffectAnnotation seaOfOverridenMethod = atypeFactory.methodSideEffectAnnotation(overridden.getElement(), false);
if (seaOfOverriderMethod.isWeakerThan(seaOfOverridenMethod)) {
isValid = false;
reportFailure("override.sideeffect.invalid", overriderTree, enclosingType, overridden, overriddenType, null, null);
}
return super.checkOverride(overriderTree, enclosingType, overridden, overriddenType) && isValid;
}
Aggregations