From 13a15de783fc78d8298c4f5448ad2781861abce8 Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Sun, 15 Dec 2024 08:51:26 -0800 Subject: [PATCH] WIP --- .../com/uber/nullaway/CodeAnnotationInfo.java | 19 ++++++++-- .../jspecify/NullMarkednessTests.java | 38 +++++++++++++++++++ 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/nullaway/src/main/java/com/uber/nullaway/CodeAnnotationInfo.java b/nullaway/src/main/java/com/uber/nullaway/CodeAnnotationInfo.java index 4844efc308..58d4db3859 100644 --- a/nullaway/src/main/java/com/uber/nullaway/CodeAnnotationInfo.java +++ b/nullaway/src/main/java/com/uber/nullaway/CodeAnnotationInfo.java @@ -83,9 +83,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 && annotatedAsNullMarkedPackage(enclosingPackage))) { // By default, unknown code is unannotated unless @NullMarked or configured as annotated by // package name return false; @@ -104,6 +102,21 @@ && hasDirectAnnotationWithSimpleName( return true; } + private static final String NONNULLAPI_NAME = "org.springframework.lang.NonNullApi"; + private static final String NONNULLFIELDS_NAME = "org.springframework.lang.NonNullFields"; + + private static boolean annotatedAsNullMarkedPackage(Symbol.PackageSymbol enclosingPackage) { + return hasDirectAnnotationWithSimpleName( + enclosingPackage, NullabilityUtil.NULLMARKED_SIMPLE_NAME) + || (packageHasAnnotation(enclosingPackage, NONNULLAPI_NAME) + && packageHasAnnotation(enclosingPackage, NONNULLFIELDS_NAME)); + } + + private static boolean packageHasAnnotation(Symbol sym, String annotationClass) { + return sym.getAnnotationMirrors().stream() + .anyMatch(a -> a.type.tsym.getQualifiedName().contentEquals(annotationClass)); + } + /** * Check if a symbol comes from generated code. * diff --git a/nullaway/src/test/java/com/uber/nullaway/jspecify/NullMarkednessTests.java b/nullaway/src/test/java/com/uber/nullaway/jspecify/NullMarkednessTests.java index 705aa8180d..0e0ae388e9 100644 --- a/nullaway/src/test/java/com/uber/nullaway/jspecify/NullMarkednessTests.java +++ b/nullaway/src/test/java/com/uber/nullaway/jspecify/NullMarkednessTests.java @@ -43,6 +43,44 @@ public void nullMarkedPackageLevel() { .doTest(); } + @Test + public void springNullMarkedPackage() { + defaultCompilationHelper + .addSourceLines( + "package-info.java", + "@NonNullApi @NonNullFields package com.example.thirdparty;", + "import org.springframework.lang.NonNullApi;", + "import org.springframework.lang.NonNullFields;") + .addSourceLines( + "ThirdPartyAnnotatedUtils.java", + "package com.example.thirdparty;", + "import org.jspecify.annotations.Nullable;", + "public class ThirdPartyAnnotatedUtils {", + " public static String toStringOrDefault(@Nullable Object o1, String s) {", + " if (o1 != null) {", + " return o1.toString();", + " }", + " return s;", + " }", + "}") + .addSourceLines( + "Test.java", + "package com.uber;", + "import com.example.thirdparty.ThirdPartyAnnotatedUtils;", + "public class Test {", + " public static void test(Object o) {", + " // Safe: passing @NonNull on both args", + " ThirdPartyAnnotatedUtils.toStringOrDefault(o, \"default\");", + " // Safe: first arg is @Nullable", + " ThirdPartyAnnotatedUtils.toStringOrDefault(null, \"default\");", + " // Unsafe: @NullMarked means the second arg is @NonNull by default, not @Nullable", + " // BUG: Diagnostic contains: passing @Nullable parameter", + " ThirdPartyAnnotatedUtils.toStringOrDefault(o, null);", + " }", + "}") + .doTest(); + } + @Test public void nullMarkedPackageEnablesChecking() { defaultCompilationHelper