blob: 2770d6ad8c5e6b58c6080674f0cb02d30dcf97fb [file] [log] [blame]
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);
}
}
}