Skip to content

Commit dd0da6d

Browse files
dhruv-agrmsridharcoderabbitai[bot]
authored
issue 1275 - report unboxing warning for for-each loop (#1281)
The core of the solution is a new check that activates when the loop variable is a primitive type For arrays (e.g., @nullable Integer[]), the existing NullabilityUtil.isArrayElementNullable utility is used to determine if the elements can be null. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * New Features * Adds a JSpecify-only check that flags unsafe auto-unboxing in enhanced for-each loops over arrays when elements may be nullable and the loop variable is primitive. Clear diagnostic provided. No impact outside JSpecify mode. * Tests * Added tests covering nullable and non-null array elements in for-each unboxing scenarios. * Introduced a test-only Nullable annotation and additional cases validating array element nullability behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Manu Sridharan <msridhar@gmail.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent 533986c commit dd0da6d

File tree

2 files changed

+55
-0
lines changed

2 files changed

+55
-0
lines changed

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1852,6 +1852,30 @@ public Description matchEnhancedForLoop(EnhancedForLoopTree tree, VisitorState s
18521852
if (mayBeNullExpr(state, expr)) {
18531853
return errorBuilder.createErrorDescription(errorMessage, buildDescription(expr), state, null);
18541854
}
1855+
// auto-unboxing check in JSpecify mode
1856+
if (!config.isJSpecifyMode()) {
1857+
return Description.NO_MATCH;
1858+
}
1859+
1860+
VariableTree loopVariable = tree.getVariable();
1861+
Type loopVariableType = ASTHelpers.getType(loopVariable);
1862+
// Only relevant when the loop variable is a primitive (implies unboxing of elements).
1863+
if (loopVariableType == null || !loopVariableType.isPrimitive()) {
1864+
return Description.NO_MATCH;
1865+
}
1866+
Type expressionType = ASTHelpers.getType(expr);
1867+
if (expressionType != null && expressionType.getKind() == TypeKind.ARRAY) {
1868+
Symbol arraySymbol = ASTHelpers.getSymbol(expr);
1869+
if (arraySymbol != null && isArrayElementNullable(arraySymbol, config)) {
1870+
// A nullable element is being unboxed to a primitive. This is unsafe.
1871+
ErrorMessage errorMessageUnbox =
1872+
new ErrorMessage(MessageTypes.UNBOX_NULLABLE, "unboxing of a @Nullable value");
1873+
state.reportMatch(
1874+
errorBuilder.createErrorDescription(
1875+
errorMessageUnbox, buildDescription(loopVariable), state, null));
1876+
}
1877+
}
1878+
18551879
return Description.NO_MATCH;
18561880
}
18571881

nullaway/src/test/java/com/uber/nullaway/jspecify/JSpecifyArrayTests.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,37 @@ public void typeUseAndDeclarationAnnotationOnArray() {
672672
.doTest();
673673
}
674674

675+
@Test
676+
public void unboxForEachLoop() {
677+
makeHelper()
678+
.addSourceLines(
679+
"Test.java",
680+
"import org.jspecify.annotations.*;",
681+
"@NullMarked",
682+
"class Test {",
683+
" void f(@Nullable Integer[] array) {",
684+
" // BUG: Diagnostic contains: unboxing of a @Nullable value",
685+
" for (int x : array) {}",
686+
" }",
687+
"}")
688+
.doTest();
689+
}
690+
691+
@Test
692+
public void unboxForEachLoopNonNull() {
693+
makeHelper()
694+
.addSourceLines(
695+
"Test.java",
696+
"import org.jspecify.annotations.*;",
697+
"@NullMarked",
698+
"class Test {",
699+
" void f(Integer[] array) {",
700+
" for (int x : array) {}",
701+
" }",
702+
"}")
703+
.doTest();
704+
}
705+
675706
private CompilationTestHelper makeHelper() {
676707
return makeTestHelperWithArgs(
677708
Arrays.asList(

0 commit comments

Comments
 (0)