package org.checkerframework.checker.guieffect;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import java.util.Collections;
import java.util.Set;
import java.util.Stack;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import org.checkerframework.checker.guieffect.qual.AlwaysSafe;
import org.checkerframework.checker.guieffect.qual.PolyUI;
import org.checkerframework.checker.guieffect.qual.PolyUIEffect;
import org.checkerframework.checker.guieffect.qual.SafeEffect;
import org.checkerframework.checker.guieffect.qual.UI;
import org.checkerframework.checker.guieffect.qual.UIEffect;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.common.basetype.BaseTypeVisitor;
import org.checkerframework.framework.qual.PolyAll;
import org.checkerframework.framework.source.Result;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.TreeUtils;
/** Require that only UI code invokes code with the UI effect. */
public class GuiEffectVisitor extends BaseTypeVisitor<GuiEffectTypeFactory> {
protected final boolean debugSpew;
// effStack and currentMethods should always be the same size.
protected final Stack<Effect> effStack;
protected final Stack<MethodTree> currentMethods;
public GuiEffectVisitor(BaseTypeChecker checker) {
super(checker);
debugSpew = checker.getLintOption("debugSpew", false);
if (debugSpew) {
System.err.println("Running GuiEffectVisitor");
}
effStack = new Stack<Effect>();
currentMethods = new Stack<MethodTree>();
}
@Override
protected GuiEffectTypeFactory createTypeFactory() {
return new GuiEffectTypeFactory(checker, debugSpew);
}
// The issue is that the receiver implicitly receives an @AlwaysSafe anno, so calls on @UI
// references fail because the framework doesn't implicitly upcast the receiver (which in
// general wouldn't be sound).
// TODO: Fix method receiver defaults: method-polymorphic for any polymorphic method, UI
// for any UI instantiations, safe otherwise
@Override
protected void checkMethodInvocability(
AnnotatedExecutableType method, MethodInvocationTree node) {
// The inherited version of this complains about invoking methods of @UI instantiations of
// classes, which by default are annotated @AlwaysSafe, which for data type qualifiers is
// reasonable, but it not what we want, since we want .
// TODO: Undo this hack!
}
@Override
protected boolean checkOverride(
MethodTree overriderTree,
AnnotatedTypeMirror.AnnotatedDeclaredType enclosingType,
AnnotatedTypeMirror.AnnotatedExecutableType overridden,
AnnotatedTypeMirror.AnnotatedDeclaredType overriddenType,
Void p) {
// Method override validity is checked manually by the type factory during visitation
return true;
}
@Override
protected Set<? extends AnnotationMirror> getExceptionParameterLowerBoundAnnotations() {
return Collections.singleton(AnnotationUtils.fromClass(elements, AlwaysSafe.class));
}
@Override
public boolean isValidUse(
AnnotatedTypeMirror.AnnotatedDeclaredType declarationType,
AnnotatedTypeMirror.AnnotatedDeclaredType useType,
Tree tree) {
boolean ret =
useType.hasAnnotation(AlwaysSafe.class)
|| useType.hasAnnotation(PolyAll.class)
|| useType.hasAnnotation(PolyUI.class)
|| atypeFactory.isPolymorphicType(
(TypeElement) declarationType.getUnderlyingType().asElement())
|| (useType.hasAnnotation(UI.class)
&& declarationType.hasAnnotation(UI.class));
if (debugSpew && !ret) {
System.err.println("use: " + useType);
System.err.println("use safe: " + useType.hasAnnotation(AlwaysSafe.class));
System.err.println("use poly: " + useType.hasAnnotation(PolyUI.class));
System.err.println("use ui: " + useType.hasAnnotation(UI.class));
System.err.println(
"declaration safe: " + declarationType.hasAnnotation(AlwaysSafe.class));
System.err.println(
"declaration poly: "
+ atypeFactory.isPolymorphicType(
(TypeElement) declarationType.getUnderlyingType().asElement()));
System.err.println("declaration ui: " + declarationType.hasAnnotation(UI.class));
System.err.println("declaration: " + declarationType);
}
return ret;
}
// Check that the invoked effect is <= permitted effect (effStack.peek())
@Override
public Void visitMethodInvocation(MethodInvocationTree node, Void p) {
if (debugSpew) {
System.err.println("For invocation " + node + " in " + currentMethods.peek().getName());
}
// Target method annotations
ExecutableElement methodElt = TreeUtils.elementFromUse(node);
if (debugSpew) {
System.err.println("methodElt found");
}
MethodTree callerTree = TreeUtils.enclosingMethod(getCurrentPath());
if (callerTree == null) {
// Static initializer; let's assume this is safe to have the UI effect
if (debugSpew) {
System.err.println("No enclosing method: likely static initializer");
}
return super.visitMethodInvocation(node, p);
}
if (debugSpew) {
System.err.println("callerTree found");
}
ExecutableElement callerElt = TreeUtils.elementFromDeclaration(callerTree);
if (debugSpew) {
System.err.println("callerElt found");
}
Effect targetEffect = atypeFactory.getDeclaredEffect(methodElt);
// System.err.println("Dispatching method "+node+"on "+node.getMethodSelect());
if (targetEffect.isPoly()) {
AnnotatedTypeMirror srcType = null;
assert (node.getMethodSelect().getKind() == Tree.Kind.IDENTIFIER
|| node.getMethodSelect().getKind() == Tree.Kind.MEMBER_SELECT);
if (node.getMethodSelect().getKind() == Tree.Kind.MEMBER_SELECT) {
ExpressionTree src = ((MemberSelectTree) node.getMethodSelect()).getExpression();
srcType = atypeFactory.getAnnotatedType(src);
} else {
// Tree.Kind.IDENTIFIER, e.g. a direct call like "super()"
srcType = visitorState.getMethodReceiver();
}
// Instantiate type-polymorphic effects
if (srcType.hasAnnotation(AlwaysSafe.class)) {
targetEffect = new Effect(SafeEffect.class);
} else if (srcType.hasAnnotation(UI.class)) {
targetEffect = new Effect(UIEffect.class);
}
// Poly substitution would be a noop.
}
Effect callerEffect = atypeFactory.getDeclaredEffect(callerElt);
// Field initializers inside anonymous inner classes show up with a null current-method ---
// the traversal goes straight from the class to the initializer.
assert (currentMethods.peek() == null || callerEffect.equals(effStack.peek()));
if (!Effect.LE(targetEffect, callerEffect)) {
checker.report(Result.failure("call.invalid.ui", targetEffect, callerEffect), node);
if (debugSpew) {
System.err.println("Issuing error for node: " + node);
}
}
if (debugSpew) {
System.err.println(
"Successfully finished main non-recursive checkinv of invocation " + node);
}
return super.visitMethodInvocation(node, p);
}
@Override
public Void visitMethod(MethodTree node, Void p) {
// TODO: If the type we're in is a polymorphic (over effect qualifiers) type, the receiver must be @PolyUI.
// Otherwise a "non-polymorphic" method of a polymorphic type could be called on a UI instance, which then
// gets a Safe reference to itself (unsound!) that it can then pass off elsewhere (dangerous!). So all
// receivers in methods of a @PolyUIType must be @PolyUI.
// TODO: What do we do then about classes that inherit from a concrete instantiation? If it subclasses a Safe
// instantiation, all is well. If it subclasses a UI instantiation, then the receivers should probably
// be @UI in both new and override methods, so calls to polymorphic methods of the parent class will work
// correctly. In which case for proving anything, the qualifier on sublasses of UI instantiations would
// always have to be @UI... Need to write down |- t for this system! And the judgments for method overrides
// and inheritance! Those are actually the hardest part of the system.
ExecutableElement methElt = TreeUtils.elementFromDeclaration(node);
if (debugSpew) {
System.err.println("\nVisiting method " + methElt);
}
// Check for conflicting (multiple) annotations
assert (methElt != null);
// TypeMirror scratch = methElt.getReturnType();
AnnotationMirror targetUIP = atypeFactory.getDeclAnnotation(methElt, UIEffect.class);
AnnotationMirror targetSafeP = atypeFactory.getDeclAnnotation(methElt, SafeEffect.class);
AnnotationMirror targetPolyP = atypeFactory.getDeclAnnotation(methElt, PolyUIEffect.class);
TypeElement targetClassElt = (TypeElement) methElt.getEnclosingElement();
if ((targetUIP != null && (targetSafeP != null || targetPolyP != null))
|| (targetSafeP != null && targetPolyP != null)) {
checker.report(Result.failure("annotations.conflicts"), node);
}
if (targetPolyP != null && !atypeFactory.isPolymorphicType(targetClassElt)) {
checker.report(Result.failure("polymorphism.invalid"), node);
}
if (targetUIP != null && atypeFactory.isUIType(targetClassElt)) {
checker.report(Result.warning("effects.redundant.uitype"), node);
}
// TODO: Report an error for polymorphic method bodies??? Until we fix the receiver defaults, it won't really be correct
@SuppressWarnings("unused") // call has side-effects
Effect.EffectRange range =
atypeFactory.findInheritedEffectRange(
((TypeElement) methElt.getEnclosingElement()), methElt, true, node);
if (targetUIP == null && targetSafeP == null && targetPolyP == null) {
// implicitly annotate this method with the LUB of the effects of the methods it overrides
// atypeFactory.fromElement(methElt).addAnnotation(range != null ? range.min.getAnnot() : (isUIType(((TypeElement)methElt.getEnclosingElement())) ? UI.class : AlwaysSafe.class));
// TODO: This line does nothing! AnnotatedTypeMirror.addAnnotation
// silently ignores non-qualifier annotations!
// System.err.println("ERROR: TREE ANNOTATOR SHOULD HAVE ADDED EXPLICIT ANNOTATION! ("+node.getName()+")");
atypeFactory
.fromElement(methElt)
.addAnnotation(atypeFactory.getDeclaredEffect(methElt).getAnnot());
}
// We hang onto the current method here for ease. We back up the old
// current method because this code is reentrant when we traverse methods of an inner class
currentMethods.push(node);
// effStack.push(targetSafeP != null ? new Effect(AlwaysSafe.class) :
// (targetPolyP != null ? new Effect(PolyUI.class) :
// (targetUIP != null ? new Effect(UI.class) :
// (range != null ? range.min : (isUIType(((TypeElement)methElt.getEnclosingElement())) ? new Effect(UI.class) : new Effect(AlwaysSafe.class))))));
effStack.push(atypeFactory.getDeclaredEffect(methElt));
if (debugSpew) {
System.err.println(
"Pushing " + effStack.peek() + " onto the stack when checking " + methElt);
}
Void ret = super.visitMethod(node, p);
currentMethods.pop();
effStack.pop();
return ret;
}
@Override
public Void visitMemberSelect(MemberSelectTree node, Void p) {
//TODO: Same effect checks as for methods
return super.visitMemberSelect(node, p);
}
@Override
public void processClassTree(ClassTree node) {
// TODO: Check constraints on this class decl vs. parent class decl., and interfaces
// TODO: This has to wait for now: maybe this will be easier with the isValidUse on the TypeFactory
// AnnotatedTypeMirror.AnnotatedDeclaredType atype = atypeFactory.fromClass(node);
// Push a null method and UI effect onto the stack for static field initialization
// TODO: Figure out if this is safe! For static data, almost certainly,
// but for statically initialized instance fields, I'm assuming those
// are implicitly moved into each constructor, which must then be @UI
currentMethods.push(null);
effStack.push(new Effect(UIEffect.class));
super.processClassTree(node);
currentMethods.pop();
effStack.pop();
}
}