-
Notifications
You must be signed in to change notification settings - Fork 348
/
GuiEffectTypeFactory.java
649 lines (594 loc) · 26.3 KB
/
GuiEffectTypeFactory.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
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.ElementUtils;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypeSystemError;
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 TypeSystemError("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);
}
}
}