package checkers.lock;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import com.sun.source.tree.*;
import checkers.basetype.BaseTypeVisitor;
import checkers.lock.quals.Holding;
import checkers.source.Result;
import checkers.types.AnnotatedTypeMirror;
import checkers.types.AnnotatedTypeMirror.AnnotatedDeclaredType;
import checkers.types.AnnotatedTypeMirror.AnnotatedExecutableType;
import checkers.util.TreeUtils;
//Disclaimer:
//This class is currently in its alpha form. For sample code on how to write
//checkers, please review other checkers for code samples.
/**
* A type-checking visitor for the Lock type system.
* This visitor reports errors ("unguarded.access") or warnings for violations
* for accessing a field or calling a method without holding their locks.
*/
public class LockVisitor extends BaseTypeVisitor<Void, Void> {
LockAnnotatedTypeFactory atypeFactory;
public LockVisitor(LockChecker checker, CompilationUnitTree root) {
super(checker, root);
this.atypeFactory = (LockAnnotatedTypeFactory)super.atypeFactory;
}
@Override
public Void visitIdentifier(IdentifierTree node, Void p) {
AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(node);
if (type.isAnnotated()) {
checker.report(Result.failure("unguarded.access", node, type), node);
}
return super.visitIdentifier(node, p);
}
@Override
public Void visitMemberSelect(MemberSelectTree node, Void p) {
AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(node);
if (type.isAnnotated()) {
checker.report(Result.failure("unguarded.access", node, type), node);
}
return super.visitMemberSelect(node, p);
}
private <T> List<T> append(List<T> lst, T o) {
if (o == null)
return lst;
List<T> newList = new ArrayList<T>(lst.size() + 1);
newList.addAll(lst);
newList.add(o);
return newList;
}
@Override
public Void visitSynchronized(SynchronizedTree node, Void p) {
List<String> prevLocks = atypeFactory.getHeldLock();
try {
List<String> locks = append(prevLocks, TreeUtils.skipParens(node.getExpression()).toString());
atypeFactory.setHeldLocks(locks);
return super.visitSynchronized(node, p);
} finally {
atypeFactory.setHeldLocks(prevLocks);
}
}
@Override
public Void visitMethod(MethodTree node, Void p) {
List<String> prevLocks = atypeFactory.getHeldLock();
List<String> locks = prevLocks;
try {
ExecutableElement method = TreeUtils.elementFromDeclaration(node);
if (method.getModifiers().contains(Modifier.SYNCHRONIZED)
|| method.getKind() == ElementKind.CONSTRUCTOR) {
if (method.getModifiers().contains(Modifier.STATIC)) {
String enclosingClass = method.getEnclosingElement().getSimpleName().toString();
locks = append(locks, enclosingClass + ".class");
} else {
locks = append(locks, "this");
}
}
List<String> methodLocks = methodHolding(method);
if (!methodLocks.isEmpty()) {
locks = new ArrayList<String>(locks);
locks.addAll(methodLocks);
}
atypeFactory.setHeldLocks(locks);
return super.visitMethod(node, p);
} finally {
atypeFactory.setHeldLocks(prevLocks);
}
}
private static String receiver(ExpressionTree methodSel) {
if (methodSel.getKind() == Tree.Kind.IDENTIFIER)
return "this";
else if (methodSel.getKind() == Tree.Kind.MEMBER_SELECT)
return ((MemberSelectTree)methodSel).getExpression().toString();
else
throw new IllegalArgumentException("Unknown tree type: " + methodSel);
}
@Override
public Void visitMethodInvocation(MethodInvocationTree node, Void p) {
// does it introduce new locks
ExecutableElement methodElt = TreeUtils.elementFromUse(node);
String lock = receiver(node.getMethodSelect());
if (methodElt.getSimpleName().contentEquals("lock")) {
List<String> locks = append(atypeFactory.getHeldLock(), lock);
atypeFactory.setHeldLocks(locks);
} else if (methodElt.getSimpleName().contentEquals("unlock")) {
List<String> locks = new ArrayList<String>(atypeFactory.getHeldLock());
locks.remove(lock);
atypeFactory.setHeldLocks(locks);
}
// does it require holding a lock
List<String> methodLocks = methodHolding(methodElt);
if (!methodLocks.isEmpty()
&& !atypeFactory.getHeldLock().containsAll(methodLocks)) {
checker.report(Result.failure("unguarded.invocation",
methodElt, methodLocks), node);
}
return super.visitMethodInvocation(node, p);
}
protected boolean checkOverride(MethodTree overriderTree,
AnnotatedDeclaredType enclosingType,
AnnotatedExecutableType overridden,
AnnotatedDeclaredType overriddenType,
Void p) {
List<String> overriderLocks = methodHolding(TreeUtils.elementFromDeclaration(overriderTree));
List<String> overridenLocks = methodHolding(overridden.getElement());
boolean isValid = overridenLocks.containsAll(overriderLocks);
if (!isValid) {
checker.report(Result.failure("override.holding.invalid",
TreeUtils.elementFromDeclaration(overriderTree),
enclosingType.getElement(), overridden.getElement(),
overriddenType.getElement(),
overriderLocks, overridenLocks), overriderTree);
}
return super.checkOverride(overriderTree, enclosingType, overridden, overriddenType, p) && isValid;
}
protected boolean checkMethodInvocability(AnnotatedExecutableType method,
MethodInvocationTree node) {
return true;
}
protected List<String> methodHolding(ExecutableElement element) {
Holding holding = element.getAnnotation(Holding.class);
net.jcip.annotations.GuardedBy guardedBy
= element.getAnnotation(net.jcip.annotations.GuardedBy.class);
if (holding == null && guardedBy == null)
return Collections.emptyList();
List<String> locks = new ArrayList<String>();
if (holding != null)
locks.addAll(Arrays.asList(holding.value()));
if (guardedBy != null)
locks.add(guardedBy.value());
return locks;
}
}