Skip to content

Commit

Permalink
Proper checking of vararg overrides with JSpecify annotations (#1116)
Browse files Browse the repository at this point in the history
Fixes #1113 

We weren't calling the right API to check the nullability of a varargs
argument when checking valid overriding. Most of this PR is tests to
check handling of most of the cases.
  • Loading branch information
msridhar authored Dec 26, 2024
1 parent 7cb6b98 commit 6d331c7
Show file tree
Hide file tree
Showing 5 changed files with 248 additions and 4 deletions.
11 changes: 9 additions & 2 deletions nullaway/src/main/java/com/uber/nullaway/NullAway.java
Original file line number Diff line number Diff line change
Expand Up @@ -780,9 +780,16 @@ private Description checkParamOverriding(
// (otherwise, whether we acknowledge @Nullable in unannotated code or not depends on the
// -XepOpt:NullAway:AcknowledgeRestrictiveAnnotations flag and its handler).
if (isOverriddenMethodAnnotated) {
boolean overriddenMethodIsVarArgs = overriddenMethod.isVarArgs();
for (int i = 0; i < superParamSymbols.size(); i++) {
Nullness paramNullness;
if (Nullness.paramHasNullableAnnotation(overriddenMethod, i, config)) {
if (overriddenMethodIsVarArgs && i == superParamSymbols.size() - 1) {
// For a varargs position, we need to check if the array itself is @Nullable
paramNullness =
Nullness.varargsArrayIsNullable(superParamSymbols.get(i), config)
? Nullness.NULLABLE
: Nullness.NONNULL;
} else if (Nullness.paramHasNullableAnnotation(overriddenMethod, i, config)) {
paramNullness = Nullness.NULLABLE;
} else if (config.isJSpecifyMode()) {
// Check if the parameter type is a type variable and the corresponding generic type
Expand Down Expand Up @@ -840,7 +847,7 @@ private Description checkParamOverriding(
for (int i = 0; i < superParamSymbols.size(); i++) {
if (!Objects.equals(overriddenMethodArgNullnessMap[i], Nullness.NULLABLE)) {
// No need to check, unless the argument of the overridden method is effectively @Nullable,
// in which case it can't be overridding a @NonNull arg.
// in which case it can't be overridden by a @NonNull arg.
continue;
}
int methodParamInd = i - startParam;
Expand Down
2 changes: 1 addition & 1 deletion nullaway/src/main/java/com/uber/nullaway/Nullness.java
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ public static boolean paramHasNonNullAnnotation(
}

/**
* Is the varargs parameter {@code paramSymbol} have a {@code @Nullable} annotation indicating
* Does the varargs parameter {@code paramSymbol} have a {@code @Nullable} annotation indicating
* that the argument array passed at a call site can be {@code null}? Syntactically, this would be
* written as {@code foo(Object @Nullable... args}}
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1030,7 +1030,6 @@ private static void checkTypeParameterNullnessForOverridingMethodParameterType(
MethodTree tree, Type overriddenMethodType, NullAway analysis, VisitorState state) {
List<? extends VariableTree> methodParameters = tree.getParameters();
List<Type> overriddenMethodParameterTypes = overriddenMethodType.getParameterTypes();
// TODO handle varargs; they are not handled for now
for (int i = 0; i < methodParameters.size(); i++) {
Type overridingMethodParameterType = getTreeType(methodParameters.get(i), state);
Type overriddenMethodParameterType = overriddenMethodParameterTypes.get(i);
Expand Down
117 changes: 117 additions & 0 deletions nullaway/src/test/java/com/uber/nullaway/VarargsTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -599,4 +599,121 @@ public void testVarargsRestrictiveBytecodes() {
"}")
.doTest();
}

@Test
public void varargsOverride() {
defaultCompilationHelper
.addSourceLines(
"VarargsOverride.java",
"package com.uber;",
"import org.jspecify.annotations.Nullable;",
"public class VarargsOverride {",
" interface NullableVarargsContents {",
" void varargs(@Nullable Object... params);",
" }",
" static class NullableVarargsContentsImpl1 implements NullableVarargsContents {",
" @Override",
" public void varargs(@Nullable Object... params) {",
" }",
" }",
" static class NullableVarargsContentsImpl2 implements NullableVarargsContents {",
" @Override",
" public void varargs(Object... params) {",
" }",
" }",
" static class NullableVarargsContentsImpl3 implements NullableVarargsContents {",
" @Override",
" public void varargs(Object @Nullable... params) {",
" }",
" }",
" static class NullableVarargsContentsImpl4 implements NullableVarargsContents {",
" @Override",
" public void varargs(@Nullable Object @Nullable... params) {",
" }",
" }",
" interface NullableVarargsArray {",
" void varargs(Object @Nullable... params);",
" }",
" static class NullableVarargsArrayImpl1 implements NullableVarargsArray {",
" @Override",
" // BUG: Diagnostic contains: parameter params is @NonNull",
" public void varargs(@Nullable Object... params) {",
" }",
" }",
" static class NullableVarargsArrayImpl2 implements NullableVarargsArray {",
" @Override",
" // BUG: Diagnostic contains: parameter params is @NonNull",
" public void varargs(Object... params) {",
" }",
" }",
" static class NullableVarargsArrayImpl3 implements NullableVarargsArray {",
" @Override",
" public void varargs(Object @Nullable... params) {",
" }",
" }",
" static class NullableVarargsArrayImpl4 implements NullableVarargsArray {",
" @Override",
" public void varargs(@Nullable Object @Nullable... params) {",
" }",
" }",
" interface NullableVarargsBoth {",
" void varargs(@Nullable Object @Nullable... params);",
" }",
" static class NullableVarargsBothImpl1 implements NullableVarargsBoth {",
" @Override",
" // BUG: Diagnostic contains: parameter params is @NonNull",
" public void varargs(@Nullable Object... params) {",
" }",
" }",
" static class NullableVarargsBothImpl2 implements NullableVarargsBoth {",
" @Override",
" // BUG: Diagnostic contains: parameter params is @NonNull",
" public void varargs(Object... params) {",
" }",
" }",
" static class NullableVarargsBothImpl3 implements NullableVarargsBoth {",
" @Override",
" public void varargs(Object @Nullable... params) {",
" }",
" }",
" static class NullableVarargsBothImpl4 implements NullableVarargsBoth {",
" @Override",
" public void varargs(@Nullable Object @Nullable... params) {",
" }",
" }",
" interface NonNullVarargs {",
" void varargs(Object... params);",
" }",
" static class NonNullVarargsImpl1 implements NonNullVarargs {",
" @Override",
" public void varargs(@Nullable Object... params) {",
" }",
" }",
" static class NonNullVarargsImpl2 implements NonNullVarargs {",
" @Override",
" public void varargs(Object... params) {",
" }",
" }",
" static class NonNullVarargsImpl3 implements NonNullVarargs {",
" @Override",
" public void varargs(Object @Nullable... params) {",
" }",
" }",
" static class NonNullVarargsImpl4 implements NonNullVarargs {",
" @Override",
" public void varargs(@Nullable Object @Nullable... params) {",
" }",
" }",
" interface MultiArgNullableVarargsArray {",
" void varargs(String s, Object @Nullable... params);",
" }",
" static class MultiArgNullableVarargsArrayImpl implements MultiArgNullableVarargsArray {",
" @Override",
" // BUG: Diagnostic contains: parameter params is @NonNull",
" public void varargs(String s, Object... params) {",
" }",
" }",
"}")
.doTest();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,127 @@ public void testVarargsRestrictive() {
.doTest();
}

@Test
public void varargsOverride() {
makeHelper()
.addSourceLines(
"VarargsOverride.java",
"package com.uber;",
"import org.jspecify.annotations.Nullable;",
"public class VarargsOverride {",
" interface NullableVarargsContents {",
" void varargs(@Nullable Object... params);",
" }",
" static class NullableVarargsContentsImpl1 implements NullableVarargsContents {",
" @Override",
" // legal override",
" public void varargs(@Nullable Object... params) {",
" }",
" }",
" static class NullableVarargsContentsImpl2 implements NullableVarargsContents {",
" @Override",
" // BUG: Diagnostic contains: Parameter has type Object[], but overridden method",
" public void varargs(Object... params) {",
" }",
" }",
" static class NullableVarargsContentsImpl3 implements NullableVarargsContents {",
" @Override",
// TODO open an issue to improve the error message in a follow up
" // BUG: Diagnostic contains: Parameter has type Object[]",
" public void varargs(Object @Nullable... params) {",
" }",
" }",
" static class NullableVarargsContentsImpl4 implements NullableVarargsContents {",
" @Override",
" // legal override",
" public void varargs(@Nullable Object @Nullable... params) {",
" }",
" }",
" interface NullableVarargsArray {",
" void varargs(Object @Nullable... params);",
" }",
" static class NullableVarargsArrayImpl1 implements NullableVarargsArray {",
" @Override",
" // BUG: Diagnostic contains: parameter params is @NonNull",
" public void varargs(@Nullable Object... params) {",
" }",
" }",
" static class NullableVarargsArrayImpl2 implements NullableVarargsArray {",
" @Override",
" // BUG: Diagnostic contains: parameter params is @NonNull",
" public void varargs(Object... params) {",
" }",
" }",
" static class NullableVarargsArrayImpl3 implements NullableVarargsArray {",
" @Override",
" // legal override",
" public void varargs(Object @Nullable... params) {",
" }",
" }",
" static class NullableVarargsArrayImpl4 implements NullableVarargsArray {",
" @Override",
" // ok: contravariance",
" public void varargs(@Nullable Object @Nullable... params) {",
" }",
" }",
" interface NullableVarargsBoth {",
" void varargs(@Nullable Object @Nullable... params);",
" }",
" static class NullableVarargsBothImpl1 implements NullableVarargsBoth {",
" @Override",
" // BUG: Diagnostic contains: parameter params is @NonNull",
" public void varargs(@Nullable Object... params) {",
" }",
" }",
" static class NullableVarargsBothImpl2 implements NullableVarargsBoth {",
" @Override",
" // BUG: Diagnostic contains: parameter params is @NonNull",
" public void varargs(Object... params) {",
" }",
" }",
" static class NullableVarargsBothImpl3 implements NullableVarargsBoth {",
" @Override",
" // BUG: Diagnostic contains: Parameter has type Object[]",
" public void varargs(Object @Nullable... params) {",
" }",
" }",
" static class NullableVarargsBothImpl4 implements NullableVarargsBoth {",
" @Override",
" // legal override",
" public void varargs(@Nullable Object @Nullable... params) {",
" }",
" }",
" interface NonNullVarargs {",
" void varargs(Object... params);",
" }",
" static class NonNullVarargsImpl1 implements NonNullVarargs {",
" @Override",
" // ok: contravariance",
" public void varargs(@Nullable Object... params) {",
" }",
" }",
" static class NonNullVarargsImpl2 implements NonNullVarargs {",
" @Override",
" // legal override",
" public void varargs(Object... params) {",
" }",
" }",
" static class NonNullVarargsImpl3 implements NonNullVarargs {",
" @Override",
" // legal override",
" public void varargs(Object @Nullable... params) {",
" }",
" }",
" static class NonNullVarargsImpl4 implements NonNullVarargs {",
" @Override",
" // ok: contravariance",
" public void varargs(@Nullable Object @Nullable... params) {",
" }",
" }",
"}")
.doTest();
}

private CompilationTestHelper makeHelper() {
return makeTestHelperWithArgs(
Arrays.asList(
Expand Down

0 comments on commit 6d331c7

Please sign in to comment.