33
33
import static com .uber .nullaway .ErrorBuilder .errMsgForInitializer ;
34
34
import static com .uber .nullaway .NullabilityUtil .castToNonNull ;
35
35
import static com .uber .nullaway .NullabilityUtil .isArrayElementNullable ;
36
+ import static com .uber .nullaway .Nullness .isNullableAnnotation ;
37
+ import static java .lang .annotation .ElementType .TYPE_PARAMETER ;
38
+ import static java .lang .annotation .ElementType .TYPE_USE ;
36
39
37
40
import com .google .auto .service .AutoService ;
38
41
import com .google .auto .value .AutoValue ;
53
56
import com .google .errorprone .matchers .Matchers ;
54
57
import com .google .errorprone .suppliers .Suppliers ;
55
58
import com .google .errorprone .util .ASTHelpers ;
59
+ import com .sun .source .tree .AnnotatedTypeTree ;
60
+ import com .sun .source .tree .AnnotationTree ;
56
61
import com .sun .source .tree .ArrayAccessTree ;
57
62
import com .sun .source .tree .AssignmentTree ;
58
63
import com .sun .source .tree .BinaryTree ;
98
103
import com .uber .nullaway .handlers .Handler ;
99
104
import com .uber .nullaway .handlers .Handlers ;
100
105
import com .uber .nullaway .handlers .MethodAnalysisContext ;
106
+ import java .lang .annotation .ElementType ;
107
+ import java .lang .annotation .Target ;
101
108
import java .util .ArrayList ;
102
109
import java .util .LinkedHashMap ;
103
110
import java .util .LinkedHashSet ;
@@ -187,6 +194,8 @@ public class NullAway extends BugChecker
187
194
static final String CORE_CHECK_NAME = "NullAway.<core>" ;
188
195
189
196
private static final Matcher <ExpressionTree > THIS_MATCHER = NullAway ::isThisIdentifierMatcher ;
197
+ private static final ImmutableSet <ElementType > TYPE_USE_OR_TYPE_PARAMETER =
198
+ ImmutableSet .of (TYPE_USE , TYPE_PARAMETER );
190
199
191
200
private final Predicate <MethodInvocationNode > nonAnnotatedMethod ;
192
201
@@ -570,6 +579,11 @@ public Description matchMemberSelect(MemberSelectTree tree, VisitorState state)
570
579
|| symbol instanceof ModuleElement ) {
571
580
return Description .NO_MATCH ;
572
581
}
582
+ if ((tree .getExpression () instanceof AnnotatedTypeTree )
583
+ && !config .isLegacyAnnotationLocation ()) {
584
+ checkNullableAnnotationPositionInType (
585
+ ((AnnotatedTypeTree ) tree .getExpression ()).getAnnotations (), tree , state );
586
+ }
573
587
574
588
Description badDeref = matchDereference (tree .getExpression (), tree , state );
575
589
if (!badDeref .equals (Description .NO_MATCH )) {
@@ -638,6 +652,10 @@ public Description matchMethod(MethodTree tree, VisitorState state) {
638
652
if (!withinAnnotatedCode (state )) {
639
653
return Description .NO_MATCH ;
640
654
}
655
+ if (!config .isLegacyAnnotationLocation ()) {
656
+ checkNullableAnnotationPositionInType (
657
+ tree .getModifiers ().getAnnotations (), tree .getReturnType (), state );
658
+ }
641
659
// if the method is overriding some other method,
642
660
// check that nullability annotations are consistent with
643
661
// overridden method (if overridden method is in an annotated
@@ -1464,6 +1482,10 @@ public Description matchVariable(VariableTree tree, VisitorState state) {
1464
1482
if (tree .getInitializer () != null && config .isJSpecifyMode ()) {
1465
1483
GenericsChecks .checkTypeParameterNullnessForAssignability (tree , this , state );
1466
1484
}
1485
+ if (!config .isLegacyAnnotationLocation ()) {
1486
+ checkNullableAnnotationPositionInType (
1487
+ tree .getModifiers ().getAnnotations (), tree .getType (), state );
1488
+ }
1467
1489
1468
1490
if (symbol .type .isPrimitive () && tree .getInitializer () != null ) {
1469
1491
doUnboxingCheck (state , tree .getInitializer ());
@@ -1487,6 +1509,85 @@ public Description matchVariable(VariableTree tree, VisitorState state) {
1487
1509
return Description .NO_MATCH ;
1488
1510
}
1489
1511
1512
+ /**
1513
+ * returns true if {@code anno} is a type use annotation; it may also be a declaration annotation
1514
+ */
1515
+ private static boolean isTypeUseAnnotation (Symbol anno ) {
1516
+ Target target = anno .getAnnotation (Target .class );
1517
+ ImmutableSet <ElementType > elementTypes =
1518
+ target == null ? ImmutableSet .of () : ImmutableSet .copyOf (target .value ());
1519
+ return elementTypes .contains (TYPE_USE );
1520
+ }
1521
+
1522
+ /**
1523
+ * returns true if {@code anno} is a declaration annotation; it may also be a type use annotation
1524
+ */
1525
+ private static boolean isDeclarationAnnotation (Symbol anno ) {
1526
+ Target target = anno .getAnnotation (Target .class );
1527
+ if (target == null ) {
1528
+ return true ;
1529
+ }
1530
+ ImmutableSet <ElementType > elementTypes = ImmutableSet .copyOf (target .value ());
1531
+ // Return true for any annotation that is not exclusively a type-use annotation
1532
+ return !(elementTypes .equals (ImmutableSet .of (ElementType .TYPE_USE ))
1533
+ || TYPE_USE_OR_TYPE_PARAMETER .containsAll (elementTypes ));
1534
+ }
1535
+
1536
+ /**
1537
+ * Checks whether any {@code @Nullable} annotation is at the right location for nested types.
1538
+ * Raises an error iff the type is a field access expression (for an inner class type), the
1539
+ * annotation is type use, and the annotation is not applied on the innermost type.
1540
+ *
1541
+ * @param annotations The annotations to check
1542
+ * @param type The tree representing the type structure
1543
+ * @param state The visitor state
1544
+ */
1545
+ private void checkNullableAnnotationPositionInType (
1546
+ List <? extends AnnotationTree > annotations , Tree type , VisitorState state ) {
1547
+
1548
+ // Early return if the type is not a nested or inner class reference.
1549
+ if (!(type instanceof MemberSelectTree )) {
1550
+ return ;
1551
+ }
1552
+
1553
+ // Get the end position of the outer type expression. Any nullable annotation before this
1554
+ // position is considered to be on the outer type, which is incorrect.
1555
+ int endOfOuterType = state .getEndPosition (((MemberSelectTree ) type ).getExpression ());
1556
+ int startOfType = ((JCTree ) type ).getStartPosition ();
1557
+
1558
+ for (AnnotationTree annotation : annotations ) {
1559
+ Symbol sym = ASTHelpers .getSymbol (annotation );
1560
+ if (sym == null ) {
1561
+ continue ;
1562
+ }
1563
+
1564
+ String qualifiedName = sym .getQualifiedName ().toString ();
1565
+ if (!isNullableAnnotation (qualifiedName , config )) {
1566
+ continue ;
1567
+ }
1568
+
1569
+ if (!isTypeUseAnnotation (sym )) {
1570
+ continue ;
1571
+ }
1572
+ // If an annotation is declaration ALSO, we check if it is at the correct location. If it is,
1573
+ // we treat it as declaration and skip the checks.
1574
+ if (isDeclarationAnnotation (sym ) && state .getEndPosition (annotation ) <= startOfType ) {
1575
+ continue ;
1576
+ }
1577
+
1578
+ if (state .getEndPosition (annotation ) < endOfOuterType ) {
1579
+ // annotation is not on the inner-most type
1580
+ ErrorMessage errorMessage =
1581
+ new ErrorMessage (
1582
+ MessageTypes .NULLABLE_ON_WRONG_NESTED_CLASS_LEVEL ,
1583
+ "Type-use nullability annotations should be applied on inner class" );
1584
+
1585
+ state .reportMatch (
1586
+ errorBuilder .createErrorDescription (errorMessage , buildDescription (type ), state , null ));
1587
+ }
1588
+ }
1589
+ }
1590
+
1490
1591
/**
1491
1592
* Check if an inner class's annotation means this Compilation Unit is partially annotated.
1492
1593
*
0 commit comments