Skip to content

Commit cac2ed3

Browse files
authored
Fix reading of JSpecify @nullable annotations from varargs parameter in bytecode (#1089)
Fixes #1088 We had correct code in one place for reading `@NonNull` annotations, but didn't use it for `@Nullable`. Get rid of the code duplication so we share the logic now.
1 parent 35279f0 commit cac2ed3

File tree

3 files changed

+68
-37
lines changed

3 files changed

+68
-37
lines changed

nullaway/src/main/java/com/uber/nullaway/NullabilityUtil.java

Lines changed: 49 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import java.util.List;
5252
import java.util.Map;
5353
import java.util.Set;
54+
import java.util.function.BiPredicate;
5455
import java.util.stream.Collectors;
5556
import java.util.stream.Stream;
5657
import javax.lang.model.element.AnnotationMirror;
@@ -328,13 +329,13 @@ private static Stream<Attribute.TypeCompound> getTypeUseAnnotations(
328329
return rawTypeAttributes.filter(
329330
(t) ->
330331
t.position.type.equals(TargetType.METHOD_RETURN)
331-
&& isDirectTypeUseAnnotation(t, symbol, config));
332+
&& (!onlyDirect || isDirectTypeUseAnnotation(t, symbol, config)));
332333
} else {
333334
// filter for annotations directly on the type
334335
return rawTypeAttributes.filter(
335336
t ->
336337
targetTypeMatches(symbol, t.position)
337-
&& (!onlyDirect || NullabilityUtil.isDirectTypeUseAnnotation(t, symbol, config)));
338+
&& (!onlyDirect || isDirectTypeUseAnnotation(t, symbol, config)));
338339
}
339340
}
340341

@@ -540,22 +541,11 @@ public static <T> T castToNonNull(@Nullable T obj) {
540541
* otherwise
541542
*/
542543
public static boolean isArrayElementNullable(Symbol arraySymbol, Config config) {
543-
for (Attribute.TypeCompound t : arraySymbol.getRawTypeAttributes()) {
544-
for (TypeAnnotationPosition.TypePathEntry entry : t.position.location) {
545-
if (entry.tag == TypeAnnotationPosition.TypePathEntryKind.ARRAY) {
546-
if (Nullness.isNullableAnnotation(t.type.toString(), config)) {
547-
return true;
548-
}
549-
}
550-
}
551-
}
552-
// For varargs symbols we also consider the elements to be @Nullable if there is a @Nullable
553-
// declaration annotation on the parameter
554-
// NOTE this flag check does not work for the varargs parameter of a method defined in bytecodes
555-
if ((arraySymbol.flags() & Flags.VARARGS) != 0) {
556-
return Nullness.hasNullableDeclarationAnnotation(arraySymbol, config);
557-
}
558-
return false;
544+
return checkArrayElementAnnotations(
545+
arraySymbol,
546+
config,
547+
Nullness::isNullableAnnotation,
548+
Nullness::hasNullableDeclarationAnnotation);
559549
}
560550

561551
/**
@@ -582,12 +572,50 @@ public static boolean nullableVarargsElementsForSourceOrBytecode(
582572
* otherwise
583573
*/
584574
public static boolean isArrayElementNonNull(Symbol arraySymbol, Config config) {
575+
return checkArrayElementAnnotations(
576+
arraySymbol,
577+
config,
578+
Nullness::isNonNullAnnotation,
579+
Nullness::hasNonNullDeclarationAnnotation);
580+
}
581+
582+
/**
583+
* Checks if the given varargs symbol has a {@code @NonNull} annotation for its elements. Works
584+
* for both source and bytecode.
585+
*
586+
* @param varargsSymbol the symbol of the varargs parameter
587+
* @param config NullAway configuration
588+
* @return true if the varargs symbol has a {@code @NonNull} annotation for its elements, false
589+
* otherwise
590+
*/
591+
public static boolean nonnullVarargsElementsForSourceOrBytecode(
592+
Symbol varargsSymbol, Config config) {
593+
return isArrayElementNonNull(varargsSymbol, config)
594+
|| Nullness.hasNonNullDeclarationAnnotation(varargsSymbol, config);
595+
}
596+
597+
/**
598+
* Checks if the annotations on the elements of some array symbol satisfy some predicate.
599+
*
600+
* @param arraySymbol the array symbol
601+
* @param config NullAway configuration
602+
* @param typeUseCheck the predicate to check the type-use annotations
603+
* @param declarationCheck the predicate to check the declaration annotations (applied only to
604+
* varargs symbols)
605+
* @return true if the annotations on the elements of the array symbol satisfy the given
606+
* predicates, false otherwise
607+
*/
608+
private static boolean checkArrayElementAnnotations(
609+
Symbol arraySymbol,
610+
Config config,
611+
BiPredicate<String, Config> typeUseCheck,
612+
BiPredicate<Symbol, Config> declarationCheck) {
585613
if (getTypeUseAnnotations(arraySymbol, config, /* onlyDirect= */ false)
586614
.anyMatch(
587615
t -> {
588616
for (TypeAnnotationPosition.TypePathEntry entry : t.position.location) {
589617
if (entry.tag == TypeAnnotationPosition.TypePathEntryKind.ARRAY) {
590-
if (Nullness.isNonNullAnnotation(t.type.toString(), config)) {
618+
if (typeUseCheck.test(t.type.toString(), config)) {
591619
return true;
592620
}
593621
}
@@ -596,30 +624,14 @@ public static boolean isArrayElementNonNull(Symbol arraySymbol, Config config) {
596624
})) {
597625
return true;
598626
}
599-
// For varargs symbols we also consider the elements to be @NonNull if there is a @NonNull
600-
// declaration annotation on the parameter
627+
// For varargs symbols we also check for declaration annotations on the parameter
601628
// NOTE this flag check does not work for the varargs parameter of a method defined in bytecodes
602629
if ((arraySymbol.flags() & Flags.VARARGS) != 0) {
603-
return Nullness.hasNonNullDeclarationAnnotation(arraySymbol, config);
630+
return declarationCheck.test(arraySymbol, config);
604631
}
605632
return false;
606633
}
607634

608-
/**
609-
* Checks if the given varargs symbol has a {@code @NonNull} annotation for its elements. Works
610-
* for both source and bytecode.
611-
*
612-
* @param varargsSymbol the symbol of the varargs parameter
613-
* @param config NullAway configuration
614-
* @return true if the varargs symbol has a {@code @NonNull} annotation for its elements, false
615-
* otherwise
616-
*/
617-
public static boolean nonnullVarargsElementsForSourceOrBytecode(
618-
Symbol varargsSymbol, Config config) {
619-
return isArrayElementNonNull(varargsSymbol, config)
620-
|| Nullness.hasNonNullDeclarationAnnotation(varargsSymbol, config);
621-
}
622-
623635
/**
624636
* Does the given symbol have a JetBrains @NotNull declaration annotation? Useful for workarounds
625637
* in light of https://github.com/uber/NullAway/issues/720

nullaway/src/test/java/com/uber/nullaway/VarargsTests.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,22 @@ public void nullableTypeUseVarArgsFromBytecode() {
106106
.doTest();
107107
}
108108

109+
@Test
110+
public void nullableVarArgsFromBytecodeJSpecify() {
111+
defaultCompilationHelper
112+
.addSourceLines(
113+
"Test.java",
114+
"package com.uber;",
115+
"import com.uber.lib.Varargs;",
116+
"public class Test {",
117+
" public void testTypeUse() {",
118+
" String x = null;",
119+
" Varargs.typeUseNullableElementsJSpecify(x);",
120+
" }",
121+
"}")
122+
.doTest();
123+
}
124+
109125
@Test
110126
public void nullableTypeUseVarargs() {
111127
defaultCompilationHelper

test-java-lib/src/main/java/com/uber/lib/Varargs.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,7 @@ public class Varargs {
77
public Varargs(@Nullable String... args) {}
88

99
public static void typeUse(String @org.jspecify.annotations.Nullable ... args) {}
10+
11+
public static void typeUseNullableElementsJSpecify(
12+
@org.jspecify.annotations.Nullable String... args) {}
1013
}

0 commit comments

Comments
 (0)