checker.src.org.checkerframework.checker.guieffect.GuiEffectVisitor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of checker Show documentation
Show all versions of checker Show documentation
The Checker Framework enhances Java’s type system to
make it more powerful and useful. This lets software developers
detect and prevent errors in their Java programs.
The Checker Framework includes compiler plug-ins ("checkers")
that find bugs or verify their absence. It also permits you to
write your own compiler plug-ins.
package org.checkerframework.checker.guieffect;
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.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;
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 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;
/**
* Require that only UI code invokes code with the UI effect.
*/
public class GuiEffectVisitor extends BaseTypeVisitor {
protected final boolean debugSpew;
// effStack and currentMethods should always be the same size.
protected final Stack effStack;
protected final Stack currentMethods;
public GuiEffectVisitor(BaseTypeChecker checker) {
super(checker);
debugSpew = checker.getLintOption("debugSpew", false);
if (debugSpew) {
System.err.println("Running GuiEffectVisitor");
}
effStack = new Stack();
currentMethods = new Stack();
}
@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 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) ||
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 visitClass(ClassTree node, Void p) {
// 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));
Void ret = super.visitClass(node, p);
currentMethods.pop();
effStack.pop();
return ret;
}
}