Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion nullaway/src/main/java/com/uber/nullaway/ErrorMessage.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ public enum MessageTypes {
WRONG_OVERRIDE_PARAM_GENERIC,
ASSIGN_NULLABLE_TO_NONNULL_ARRAY,
GENERIC_INFERENCE_FAILURE,
NULLABLE_ON_WRONG_NESTED_CLASS_LEVEL
NULLABLE_ON_WRONG_NESTED_CLASS_LEVEL,
UNRECOGNIZED_NULLNESS_ANNOTATION_LOCATION
}

public String getMessage() {
Expand Down
9 changes: 9 additions & 0 deletions nullaway/src/main/java/com/uber/nullaway/NullAway.java
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ public class NullAway extends BugChecker
BugChecker.SwitchExpressionTreeMatcher,
BugChecker.TypeCastTreeMatcher,
BugChecker.ParameterizedTypeTreeMatcher,
BugChecker.AnnotatedTypeTreeMatcher,
BugChecker.SynchronizedTreeMatcher {

static final String INITIALIZATION_CHECK_NAME = "NullAway.Init";
Expand Down Expand Up @@ -778,6 +779,14 @@ public Description matchParameterizedType(ParameterizedTypeTree tree, VisitorSta
return Description.NO_MATCH;
}

@Override
public Description matchAnnotatedType(AnnotatedTypeTree tree, VisitorState state) {
if (withinAnnotatedCode(state) && config.isJSpecifyMode() && config.handleWildcardGenerics()) {
genericsChecks.checkForNullnessAnnotationsOnWildcards(tree, state);
}
return Description.NO_MATCH;
}

/**
* checks that an overriding method does not override a {@code @Nullable} parameter with a
* {@code @NonNull} parameter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import com.sun.source.tree.WildcardTree;
import com.sun.source.util.TreePath;
import com.sun.source.util.TreePathScanner;
import com.sun.tools.javac.code.Attribute;
Expand Down Expand Up @@ -163,6 +164,11 @@ public void checkInstantiationForParameterizedTypedTree(
for (int i = 0; i < typeArguments.size(); i++) {
Tree curTypeArg = typeArguments.get(i);
if (curTypeArg instanceof AnnotatedTypeTree annotatedType) {
// Annotations directly on wildcards are illegal in JSpecify mode and are diagnosed in
// checkForNullnessAnnotationsOnWildcards().
if (annotatedType.getUnderlyingType() instanceof WildcardTree) {
continue;
}
for (AnnotationTree annotation : annotatedType.getAnnotations()) {
Type annotationType = ASTHelpers.getType(annotation);
if (annotationType != null
Expand Down Expand Up @@ -190,6 +196,36 @@ public void checkInstantiationForParameterizedTypedTree(
}
}

/**
* Reports nullness annotations written directly on a wildcard, which is not a legal annotation
* location under JSpecify.
*/
public void checkForNullnessAnnotationsOnWildcards(
AnnotatedTypeTree annotatedType, VisitorState state) {
if (!(annotatedType.getUnderlyingType() instanceof WildcardTree)) {
return;
}
for (AnnotationTree annotation : annotatedType.getAnnotations()) {
Type annotationType = ASTHelpers.getType(annotation);
if (annotationType == null) {
continue;
}
String annotationName = annotationType.toString();
if (!Nullness.isNullableAnnotation(annotationName, config)
&& !Nullness.isNonNullAnnotation(annotationName, config)) {
continue;
}
ErrorBuilder errorBuilder = analysis.getErrorBuilder();
ErrorMessage errorMessage =
new ErrorMessage(
ErrorMessage.MessageTypes.UNRECOGNIZED_NULLNESS_ANNOTATION_LOCATION,
"illegal location for annotation: nullness annotations cannot be written directly on a wildcard; annotate the wildcard bound instead");
state.reportMatch(
errorBuilder.createErrorDescription(
errorMessage, analysis.buildDescription(annotation), state, null));
}
}

private boolean[] getTypeParamsWithNullableUpperBound(Type type) {
Symbol.TypeSymbol tsym = type.tsym;
com.sun.tools.javac.util.List<Type> baseTypeArgs = tsym.type.getTypeArguments();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,36 @@ Foo<?> test(Foo<@Nullable Void> foo) {
.doTest();
}

@Test
public void nullableOnWildcard() {
makeHelperWithInferenceFailureWarning()
.addSourceLines(
"Test.java",
"""
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
import java.util.function.Function;
@NullMarked
class Test<K,V> {
@Nullable V testPositive(@Nullable K k,
Function<
// BUG: Diagnostic contains: illegal location for annotation
@Nullable ? super K,
// BUG: Diagnostic contains: illegal location for annotation
@NonNull ? extends V> function) {
// BUG: Diagnostic contains: passing @Nullable parameter 'k' where @NonNull is required
return function.apply(k);
}

@Nullable V testNegative(@Nullable K k, Function<? super @Nullable K, ? extends @Nullable V> function) {
return function.apply(k);
}
}
""")
.doTest();
}

private CompilationTestHelper makeHelper() {
return makeTestHelperWithArgs(
JSpecifyJavacConfig.withJSpecifyModeArgs(
Expand Down
Loading