Skip to content

Commit 876f635

Browse files
authored
Use Type Use Annotations correctly when assigning null to non-null array component. (#267)
This PR adds Nulllable annotation at the component of an array in case when null is being assigned to it. This feature uses TypeUseAnnotations and is available only in Jspecify mode. We check for a specific error message from Nullaway checker - `ASSIGN_NULLABLE_TO_NONNULL_ARRAY` and based on this create Type Use annotation at the required index.
1 parent 3dd84c6 commit 876f635

File tree

10 files changed

+104
-14
lines changed

10 files changed

+104
-14
lines changed

annotator-core/src/main/java/edu/ucr/cs/riple/core/checkers/nullaway/NullAway.java

+12-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
package edu.ucr.cs.riple.core.checkers.nullaway;
2626

2727
import com.google.common.base.Preconditions;
28+
import com.google.common.collect.ImmutableList;
2829
import com.google.common.collect.ImmutableSet;
2930
import edu.ucr.cs.riple.core.Context;
3031
import edu.ucr.cs.riple.core.checkers.CheckerBaseClass;
@@ -134,7 +135,17 @@ private NullAwayError deserializeErrorFromTSVLine(ModuleInfo moduleInfo, String
134135
if (nonnullTarget == null) {
135136
annotations = Set.of();
136137
} else if (Utility.isTypeUseAnnotation(config.nullableAnnot)) {
137-
annotations = Set.of(new AddTypeUseMarkerAnnotation(nonnullTarget, config.nullableAnnot));
138+
if (errorType.equals(NullAwayError.ASSIGN_NULLABLE_TO_NONNULL_ARRAY)) {
139+
// The error ASSIGN_NULLABLE_TO_NONNULL_ARRAY from NullAway triggers a fix on an array
140+
// variable
141+
// with [1, 0] indicating its component type.
142+
annotations =
143+
Set.of(
144+
new AddTypeUseMarkerAnnotation(
145+
nonnullTarget, config.nullableAnnot, ImmutableList.of(ImmutableList.of(1, 0))));
146+
} else {
147+
annotations = Set.of(new AddTypeUseMarkerAnnotation(nonnullTarget, config.nullableAnnot));
148+
}
138149
} else {
139150
annotations = Set.of(new AddMarkerAnnotation(nonnullTarget, config.nullableAnnot));
140151
}

annotator-core/src/main/java/edu/ucr/cs/riple/core/checkers/nullaway/NullAwayError.java

+6
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ public class NullAwayError extends Error {
4141
/** Error type for field initialization errors from NullAway in {@code String}. */
4242
public static final String FIELD_INITIALIZER_ERROR = "FIELD_NO_INIT";
4343

44+
/**
45+
* Error type for assigning nullable values to non-nullable arrays from NullAway in {@code
46+
* String}.
47+
*/
48+
public static final String ASSIGN_NULLABLE_TO_NONNULL_ARRAY = "ASSIGN_NULLABLE_TO_NONNULL_ARRAY";
49+
4450
public NullAwayError(
4551
String messageType,
4652
String message,

annotator-core/src/test/java/edu/ucr/cs/riple/core/CoreTest.java

+25
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import static com.google.common.collect.Sets.newHashSet;
2828
import static java.util.Collections.singleton;
2929

30+
import com.google.common.collect.ImmutableList;
3031
import edu.ucr.cs.riple.core.tools.TReport;
3132
import edu.ucr.cs.riple.injector.location.OnField;
3233
import edu.ucr.cs.riple.injector.location.OnMethod;
@@ -572,4 +573,28 @@ public void nonnullTypeUseAnnotationOnFormalParameterAcknowledgmentTest() {
572573
// No annotation should be added even though it can reduce the number of errors.
573574
Assert.assertEquals(coreTestHelper.getLog().getInjectedAnnotations().size(), 0);
574575
}
576+
577+
@Test
578+
public void assignNullableToNonnullArrayComponentTypeTest() {
579+
coreTestHelper
580+
.onTarget()
581+
.withSourceLines(
582+
"Main.java",
583+
"package test;",
584+
"public class Main {",
585+
" Object[] arr = new Object[1];",
586+
" void run() {",
587+
" arr[0] = null;",
588+
" }",
589+
"}")
590+
.withExpectedReports(
591+
new TReport(
592+
new OnField("Main.java", "test.Main", Set.of("arr")),
593+
ImmutableList.of(ImmutableList.of(1, 0)),
594+
-1))
595+
.disableBailOut()
596+
.checkExpectedOutput("assignNullableToNonnullArrayComponentTypeTest/expected")
597+
.enableJSpecifyMode()
598+
.start();
599+
}
575600
}

annotator-core/src/test/java/edu/ucr/cs/riple/core/tools/CoreTestHelper.java

+25-4
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,19 @@ public class CoreTestHelper {
109109

110110
private ParserConfiguration.LanguageLevel languageLevel;
111111

112+
/**
113+
* JSpecify mode activation. Deactivated by default. If activated, the test will enable {<a
114+
* href="https://jspecify.dev">JSpecify</a>} mode on NullAway.
115+
*/
116+
private boolean jSpecifyModeEnabled;
117+
112118
public CoreTestHelper(Path projectPath, Path outDirPath) {
113119
this.projectPath = projectPath;
114120
this.outDirPath = outDirPath;
115121
this.expectedReports = new HashSet<>();
116122
this.projectBuilder = new ProjectBuilder(this, projectPath);
117123
this.languageLevel = ParserConfiguration.LanguageLevel.JAVA_17;
124+
this.jSpecifyModeEnabled = false;
118125
}
119126

120127
public Module onTarget() {
@@ -241,6 +248,16 @@ public CoreTestHelper withLanguageLevel(ParserConfiguration.LanguageLevel langua
241248
return this;
242249
}
243250

251+
/**
252+
* Enables JSpecify mode.
253+
*
254+
* @return This instance of {@link CoreTestHelper}.
255+
*/
256+
public CoreTestHelper enableJSpecifyMode() {
257+
this.jSpecifyModeEnabled = true;
258+
return this;
259+
}
260+
244261
/** Starts the test process. */
245262
public void start() {
246263
Path configPath = outDirPath.resolve("config.json");
@@ -430,7 +447,9 @@ public void makeAnnotatorConfigFile(Path configPath) {
430447
outDirPath.resolve(name + "-scanner.xml")))
431448
.collect(Collectors.toList());
432449
builder.checker = NullAway.NAME;
433-
builder.nullableAnnotation = "javax.annotation.Nullable";
450+
// Set annotation name based on JSpecify mode activation.
451+
builder.nullableAnnotation =
452+
jSpecifyModeEnabled ? "org.jspecify.annotations.Nullable" : "javax.annotation.Nullable";
434453
// In tests, we use NullAway @Initializer annotation.
435454
builder.initializerAnnotation = "com.uber.nullaway.annotations.Initializer";
436455
builder.outputDir = outDirPath.toString();
@@ -453,17 +472,19 @@ public void makeAnnotatorConfigFile(Path configPath) {
453472
!getEnvironmentVariable("ANNOTATOR_TEST_DISABLE_PARALLEL_PROCESSING");
454473
if (downstreamDependencyAnalysisActivated) {
455474
builder.buildCommand =
456-
projectBuilder.computeTargetBuildCommandWithLibraryModelLoaderDependency(this.outDirPath);
475+
projectBuilder.computeTargetBuildCommandWithLibraryModelLoaderDependency(
476+
this.outDirPath, jSpecifyModeEnabled);
457477
builder.downstreamBuildCommand =
458478
projectBuilder.computeDownstreamDependencyBuildCommandWithLibraryModelLoaderDependency(
459-
this.outDirPath);
479+
this.outDirPath, jSpecifyModeEnabled);
460480
builder.nullawayLibraryModelLoaderPath =
461481
Utility.getPathToLibraryModel(outDirPath)
462482
.resolve(
463483
Paths.get(
464484
"src", "main", "resources", "edu", "ucr", "cs", "riple", "librarymodel"));
465485
} else {
466-
builder.buildCommand = projectBuilder.computeTargetBuildCommand(this.outDirPath);
486+
builder.buildCommand =
487+
projectBuilder.computeTargetBuildCommand(this.outDirPath, jSpecifyModeEnabled);
467488
}
468489
builder.write(configPath);
469490
}

annotator-core/src/test/java/edu/ucr/cs/riple/core/tools/ProjectBuilder.java

+16-9
Original file line numberDiff line numberDiff line change
@@ -104,17 +104,19 @@ CoreTestHelper exitProjectConstruction() {
104104
* which will be passed through gradle command line arguments.
105105
*
106106
* @param outDirPath Path to serialization output directory,
107+
* @param jSpecifyModeEnabled Flag to enable jSpecify mode when running NullAway.
107108
* @return The command to build the project including the command line arguments, this command can
108109
* * be executed from any directory.
109110
*/
110-
public String computeTargetBuildCommand(Path outDirPath) {
111+
public String computeTargetBuildCommand(Path outDirPath, boolean jSpecifyModeEnabled) {
111112
return String.format(
112-
"%s && ./gradlew %s %s -Plibrary-model-loader-path=%s --rerun-tasks",
113+
"%s && ./gradlew %s %s -Plibrary-model-loader-path=%s -Pjspecify=%s --rerun-tasks",
113114
Utility.changeDirCommand(pathToProject),
114115
computeCompileGradleCommandForModules(modules.subList(0, 1)),
115116
String.join(" ", Utility.computeConfigPathsWithGradleArguments(outDirPath, modules)),
116117
Utility.getPathToLibraryModel(outDirPath)
117-
.resolve(Paths.get("build", "libs", "librarymodel.jar")));
118+
.resolve(Paths.get("build", "libs", "librarymodel.jar")),
119+
jSpecifyModeEnabled);
118120
}
119121

120122
/**
@@ -124,18 +126,21 @@ public String computeTargetBuildCommand(Path outDirPath) {
124126
* line arguments.
125127
*
126128
* @param outDirPath Path to serialization output directory,
129+
* @param jSpecifyModeEnabled Flag to enable jSpecify mode when running NullAway.
127130
* @return The command to build the project including the command line arguments, this command can
128131
* * be executed from any directory.
129132
*/
130-
public String computeTargetBuildCommandWithLibraryModelLoaderDependency(Path outDirPath) {
133+
public String computeTargetBuildCommandWithLibraryModelLoaderDependency(
134+
Path outDirPath, boolean jSpecifyModeEnabled) {
131135
return String.format(
132-
"%s && ./gradlew library-model-loader:jar --rerun-tasks && %s && ./gradlew %s %s -Plibrary-model-loader-path=%s --rerun-tasks",
136+
"%s && ./gradlew library-model-loader:jar --rerun-tasks && %s && ./gradlew %s %s -Plibrary-model-loader-path=%s -Pjspecify=%s --rerun-tasks",
133137
Utility.changeDirCommand(outDirPath.resolve("Annotator")),
134138
Utility.changeDirCommand(pathToProject),
135139
computeCompileGradleCommandForModules(modules.subList(0, 1)),
136140
String.join(" ", Utility.computeConfigPathsWithGradleArguments(outDirPath, modules)),
137141
Utility.getPathToLibraryModel(outDirPath)
138-
.resolve(Paths.get("build", "libs", "librarymodel.jar")));
142+
.resolve(Paths.get("build", "libs", "librarymodel.jar")),
143+
jSpecifyModeEnabled);
139144
}
140145

141146
/**
@@ -145,19 +150,21 @@ public String computeTargetBuildCommandWithLibraryModelLoaderDependency(Path out
145150
* line arguments.
146151
*
147152
* @param outDirPath Path to serialization output directory,
153+
* @param jSpecifyModeEnabled Flag to enable jSpecify mode when running NullAway.
148154
* @return The command to build the project including the command line arguments, this command can
149155
* * be executed from any directory.
150156
*/
151157
public String computeDownstreamDependencyBuildCommandWithLibraryModelLoaderDependency(
152-
Path outDirPath) {
158+
Path outDirPath, boolean jSpecifyModeEnabled) {
153159
return String.format(
154-
"%s && ./gradlew library-model-loader:jar --rerun-tasks && %s && ./gradlew %s %s -Plibrary-model-loader-path=%s --rerun-tasks",
160+
"%s && ./gradlew library-model-loader:jar --rerun-tasks && %s && ./gradlew %s %s -Plibrary-model-loader-path=%s -Pjspecify=%s --rerun-tasks",
155161
Utility.changeDirCommand(outDirPath.resolve("Annotator")),
156162
Utility.changeDirCommand(pathToProject),
157163
computeCompileGradleCommandForModules(modules.subList(1, modules.size())),
158164
String.join(" ", Utility.computeConfigPathsWithGradleArguments(outDirPath, modules)),
159165
Utility.getPathToLibraryModel(outDirPath)
160-
.resolve(Paths.get("build", "libs", "librarymodel.jar")));
166+
.resolve(Paths.get("build", "libs", "librarymodel.jar")),
167+
jSpecifyModeEnabled);
161168
}
162169

163170
/**

annotator-core/src/test/java/edu/ucr/cs/riple/core/tools/TReport.java

+9
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@
2424

2525
package edu.ucr.cs.riple.core.tools;
2626

27+
import com.google.common.collect.ImmutableList;
2728
import com.google.common.collect.ImmutableSet;
2829
import edu.ucr.cs.riple.core.Config;
2930
import edu.ucr.cs.riple.core.Report;
3031
import edu.ucr.cs.riple.core.registries.index.Fix;
3132
import edu.ucr.cs.riple.injector.changes.AddMarkerAnnotation;
33+
import edu.ucr.cs.riple.injector.changes.AddTypeUseMarkerAnnotation;
3234
import edu.ucr.cs.riple.injector.location.Location;
3335
import java.util.Set;
3436
import java.util.stream.Collectors;
@@ -56,6 +58,13 @@ public TReport(Location root, int effect, Tag tag) {
5658
}
5759
}
5860

61+
public TReport(Location root, ImmutableList<ImmutableList<Integer>> index, int effect) {
62+
super(
63+
new Fix(new AddTypeUseMarkerAnnotation(root, "org.jspecify.annotations.Nullable", index)),
64+
effect);
65+
this.expectedValue = effect;
66+
}
67+
5968
public TReport(
6069
Location root,
6170
int effect,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package test;
2+
import org.jspecify.annotations.Nullable;
3+
public class Main {
4+
@Nullable Object[] arr = new Object[1];
5+
void run() {
6+
arr[0] = null;
7+
}
8+
}

annotator-core/src/test/resources/templates/java-17/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ subprojects {
7373
option("NullAway:SerializeFixMetadata", "true")
7474
option("NullAway:FixSerializationConfigPath", project.getProperty(project.name + "-nullaway-config-path"))
7575
option("NullAway:AcknowledgeLibraryModelsOfAnnotatedCode", "true")
76+
option("NullAway:JSpecifyMode", project.getProperty("jspecify"))
7677
option("AnnotatorScanner:ConfigPath", project.getProperty(project.name + "-scanner-config-path"))
7778
}
7879
}

annotator-core/src/test/resources/templates/lombok/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ subprojects {
7575
option("NullAway:SerializeFixMetadata", "true")
7676
option("NullAway:FixSerializationConfigPath", project.getProperty(project.name + "-nullaway-config-path"))
7777
option("NullAway:AcknowledgeLibraryModelsOfAnnotatedCode", "true")
78+
option("NullAway:JSpecifyMode", project.getProperty("jspecify"))
7879
option("AnnotatorScanner:ConfigPath", project.getProperty(project.name + "-scanner-config-path"))
7980
}
8081
}

annotator-core/src/test/resources/templates/nullable-multi-modular/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ subprojects {
7373
option("NullAway:SerializeFixMetadata", "true")
7474
option("NullAway:FixSerializationConfigPath", project.getProperty(project.name + "-nullaway-config-path"))
7575
option("NullAway:AcknowledgeLibraryModelsOfAnnotatedCode", "true")
76+
option("NullAway:JSpecifyMode", project.getProperty("jspecify"))
7677
option("AnnotatorScanner:ConfigPath", project.getProperty(project.name + "-scanner-config-path"))
7778
}
7879
}

0 commit comments

Comments
 (0)