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
4 changes: 4 additions & 0 deletions jdk-recent-unit-tests/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ dependencies {
}
testImplementation deps.test.jsr305Annotations
testModulePath deps.test.cfQual
testModulePath deps.build.jspecify
testModulePath project(":test-java-module")
}

tasks.withType(Test).configureEach { test ->
Expand All @@ -64,4 +66,6 @@ tasks.getByName('test').configure {
onlyIf {
deps.versions.errorProneApi == deps.versions.errorProneLatest
}
// we need this since we don't have an implementation / api dependence on test-java-module
dependsOn ':test-java-module:jar'
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,88 @@ public void testModuleInfo() {
"}")
.doTest();
}

@Test
public void nullmarkedModule() {
defaultCompilationHelper
.addSourceLines(
"module-info.java",
"import org.jspecify.annotations.NullMarked;",
"@NullMarked",
"module com.example.myapp {",
" exports com.example.myapp;",
" requires java.base;",
" requires org.jspecify;",
"}")
.addSourceLines(
"com/example/myapp/Test.java",
"package com.example.myapp;",
"public class Test {",
" public static void main(String[] args) {",
" String s = null;",
" // BUG: Diagnostic contains: dereferenced expression s is @Nullable",
" s.hashCode();",
" }",
"}")
.doTest();
}

@Test
public void nullUnmarkedPackageInNullMarkedModule() {
defaultCompilationHelper
.addSourceLines(
"module-info.java",
"import org.jspecify.annotations.NullMarked;",
"@NullMarked",
"module com.example.myapp {",
" exports com.example.myapp;",
" requires java.base;",
" requires org.jspecify;",
"}")
.addSourceLines(
"com/example/myapp/package-info.java",
"@NullUnmarked package com.example.myapp;",
"import org.jspecify.annotations.NullUnmarked;")
.addSourceLines(
"com/example/myapp/Test.java",
"package com.example.myapp;",
"public class Test {",
" public static void main(String[] args) {",
" String s = null;",
" // no error since @NullUnmarked is in effect",
" s.hashCode();",
" }",
"}")
.doTest();
}

@Test
public void fromBytecode() {
defaultCompilationHelper
.addSourceLines(
"module-info.java",
"import org.jspecify.annotations.NullMarked;",
"@NullMarked",
"module com.example.myapp {",
" requires java.base;",
" requires org.jspecify;",
" requires com.uber.test.java.module;",
"}")
.addSourceLines(
"Test.java",
"import org.jspecify.annotations.NullMarked;",
"import com.example.nullmarked.NullMarkedFromModule;",
"import com.example.nullunmarked.NullUnmarkedFromPackage;",
"@NullMarked",
"class Test {",
" void testPositive() {",
" // BUG: Diagnostic contains: passing @Nullable parameter",
" NullMarkedFromModule.takesNonNull(null);",
" }",
" void testNegative() {",
" NullUnmarkedFromPackage.takesAny(null);",
" }",
"}")
.doTest();
}
}
18 changes: 14 additions & 4 deletions nullaway/src/main/java/com/uber/nullaway/CodeAnnotationInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public static CodeAnnotationInfo instance(Context context) {
/**
* Checks if a symbol comes from an annotated package, as determined by either configuration flags
* (e.g. {@code -XepOpt:NullAway::AnnotatedPackages}) or package level annotations (e.g. {@code
* org.jspecify.annotations.NullMarked}).
* org.jspecify.annotations.NullMarked}) or module level annotations.
*
* @param outermostClassSymbol symbol for class (must be an outermost class)
* @param config NullAway config
Expand All @@ -84,9 +84,7 @@ private static boolean fromAnnotatedPackage(
String className = outermostClassSymbol.getQualifiedName().toString();
Symbol.PackageSymbol enclosingPackage = ASTHelpers.enclosingPackage(outermostClassSymbol);
if (!config.fromExplicitlyAnnotatedPackage(className)
&& !(enclosingPackage != null
&& hasDirectAnnotationWithSimpleName(
enclosingPackage, NullabilityUtil.NULLMARKED_SIMPLE_NAME))) {
&& !(enclosingPackage != null && explicitlyNullMarkedPackageOrModule(enclosingPackage))) {
// By default, unknown code is unannotated unless @NullMarked or configured as annotated by
// package name
return false;
Expand All @@ -105,6 +103,18 @@ && hasDirectAnnotationWithSimpleName(
return true;
}

private static boolean explicitlyNullMarkedPackageOrModule(
Symbol.PackageSymbol enclosingPackage) {
if (hasDirectAnnotationWithSimpleName(
enclosingPackage, NullabilityUtil.NULLMARKED_SIMPLE_NAME)) {
return true;
}
Symbol enclosingModule = enclosingPackage.getEnclosingElement();
return enclosingModule != null
&& hasDirectAnnotationWithSimpleName(
enclosingModule, NullabilityUtil.NULLMARKED_SIMPLE_NAME);
}

/**
* Check if a symbol comes from generated code.
*
Expand Down
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ include ':sample-library-model'
include ':sample'
include ':test-java-lib'
include ':test-java-lib-lombok'
include ':test-java-module'
include ':test-library-models'
include ':jar-infer:android-jarinfer-models-sdk28'
include ':jar-infer:android-jarinfer-models-sdk29'
Expand Down
23 changes: 23 additions & 0 deletions test-java-module/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright (C) 2025. Uber Technologies
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

plugins {
id "java-library"
}

dependencies {
implementation deps.build.jspecify
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.example.nullmarked;

public class NullMarkedFromModule {

public static void takesNonNull(Object o) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.example.nullunmarked;

public class NullUnmarkedFromPackage {

public static void takesAny(Object o) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@NullUnmarked
package com.example.nullunmarked;

import org.jspecify.annotations.NullUnmarked;
10 changes: 10 additions & 0 deletions test-java-module/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import org.jspecify.annotations.NullMarked;

@NullMarked
module com.uber.test.java.module {
requires java.base;
requires static org.jspecify;

exports com.example.nullmarked;
exports com.example.nullunmarked;
}