| package org.checkerframework.checker.guieffect; |
| |
| import com.sun.source.tree.ConditionalExpressionTree; |
| import com.sun.source.tree.ExpressionTree; |
| import com.sun.source.tree.LambdaExpressionTree; |
| import com.sun.source.tree.MemberSelectTree; |
| import com.sun.source.tree.MethodInvocationTree; |
| import com.sun.source.tree.MethodTree; |
| import com.sun.source.tree.ParenthesizedTree; |
| import com.sun.source.tree.Tree; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| 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.type.AnnotatedTypeMirror; |
| import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator; |
| import org.checkerframework.framework.type.treeannotator.TreeAnnotator; |
| import org.checkerframework.framework.util.AnnotatedTypes; |
| import org.checkerframework.javacutil.AnnotationBuilder; |
| import org.checkerframework.javacutil.BugInCF; |
| import org.checkerframework.javacutil.ElementUtils; |
| import org.checkerframework.javacutil.TreeUtils; |
| import org.checkerframework.javacutil.TypesUtils; |
| |
| /** Annotated type factory for the GUI Effect Checker. */ |
| public class GuiEffectTypeFactory extends BaseAnnotatedTypeFactory { |
| |
| protected final boolean debugSpew; |
| |
| /** |
| * Keeps track of all lambda expressions with inferred UIEffect. |
| * |
| * <p>{@link #constrainLambdaToUI(LambdaExpressionTree) constrainLambdaToUI} adds lambda |
| * expressions to this set, and is called from GuiEffectVisitor whenever a lambda expression calls |
| * a @UIEffect method. Afterwards {@link |
| * #getInferedEffectForLambdaExpression(LambdaExpressionTree) getInferedEffectForLambdaExpression} |
| * uses this set and the type annotations of the functional interface of the lambda to figure out |
| * if it can affect the UI or not. |
| */ |
| protected final Set<LambdaExpressionTree> uiLambdas = new HashSet<>(); |
| |
| /** |
| * Keeps track of all anonymous inner classes with inferred UIEffect. |
| * |
| * <p>{@link #constrainAnonymousClassToUI(TypeElement) constrainAnonymousClassToUI} adds anonymous |
| * inner classes to this set, and is called from GuiEffectVisitor whenever an anonymous inner |
| * class calls a @UIEffect method. Afterwards {@link #isUIType(TypeElement) isUIType} and {@link |
| * #getAnnotatedType(Tree) getAnnotatedType} will treat this inner class as if it had been |
| * annotated with @UI. |
| */ |
| protected final Set<TypeElement> uiAnonClasses = new HashSet<>(); |
| |
| 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)) { |
| // However, we need to look into Anonymous class effect inference |
| if (uiAnonClasses.contains(cls)) { |
| return true; |
| } |
| 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(); |
| boolean hasMainThreadAnnot = |
| getDeclAnnotations(methodElt).toString().contains("@android.support.annotation.MainThread"); |
| |
| 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 || hasMainThreadAnnot) { |
| 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); |
| } |
| |
| /** |
| * Get the effect of a method call at its callsite, acknowledging polymorphic instantiation using |
| * type use annotations. |
| * |
| * @param node the method invocation as an AST node |
| * @param callerReceiver the type of the receiver object if available. Used to resolve direct |
| * calls like "super()" |
| * @param methodElt the element of the callee method |
| * @return the computed effect (SafeEffect or UIEffect) for the method call |
| */ |
| public Effect getComputedEffectAtCallsite( |
| MethodInvocationTree node, |
| AnnotatedTypeMirror.AnnotatedDeclaredType callerReceiver, |
| ExecutableElement methodElt) { |
| Effect targetEffect = getDeclaredEffect(methodElt); |
| if (targetEffect.isPoly()) { |
| AnnotatedTypeMirror srcType = null; |
| if (node.getMethodSelect().getKind() == Tree.Kind.MEMBER_SELECT) { |
| ExpressionTree src = ((MemberSelectTree) node.getMethodSelect()).getExpression(); |
| srcType = getAnnotatedType(src); |
| } else if (node.getMethodSelect().getKind() == Tree.Kind.IDENTIFIER) { |
| // Tree.Kind.IDENTIFIER, e.g. a direct call like "super()" |
| if (callerReceiver == null) { |
| // Not enought information provided to instantiate this type-polymorphic effects |
| return targetEffect; |
| } |
| srcType = callerReceiver; |
| } else { |
| throw new BugInCF("Unexpected getMethodSelect() kind at callsite " + node); |
| } |
| |
| // 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. |
| } |
| return targetEffect; |
| } |
| |
| /** |
| * Get the inferred effect of a lambda expression based on the type annotations of its functional |
| * interface and the effects of the calls in its body. |
| * |
| * <p>This relies on GuiEffectVisitor to perform the actual inference step and mark lambdas |
| * with @PolyUIEffect functional interfaces as being explicitly UI-affecting using the {@link |
| * #constrainLambdaToUI(LambdaExpressionTree) constrainLambdaToUI} method. |
| * |
| * @param lambdaTree a lambda expression's AST node |
| * @return the inferred effect of the lambda |
| */ |
| public Effect getInferedEffectForLambdaExpression(LambdaExpressionTree lambdaTree) { |
| // @UI type if annotated on the lambda expression explicitly |
| if (uiLambdas.contains(lambdaTree)) { |
| return new Effect(UIEffect.class); |
| } |
| ExecutableElement functionalInterfaceMethodElt = |
| (ExecutableElement) TreeUtils.findFunction(lambdaTree, checker.getProcessingEnvironment()); |
| if (debugSpew) { |
| System.err.println("functionalInterfaceMethodElt found for lambda"); |
| } |
| return getDeclaredEffect(functionalInterfaceMethodElt); |
| } |
| |
| /** |
| * Test if this tree corresponds to a lambda expression or new class marked as UI affecting by |
| * either {@link #constrainLambdaToUI(LambdaExpressionTree) constrainLambdaToUI}} or {@link |
| * #constrainAnonymousClassToUI(TypeElement)}. Only explicit markings due to inference are |
| * considered here, for the properly computed type of the expression, use {@link |
| * #getAnnotatedType(Tree)} instead. |
| * |
| * @param tree the tree to check |
| * @return whether it is a lambda expression or new class marked as UI by inference |
| */ |
| public boolean isDirectlyMarkedUIThroughInference(Tree tree) { |
| if (tree.getKind() == Tree.Kind.LAMBDA_EXPRESSION) { |
| return uiLambdas.contains((LambdaExpressionTree) tree); |
| } else if (tree.getKind() == Tree.Kind.NEW_CLASS) { |
| AnnotatedTypeMirror typeMirror = super.getAnnotatedType(tree); |
| if (typeMirror.getKind() == TypeKind.DECLARED) { |
| return uiAnonClasses.contains(((DeclaredType) typeMirror.getUnderlyingType()).asElement()); |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public AnnotatedTypeMirror getAnnotatedType(Tree tree) { |
| AnnotatedTypeMirror typeMirror = super.getAnnotatedType(tree); |
| if (typeMirror.hasAnnotation(UI.class)) { |
| return typeMirror; |
| } |
| // Check if this an @UI anonymous class or lambda due to inference, or an expression |
| // containing such class/lambda |
| if (isDirectlyMarkedUIThroughInference(tree)) { |
| typeMirror.replaceAnnotation(AnnotationBuilder.fromClass(elements, UI.class)); |
| } else if (tree.getKind() == Tree.Kind.PARENTHESIZED) { |
| ParenthesizedTree parenthesizedTree = (ParenthesizedTree) tree; |
| return this.getAnnotatedType(parenthesizedTree.getExpression()); |
| } else if (tree.getKind() == Tree.Kind.CONDITIONAL_EXPRESSION) { |
| ConditionalExpressionTree cet = (ConditionalExpressionTree) tree; |
| boolean isTrueOperandUI = |
| (cet.getTrueExpression() != null |
| && this.getAnnotatedType(cet.getTrueExpression()).hasAnnotation(UI.class)); |
| boolean isFalseOperandUI = |
| (cet.getFalseExpression() != null |
| && this.getAnnotatedType(cet.getFalseExpression()).hasAnnotation(UI.class)); |
| if (isTrueOperandUI || isFalseOperandUI) { |
| typeMirror.replaceAnnotation(AnnotationBuilder.fromClass(elements, UI.class)); |
| } |
| } |
| // TODO: Do we need to support other expression here? |
| // (i.e. are there any other operators that take new or lambda expressions as operands) |
| return typeMirror; |
| } |
| |
| // Only the visitMethod call should pass true for warnings |
| public Effect.EffectRange findInheritedEffectRange( |
| TypeElement declaringType, ExecutableElement overridingMethod) { |
| return findInheritedEffectRange(declaringType, overridingMethod, false, null); |
| } |
| |
| /** |
| * Find the greatest and least effects of methods the specified definition overrides. This method |
| * is used for two reasons: |
| * |
| * <p>1. {@link GuiEffectVisitor#visitMethod(MethodTree,Void) GuiEffectVisitor.visitMethod} calls |
| * this to perform an effect override check (that a method's effect is less than or equal to the |
| * effect of any method it overrides). This use passes {@code true} for the {@code |
| * issueConflictWarning} in order to trigger warning messages. |
| * |
| * <p>2. {@link #getDeclaredEffect(ExecutableElement) getDeclaredEffect} in this class uses this |
| * to infer the default effect of methods in anonymous inner classes. This use passes {@code |
| * false} for {@code issueConflictWarning}, because it only needs the return value. |
| * |
| * @param declaringType the type declaring the override |
| * @param overridingMethod the method override itself |
| * @param issueConflictWarning whether or not to issue warnings |
| * @param errorNode the method declaration node; used for reporting errors |
| * @return the min and max inherited effects |
| */ |
| public Effect.EffectRange findInheritedEffectRange( |
| TypeElement declaringType, |
| ExecutableElement overridingMethod, |
| boolean issueConflictWarning, |
| Tree errorNode) { |
| assert (declaringType != null); |
| ExecutableElement uiOverriden = null; |
| ExecutableElement safeOverriden = null; |
| ExecutableElement polyOverriden = 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; |
| |
| // Check for invalid overrides. |
| // AnnotatedTypes.overriddenMethods retrieves all transitive definitions overridden by this |
| // declaration. |
| Map<AnnotatedTypeMirror.AnnotatedDeclaredType, ExecutableElement> overriddenMethods = |
| AnnotatedTypes.overriddenMethods(elements, this, overridingMethod); |
| |
| for (Map.Entry<AnnotatedTypeMirror.AnnotatedDeclaredType, ExecutableElement> pair : |
| overriddenMethods.entrySet()) { |
| AnnotatedTypeMirror.AnnotatedDeclaredType overriddenType = pair.getKey(); |
| AnnotatedTypeMirror.AnnotatedExecutableType overriddenMethod = |
| AnnotatedTypes.asMemberOf(types, this, overriddenType, pair.getValue()); |
| ExecutableElement overriddenMethodElt = pair.getValue(); |
| if (debugSpew) |
| System.err.println( |
| "Found " |
| + declaringType |
| + "::" |
| + overridingMethod |
| + " overrides " |
| + overriddenType |
| + "::" |
| + overriddenMethod); |
| Effect eff = getDeclaredEffect(overriddenMethodElt); |
| if (eff.isSafe()) { |
| safeOverriden = overriddenMethodElt; |
| if (isUI) { |
| checker.reportError( |
| errorNode, |
| "override.effect", |
| declaringType, |
| overridingMethod, |
| overriddenType, |
| safeOverriden); |
| } else if (isPolyUI) { |
| checker.reportError( |
| errorNode, |
| "override.effect.polymorphic", |
| declaringType, |
| overridingMethod, |
| overriddenType, |
| safeOverriden); |
| } |
| } else if (eff.isUI()) { |
| uiOverriden = overriddenMethodElt; |
| } else { |
| assert eff.isPoly(); |
| polyOverriden = overriddenMethodElt; |
| if (isUI) { |
| // 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) |
| || uiAnonClasses.contains(declaringType)); |
| if (!isAnonInstantiation && !overriddenType.hasAnnotation(UI.class)) { |
| checker.reportError( |
| errorNode, |
| "override.effect.nonui", |
| declaringType, |
| overridingMethod, |
| overriddenType, |
| polyOverriden); |
| } |
| } |
| } |
| } |
| |
| // We don't need to issue warnings for overriding both poly and a concrete effect. |
| if (uiOverriden != null && safeOverriden != 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.reportWarning( |
| errorNode, |
| "override.effect.warning.inheritance", |
| declaringType, |
| overridingMethod, |
| uiOverriden.getEnclosingElement().asType(), |
| uiOverriden, |
| safeOverriden.getEnclosingElement().asType(), |
| safeOverriden); |
| } |
| |
| Effect min = |
| (safeOverriden != null |
| ? new Effect(SafeEffect.class) |
| : (polyOverriden != null |
| ? new Effect(PolyUIEffect.class) |
| : (uiOverriden != null ? new Effect(UIEffect.class) : null))); |
| Effect max = |
| (uiOverriden != null |
| ? new Effect(UIEffect.class) |
| : (polyOverriden != null |
| ? new Effect(PolyUIEffect.class) |
| : (safeOverriden != 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 Set<? extends AnnotationMirror> getDefaultTypeDeclarationBounds() { |
| return qualHierarchy.getBottomAnnotations(); |
| } |
| |
| @Override |
| protected TreeAnnotator createTreeAnnotator() { |
| return new ListTreeAnnotator(super.createTreeAnnotator(), new GuiEffectTreeAnnotator()); |
| } |
| |
| /** |
| * Force the given lambda expression to have UIEffect. |
| * |
| * <p>Used by GuiEffectVisitor to mark as UIEffect all lambdas that perform UIEffect calls inside |
| * their bodies. |
| * |
| * @param lambdaExpressionTree a lambda expression's AST node |
| */ |
| public void constrainLambdaToUI(LambdaExpressionTree lambdaExpressionTree) { |
| uiLambdas.add(lambdaExpressionTree); |
| } |
| |
| /** |
| * Force the given anonymous inner class to be an @UI instantiation of its base class. |
| * |
| * <p>Used by GuiEffectVisitor to mark as @UI all anonymous inner classes which: inherit from a |
| * PolyUIType annotated superclass, override a PolyUIEffect method from said superclass, and |
| * perform UIEffect calls inside the body of this method. |
| * |
| * @param classElt the TypeElement corresponding to the anonymous inner class to mark as an @UI |
| * instantiation of an UI-polymorphic superclass. |
| */ |
| public void constrainAnonymousClassToUI(TypeElement classElt) { |
| assert TypesUtils.isAnonymous(classElt.asType()); |
| uiAnonClasses.add(classElt); |
| } |
| |
| /** 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( |
| AnnotationBuilder.fromClass(elements, UI.class))) { |
| receiverType.addAnnotation( |
| isPolymorphicType(cls) |
| ? PolyUI.class |
| : fromElement(cls).hasAnnotation(UI.class) ? UI.class : AlwaysSafe.class); |
| } |
| return super.visitMethod(node, type); |
| } |
| } |
| } |