Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSpecify: Reason about nullability of reads from arrays #875

Merged
merged 13 commits into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from 11 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
23 changes: 21 additions & 2 deletions nullaway/src/main/java/com/uber/nullaway/NullAway.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,12 @@
import com.sun.source.tree.WhileLoopTree;
import com.sun.source.util.TreePath;
import com.sun.source.util.Trees;
import com.sun.tools.javac.code.Attribute;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Symbol.VarSymbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.TypeAnnotationPosition;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.uber.nullaway.ErrorMessage.MessageTypes;
Expand Down Expand Up @@ -2309,8 +2311,6 @@
case NULL_LITERAL:
// obviously null
return true;
case ARRAY_ACCESS:
// unsound! we cannot check for nullness of array contents yet
case NEW_CLASS:
case NEW_ARRAY:
// for string concatenation, auto-boxing
Expand Down Expand Up @@ -2368,6 +2368,25 @@
Symbol exprSymbol = ASTHelpers.getSymbol(expr);
boolean exprMayBeNull;
switch (expr.getKind()) {
case ARRAY_ACCESS:
exprMayBeNull = false;
// TODO: export to utility method
if (config.isJSpecifyMode()) {
ArrayAccessTree arrayAccess = (ArrayAccessTree) expr;
ExpressionTree arrayExpr = arrayAccess.getExpression();
Symbol arraySymbol = ASTHelpers.getSymbol(arrayExpr);
if (arraySymbol != null) {
for (Attribute.TypeCompound t : arraySymbol.getRawTypeAttributes()) {
for (TypeAnnotationPosition.TypePathEntry entry : t.position.location) {
if (entry.tag == TypeAnnotationPosition.TypePathEntryKind.ARRAY) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to check whether the annotation is actually @Nullable. This just checks for the presence of some annotation. And yes this logic should be extracted to a utility method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed!

exprMayBeNull = true;
break;
}
}

Check warning on line 2385 in nullaway/src/main/java/com/uber/nullaway/NullAway.java

View check run for this annotation

Codecov / codecov/patch

nullaway/src/main/java/com/uber/nullaway/NullAway.java#L2385

Added line #L2385 was not covered by tests
}
}
}
break;
case MEMBER_SELECT:
if (exprSymbol == null) {
throw new IllegalStateException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@
import com.google.errorprone.suppliers.Suppliers;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.tools.javac.code.Attribute;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.TypeAnnotationPosition;
import com.sun.tools.javac.code.TypeTag;
import com.uber.nullaway.CodeAnnotationInfo;
import com.uber.nullaway.Config;
Expand Down Expand Up @@ -794,9 +796,32 @@
public TransferResult<Nullness, NullnessStore> visitArrayAccess(
ArrayAccessNode node, TransferInput<Nullness, NullnessStore> input) {
ReadableUpdates updates = new ReadableUpdates();

setNonnullIfAnalyzeable(updates, node.getArray());
// this is unsound
return updateRegularStore(defaultAssumption, input, updates);

Nullness resultNullness = defaultAssumption;

// TODO: export to utility method
if (config.isJSpecifyMode()) {
Symbol arraySymbol = ASTHelpers.getSymbol(node.getArray().getTree());
if (arraySymbol != null) {
boolean isElementNullable = false;
for (Attribute.TypeCompound t : arraySymbol.getRawTypeAttributes()) {
for (TypeAnnotationPosition.TypePathEntry entry : t.position.location) {
if (entry.tag == TypeAnnotationPosition.TypePathEntryKind.ARRAY) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as above, check for @Nullable

isElementNullable = true;
break;
}
}

Check warning on line 815 in nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPathNullnessPropagation.java

View check run for this annotation

Codecov / codecov/patch

nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPathNullnessPropagation.java#L815

Added line #L815 was not covered by tests
if (isElementNullable) {
resultNullness = Nullness.NULLABLE;
break;
}
}
}
}

return updateRegularStore(resultNullness, input, updates);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public void arrayContentsAnnotationDereference() {
" static @Nullable String [] fizz = {\"1\"};",
" static Object foo = new Object();",
" static void foo() {",
" // TODO: This should report an error due to dereference of @Nullable fizz[0]",
" // BUG: Diagnostic contains: dereferenced expression fizz[0] is @Nullable",
" int bar = fizz[0].length();",
" // OK: valid dereference since only elements of the array can be null",
" foo = fizz.length;",
Expand All @@ -74,7 +74,7 @@ public void arrayContentsAnnotationAssignment() {
"class Test {",
" Object fizz = new Object();",
" void m( @Nullable Integer [] foo) {",
" // TODO: This should report an error due to assignment of @Nullable foo[0] to @NonNull field",
" // BUG: Diagnostic contains: assigning @Nullable expression to @NonNull field",
" fizz = foo[0];",
" // OK: valid assignment since only elements can be null",
" fizz = foo;",
Expand Down Expand Up @@ -110,6 +110,31 @@ public void arrayDeclarationAnnotation() {
.doTest();
}

@Test
public void arrayContentsAndTopLevelAnnotation() {
makeHelper()
.addSourceLines(
"Test.java",
"package com.uber;",
"import org.jspecify.annotations.Nullable;",
"class Test {",
" static @Nullable String @Nullable [] fizz = {\"1\"};",
" static Object foo = new Object();",
" static void foo() {",
" if (fizz != null) {",
" String s = fizz[0];",
" // BUG: Diagnostic contains: dereferenced expression s is @Nullable",
" int l1 = s.length();",
" if (s != null){",
" // OK: handled by null check",
" int l2 = s.length();",
" }",
" }",
" }",
"}")
.doTest();
}

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