package org.checkerframework.checker.guieffect; import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; 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.PolyUIType; import org.checkerframework.checker.guieffect.qual.SafeEffect; import org.checkerframework.checker.guieffect.qual.SafeType; import org.checkerframework.checker.guieffect.qual.UI; import org.checkerframework.checker.guieffect.qual.UIEffect; import org.checkerframework.checker.guieffect.qual.UIPackage; import org.checkerframework.checker.guieffect.qual.UIType; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.source.Result; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator; import org.checkerframework.framework.type.treeannotator.TreeAnnotator; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TypesUtils; /** Annotated type factory for the GUI Effect Checker. */ public class GuiEffectTypeFactory extends BaseAnnotatedTypeFactory { protected final boolean debugSpew; public GuiEffectTypeFactory(BaseTypeChecker checker, boolean spew) { // use true to enable flow inference, false to disable it super(checker, false); debugSpew = spew; this.postInit(); } // Could move this to a public method on the checker class public ExecutableElement findJavaOverride(ExecutableElement overrider, TypeMirror parentType) { if (parentType.getKind() != TypeKind.NONE) { if (debugSpew) { System.err.println("Searching for overridden methods from " + parentType); } TypeElement overriderClass = (TypeElement) overrider.getEnclosingElement(); TypeElement elem = (TypeElement) ((DeclaredType) parentType).asElement(); if (debugSpew) { System.err.println("necessary TypeElements acquired: " + elem); } for (Element e : elem.getEnclosedElements()) { if (debugSpew) { System.err.println("Considering element " + e); } if (e.getKind() == ElementKind.METHOD || e.getKind() == ElementKind.CONSTRUCTOR) { ExecutableElement ex = (ExecutableElement) e; boolean overrides = elements.overrides(overrider, ex, overriderClass); if (overrides) { return ex; } } } if (debugSpew) { System.err.println("Done considering elements of " + parentType); } } return null; } public boolean isPolymorphicType(TypeElement cls) { assert (cls != null); return getDeclAnnotation(cls, PolyUIType.class) != null || fromElement(cls).hasAnnotation(PolyUI.class); } public boolean isUIType(TypeElement cls) { if (debugSpew) { System.err.println(" isUIType(" + cls + ")"); } boolean targetClassUIP = fromElement(cls).hasAnnotation(UI.class); AnnotationMirror targetClassUITypeP = getDeclAnnotation(cls, UIType.class); AnnotationMirror targetClassSafeTypeP = getDeclAnnotation(cls, SafeType.class); if (targetClassSafeTypeP != null) { return false; // explicitly marked not a UI type } boolean hasUITypeDirectly = (targetClassUIP || targetClassUITypeP != null); if (hasUITypeDirectly) { return true; } // Anon inner classes should not inherit the package annotation, since // they're so often used for closures to run async on background // threads. if (isAnonymousType(cls)) { return false; } // We don't check polymorphic annos so we can make a couple methods of // an @UIType polymorphic explicitly // AnnotationMirror targetClassPolyP = getDeclAnnotation(cls, PolyUI.class); // AnnotationMirror targetClassPolyTypeP = getDeclAnnotation(cls, PolyUIType.class); boolean targetClassSafeP = fromElement(cls).hasAnnotation(AlwaysSafe.class); if (targetClassSafeP) { return false; // explicitly annotated otherwise } // Look for the package Element packageP = ElementUtils.enclosingPackage(cls); if (packageP != null) { if (debugSpew) { System.err.println("Found package " + packageP); } if (getDeclAnnotation(packageP, UIPackage.class) != null) { if (debugSpew) { System.err.println("Package " + packageP + " is annotated @UIPackage"); } return true; } } return false; } // TODO: is there a framework method for this? private static boolean isAnonymousType(TypeElement elem) { return elem.getSimpleName().length() == 0; } /** * Calling context annotations * * <p>To make anon-inner-classes work, I need to climb the inheritance DAG, until I: * * <ul> * <li>find the class/interface that declares this calling method (an anon inner class is a * separate class that implements an interface) * <li>check whether *that* declaration specifies @UI on either the type or method * </ul> * * A method has the UI effect when: * * <ol> * <li>A method is UI if annotated @UIEffect * <li>A method is UI if the enclosing class is annotated @UI or @UIType and the method is not * annotated @AlwaysSafe * <li>A method is UI if the corresponding method in the super-class/interface is UI, and this * method is not annotated @AlwaysSafe, and this method resides in an anonymous inner * class (named classes still require a package/class/method annotation to make it UI, * only anon inner classes have this inheritance-by-default) * <ul> * <li>A method must be *annotated* UI if the method it overrides is *annotated* UI * <li>A method must be *annotated* UI if it overrides a UI method and the enclosing * class is not UI * </ul> * <li>It is an error if a method is UI but the same method in a super-type is not UI * <li>It is an error if two super-types specify the same method, where one type says it's UI * and one says it's not (it's possible to simply enforce the weaker (safe) effect, but * this seems more principled, it's easier --- backwards-compatible --- to change our * minds about this later) * </ol> */ public Effect getDeclaredEffect(ExecutableElement methodElt) { if (debugSpew) { System.err.println("begin mayHaveUIEffect(" + methodElt + ")"); } AnnotationMirror targetUIP = getDeclAnnotation(methodElt, UIEffect.class); AnnotationMirror targetSafeP = getDeclAnnotation(methodElt, SafeEffect.class); AnnotationMirror targetPolyP = getDeclAnnotation(methodElt, PolyUIEffect.class); TypeElement targetClassElt = (TypeElement) methodElt.getEnclosingElement(); if (debugSpew) { System.err.println("targetClassElt found"); } // Short-circuit if the method is explicitly annotated if (targetSafeP != null) { if (debugSpew) { System.err.println("Method marked @SafeEffect"); } return new Effect(SafeEffect.class); } else if (targetUIP != null) { if (debugSpew) { System.err.println("Method marked @UIEffect"); } return new Effect(UIEffect.class); } else if (targetPolyP != null) { if (debugSpew) { System.err.println("Method marked @PolyUIEffect"); } return new Effect(PolyUIEffect.class); } // The method is not explicitly annotated, so check class and package annotations, // and supertype effects if in an anonymous inner class if (isUIType(targetClassElt)) { // Already checked, no explicit @SafeEffect annotation return new Effect(UIEffect.class); } // Anonymous inner types should just get the effect of the parent by // default, rather than annotating every instance. Unless it's // implementing a polymorphic supertype, in which case we still want the // developer to be explicit. if (isAnonymousType(targetClassElt)) { boolean canInheritParentEffects = true; // Refine this for polymorphic parents DeclaredType directSuper = (DeclaredType) targetClassElt.getSuperclass(); TypeElement superElt = (TypeElement) directSuper.asElement(); // Anonymous subtypes of polymorphic classes other than object can't inherit if (getDeclAnnotation(superElt, PolyUIType.class) != null && !TypesUtils.isObject(directSuper)) { canInheritParentEffects = false; } else { for (TypeMirror ifaceM : targetClassElt.getInterfaces()) { DeclaredType iface = (DeclaredType) ifaceM; TypeElement ifaceElt = (TypeElement) iface.asElement(); if (getDeclAnnotation(ifaceElt, PolyUIType.class) != null) { canInheritParentEffects = false; } } } if (canInheritParentEffects) { Effect.EffectRange r = findInheritedEffectRange(targetClassElt, methodElt); return (r != null ? Effect.min(r.min, r.max) : new Effect(SafeEffect.class)); } } return new Effect(SafeEffect.class); } // Only the visitMethod call should pass true for warnings public Effect.EffectRange findInheritedEffectRange( TypeElement declaringType, ExecutableElement overridingMethod) { return findInheritedEffectRange(declaringType, overridingMethod, false, null); } public Effect.EffectRange findInheritedEffectRange( TypeElement declaringType, ExecutableElement overridingMethod, boolean issueConflictWarning, Tree errorNode) { assert (declaringType != null); ExecutableElement ui_override = null; ExecutableElement safe_override = null; ExecutableElement poly_override = null; // We must account for explicit annotation, type declaration annotations, and package annotations boolean isUI = (getDeclAnnotation(overridingMethod, UIEffect.class) != null || isUIType(declaringType)) && getDeclAnnotation(overridingMethod, SafeEffect.class) == null; boolean isPolyUI = getDeclAnnotation(overridingMethod, PolyUIEffect.class) != null; // TODO: We must account for @UI and @AlwaysSafe annotations for extends // and implements clauses, and do the proper substitution of @Poly effects and quals! // List<? extends TypeMirror> interfaces = declaringType.getInterfaces(); TypeMirror superclass = declaringType.getSuperclass(); while (superclass != null && superclass.getKind() != TypeKind.NONE) { ExecutableElement overrides = findJavaOverride(overridingMethod, superclass); if (overrides != null) { Effect eff = getDeclaredEffect(overrides); assert (eff != null); if (eff.isSafe()) { // found a safe override safe_override = overrides; if (isUI && issueConflictWarning) { checker.report( Result.failure( "override.effect.invalid", overridingMethod, declaringType, safe_override, superclass), errorNode); } if (isPolyUI && issueConflictWarning) { checker.report( Result.failure( "override.effect.invalid.polymorphic", overridingMethod, declaringType, safe_override, superclass), errorNode); } } else if (eff.isUI()) { // found a ui override ui_override = overrides; } else { assert (eff.isPoly()); poly_override = overrides; // TODO: Is this right? is the supertype covered by the // directSuperTypes() method all I need? Or should I be // using that utility method that returns a set of // annodecl-method pairs given a method that overrides stuff // if (isUI && issueConflictWarning) { // AnnotatedTypeMirror.AnnotatedDeclaredType supdecl = fromElement((TypeElement)(((DeclaredType)superclass).asElement()));//((DeclaredType)superclass).asElement()); // // Need to special case an anonymous class with @UI on the decl, because "new @UI Runnable {...}" parses as @UI on an anon class decl extending Runnable // boolean isAnonInstantiation = TypesUtils.isAnonymousType(ElementUtils.getType(declaringType)) && getDeclAnnotation(declaringType, UI.class) != null; // if (!isAnonInstantiation && !hasAnnotationByName(supdecl, UI.class)) { // checker.report(Result.failure("override.effect.invalid", "non-UI instantiation of "+supdecl), errorNode); // If uncommenting this, change the above line to match other calls of Result.failure("override.effect.invalid", ...) // } //} } } DeclaredType decl = (DeclaredType) superclass; superclass = ((TypeElement) decl.asElement()).getSuperclass(); } AnnotatedTypeMirror.AnnotatedDeclaredType annoDecl = fromElement(declaringType); for (AnnotatedTypeMirror.AnnotatedDeclaredType ty : annoDecl.directSuperTypes()) { ExecutableElement overrides = findJavaOverride(overridingMethod, ty.getUnderlyingType()); if (overrides != null) { Effect eff = getDeclaredEffect(overrides); if (eff.isSafe()) { // found a safe override safe_override = overrides; if (isUI && issueConflictWarning) { checker.report( Result.failure( "override.effect.invalid", overridingMethod, declaringType, safe_override, ty), errorNode); } if (isPolyUI && issueConflictWarning) { checker.report( Result.failure( "override.effect.invalid.polymorphic", overridingMethod, declaringType, safe_override, ty), errorNode); } } else if (eff.isUI()) { // found a ui override ui_override = overrides; } else { assert (eff.isPoly()); poly_override = overrides; if (isUI && issueConflictWarning) { AnnotatedTypeMirror.AnnotatedDeclaredType supdecl = ty; // Need to special case an anonymous class with @UI on // the decl, because "new @UI Runnable {...}" parses as // @UI on an anon class decl extending Runnable boolean isAnonInstantiation = isAnonymousType(declaringType) && fromElement(declaringType).hasAnnotation(UI.class); if (!isAnonInstantiation && !supdecl.hasAnnotation(UI.class)) { checker.report( Result.failure( "override.effect.invalid.nonui", overridingMethod, declaringType, poly_override, supdecl), errorNode); } } } } } // We don't need to issue warnings for inheriting from poly and a concrete effect. if (ui_override != null && safe_override != null && issueConflictWarning) { // There may be more than two parent methods, but for now it's // enough to know there are at least 2 in conflict checker.report( Result.warning( "override.effect.warning.inheritance", overridingMethod, declaringType, ui_override.toString(), ui_override.getEnclosingElement().asType().toString(), safe_override.toString(), safe_override.getEnclosingElement().asType().toString()), errorNode); } Effect min = (safe_override != null ? new Effect(SafeEffect.class) : (poly_override != null ? new Effect(PolyUIEffect.class) : (ui_override != null ? new Effect(UIEffect.class) : null))); Effect max = (ui_override != null ? new Effect(UIEffect.class) : (poly_override != null ? new Effect(PolyUIEffect.class) : (safe_override != null ? new Effect(SafeEffect.class) : null))); if (debugSpew) { System.err.println( "Found " + declaringType + "." + overridingMethod + " to have inheritance pair (" + min + "," + max + ")"); } if (min == null && max == null) { return null; } else { return new Effect.EffectRange(min, max); } } @Override protected TreeAnnotator createTreeAnnotator() { return new ListTreeAnnotator(super.createTreeAnnotator(), new GuiEffectTreeAnnotator()); } /** A class for adding annotations based on tree. */ private class GuiEffectTreeAnnotator extends TreeAnnotator { GuiEffectTreeAnnotator() { super(GuiEffectTypeFactory.this); } public boolean hasExplicitUIEffect(ExecutableElement methElt) { return GuiEffectTypeFactory.this.getDeclAnnotation(methElt, UIEffect.class) != null; } public boolean hasExplicitSafeEffect(ExecutableElement methElt) { return GuiEffectTypeFactory.this.getDeclAnnotation(methElt, SafeEffect.class) != null; } public boolean hasExplicitPolyUIEffect(ExecutableElement methElt) { return GuiEffectTypeFactory.this.getDeclAnnotation(methElt, PolyUIEffect.class) != null; } public boolean hasExplicitEffect(ExecutableElement methElt) { return hasExplicitUIEffect(methElt) || hasExplicitSafeEffect(methElt) || hasExplicitPolyUIEffect(methElt); } @Override public Void visitMethod(MethodTree node, AnnotatedTypeMirror type) { AnnotatedTypeMirror.AnnotatedExecutableType methType = (AnnotatedTypeMirror.AnnotatedExecutableType) type; Effect e = getDeclaredEffect(methType.getElement()); TypeElement cls = (TypeElement) methType.getElement().getEnclosingElement(); // STEP 1: Get the method effect annotation if (!hasExplicitEffect(methType.getElement())) { // TODO: This line does nothing! // AnnotatedTypeMirror.addAnnotation silently ignores non-qualifier annotations! // We should be digging up the /declaration/ of the method, and annotating that. methType.addAnnotation(e.getAnnot()); } // STEP 2: Fix up the method receiver annotation AnnotatedTypeMirror.AnnotatedDeclaredType receiverType = methType.getReceiverType(); if (receiverType != null && !receiverType.isAnnotatedInHierarchy( AnnotationUtils.fromClass(elements, UI.class))) { receiverType.addAnnotation( isPolymorphicType(cls) ? PolyUI.class : fromElement(cls).hasAnnotation(UI.class) ? UI.class : AlwaysSafe.class); } return super.visitMethod(node, type); } } }