diff --git a/CHANGELOG.md b/CHANGELOG.md index 18033f9e75..1854e1fda0 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ Changelog ========= +Version 0.10.22 +--------------- +IMPORTANT: The support for JDK 8 is deprecated in this release and will be removed in + an upcoming release. + +* Fix bug with implicit equals() methods in interfaces (#898) +* Fix crash with raw types in overrides in JSpecify mode (#899) +* Docs fix: Update instructions for Android and our sample app (#900) + +Version 0.10.21 +--------------- +IMPORTANT: This release fixes a crash when running against <2.24.0 release of + Error Prone (see #894) introduced in NullAway v0.10.20 and another crash related to + Checker Framework (see #895) introduced in NullAway v0.10.19. + +* Fix backwards-incompatible calls to ASTHelpers.hasDirectAnnotationWithSimpleName (#894) +* Downgrade to Checker Framework 3.40.0 (#895) + +Version 0.10.20 +--------------- +* Fix JSpecify support on JDK 21 (#869) +* Build / CI tooling upgrades for NullAway itself: + - Update to WALA 1.6.3 (#887) + - Update to Error Prone 2.24.1 (#888) + Version 0.10.19 --------------- * Update to Checker Framework 3.41.0 (#873) @@ -110,7 +135,7 @@ Note: This is the first release built with Java 11. In particular, running - Update to Error Prone 2.20.0 (#772) - Add tasks to run JDK 8 tests on JDK 11+ (#778) - Switch to Spotless for formatting Java code (#780) - - Added GCP JMH Benchmark Workflow (#770) + - Added GCP JMH Benchmark Workflow (#770) - Set concurrency for JMH benchmarking workflow (#784) - Disable daemon when running benchmarks (#786) - Update to Gradle 8.2.1 (#781) @@ -194,7 +219,7 @@ Version 0.10.6 * Fix logic for @Nullable annotation on type parameter (#702) * Preserve nullness checks in final fields when propagating nullness into inner contexts (#703) * NullAwayInfer/Annotator data serialization support [experimental] - - Add source offset and path to reported errors in error serialization. (#704) + - Add source offset and path to reported errors in error serialization. (#704) * Build / CI tooling for NullAway itself: - [Jspecify] Update test dep to final JSpecify 0.3.0 release (#700) = Intermediate PRs: 0.3.0-alpha-3 (#692), 0.3-alpha2 (#691) @@ -226,10 +251,10 @@ Version 0.10.3 -------------- * Report an error when casting @Nullable expression to primitive type (#663) * Fix an NPE in the optional emptiness handler (#678) -* Add support for boolean constraints (about nullness) in Contract annotations (#669) +* Add support for boolean constraints (about nullness) in Contract annotations (#669) * Support for specific libraries/APIs: - PreconditionsHandler reflects Guava Preconditions exception types (#668) - - Handle Guava Verify functions (#682) + - Handle Guava Verify functions (#682) * Dependency Updates: - checkerframework 3.26.0 (#671) * Build / CI tooling for NullAway itself: @@ -238,7 +263,7 @@ Version 0.10.3 Version 0.10.2 -------------- -* Make AbstractConfig collection fields explicity Immutable (#601) +* Make AbstractConfig collection fields explicity Immutable (#601) * NullAwayInfer/Annotator data serialization support [experimental] - Fix crash in fixserialization when ClassSymbol.sourcefile is null (#656) @@ -302,7 +327,7 @@ Version 0.9.8 - Model for com.google.api.client.util.Strings.isNullOrEmpty (#605) * Refactoring: - Cleanups to AccessPath representation and implementation (#603) - - Clean-up: Remove unused fix suggestion code. (#615) + - Clean-up: Remove unused fix suggestion code. (#615) * Dependency Updates: - Update to Checker Framework 3.22.2 (#610) * Build / CI tooling for NullAway itself: @@ -354,7 +379,7 @@ Version 0.9.6 * Build / CI tooling for NullAway itself: - Enable parallel builds (#549) (#555) - Add dependence from coveralls task to codeCoverageReport (#552) - - Switch to temurin on CI (#553) + - Switch to temurin on CI (#553) - Separating NullAwayTests into smaller files (#550) - Require braces for all conditionals and loops (#556) - Enable build cache (#562) @@ -364,7 +389,7 @@ Version 0.9.6 - Update CI jobs (#565) - Set epApiVersion for jacoco coverage reporting (#566) - Compile and test against Error Prone 2.11.0 (#567) - - Fix EP version for jacoco coverage step (#571) + - Fix EP version for jacoco coverage step (#571) - Update to latest Google Java Format (#572) Version 0.9.5 @@ -386,7 +411,7 @@ Version 0.9.3 ------------- IMPORTANT: This version introduces EXPERIMENTAL JDK17 support. There is a known crash on lambdas with switch expressions as body - (see #524). Best current workaround is to + (see #524). Best current workaround is to `@SuppressWarnings("NullAway")` on the enclosing method * Improve reporting of multiple parameter errors on a single method call (#503) * Support compile-time constant field args in method Access Paths (#504) @@ -478,13 +503,13 @@ Version 0.8.0 Version 0.7.10 -------------- -* Add Java 8 streams nullness-propagation support (#371) +* Add Java 8 streams nullness-propagation support (#371) * Give line numbers for uninitialized fields when reporting error on an initializer (#380) -* Include outer$inner class name when reporting field init errors (#375) +* Include outer$inner class name when reporting field init errors (#375) * Update to Gradle 6.1.1 (#381) * Add @MonotonicNonNull as lazy initialization annotation. (#383) * Add default library model for CompilationUnitTree.getPackageName() (#384) -* Improve matching of native Map methods (#390) +* Improve matching of native Map methods (#390) - Fixes an IndexOutOfBoundsException checker crash Version 0.7.9 @@ -494,14 +519,14 @@ Version 0.7.9 - WALA to 1.5.4 (#337) - Checker Dataflow to 3.0.0 (#369) * Added OPTIONAL_CONTENT synthetic field to track Optional emptiness (#364) - - With this, `-XepOpt:NullAway:CheckOptionalEmptiness` should be + - With this, `-XepOpt:NullAway:CheckOptionalEmptiness` should be ready for use. * Handle Nullchk operator (#368) Version 0.7.8 ------------- -* Added NullAway.Optional suppression (#359) -* [JarInfer] Ignore non-public classes when inferring annotations. (#360) +* Added NullAway.Optional suppression (#359) +* [JarInfer] Ignore non-public classes when inferring annotations. (#360) Version 0.7.7 ------------- @@ -537,7 +562,7 @@ Version 0.7.4 * Refactor the driver and annotation summary type in JarInfer (#317) * Minor refactor and cleanup in JarInfer-lib (#319) * Different approach for param analysis (#320) -* Fix @NullableDecl support (#324) +* Fix @NullableDecl support (#324) * Treat methods of final classes as final for initialization. (#325) Version 0.7.3 @@ -551,7 +576,7 @@ Version 0.7.3 Version 0.7.2 ------------- -* Install GJF hook using a gradle task, rather than a gradlew hack (#298). +* Install GJF hook using a gradle task, rather than a gradlew hack (#298). * Nullable switch expression support (#300). * Upgrade to Error Prone 2.3.3 (#295). Update Gradle, Error Prone plugin, and Android Gradle Plugin (#294). @@ -572,7 +597,7 @@ Version 0.7.0 Version 0.6.6 --------------- -This only adds a minor library fix supporting Guava's Preconditions.checkNotNull with an error message +This only adds a minor library fix supporting Guava's Preconditions.checkNotNull with an error message argument (#283) Version 0.6.5 @@ -602,16 +627,16 @@ Version 0.6.2 Version 0.6.1 ------------- * Enable excluded class annotations to (mostly) work on inner classes (#239) -* Assertion of not equal to null updates the access path (#240) +* Assertion of not equal to null updates the access path (#240) * Update Gradle examples in README (#244) * Change how jarinfer finds astubx model jars. (#243) * Update to Error Prone 2.3.2 (#242) * Update net.ltgt.errorprone to 0.6, and build updates ((#248) -* Restrictive annotated method overriding (#249) - Note: This can require significant annotation changes if +* Restrictive annotated method overriding (#249) + Note: This can require significant annotation changes if `-XepOpt:NullAway:AcknowledgeRestrictiveAnnotations=true` is set. Not a new minor version, since that option is false by default. -* Fix error on checking the initTree2PrevFieldInit cache. (#252) +* Fix error on checking the initTree2PrevFieldInit cache. (#252) * Add support for renamed android.support packages in models. (#253) Version 0.6.0 @@ -619,12 +644,12 @@ Version 0.6.0 * Add support for marking library parameters as explicitly @Nullable (#228) * De-genericize NullnessStore (#231) * Bump Checker Framework to 2.5.5 (#233) -* Pass nullability info on enclosing locals into dataflow analysis for +* Pass nullability info on enclosing locals into dataflow analysis for lambdas and anonymous / local classes (#235) Version 0.5.6 ------------- -* Add coverage measurement through coveralls. (#224) +* Add coverage measurement through coveralls. (#224) * Fix empty comment added when AutoFixSuppressionComment is not set. (#225) * Make JarInfer generated jars fully deterministic by removing timestamps. (#227) @@ -657,7 +682,7 @@ android-jarinfer-models-sdk28 artifacts Version 0.5.2 ------------- * Fix NPE in Thrift handler on complex receiver expressions (#195) -* Add ExcludedFieldAnnotations unit tests. (#192) +* Add ExcludedFieldAnnotations unit tests. (#192) * Various crash fixes (#196) * Fix @NonNull argument detection in RestrictiveAnnotationHandler. (#198) @@ -683,7 +708,7 @@ Version 0.4.7 Version 0.4.6 ------------- * Fix a couple of Thrift issues (#164) -* Don't report initialization warnings on fields for @ExternalInit classes with +* Don't report initialization warnings on fields for @ExternalInit classes with no initializer methods (#166) Version 0.4.5 @@ -732,7 +757,7 @@ Version 0.3.6 Version 0.3.5 ------------- -* Support for treating `@Generated`-annotated classes as unannotated (#127) +* Support for treating `@Generated`-annotated classes as unannotated (#127) Version 0.3.4 ------------- @@ -804,8 +829,8 @@ Version 0.1.5 ------------- * Add finer grained suppressions and auto-fixes (#31). You can suppress initialization errors specifically now with - `@SuppressWarnings("NullAway.Init")` -* Fix performance issue with lambdas (#29) + `@SuppressWarnings("NullAway.Init")` +* Fix performance issue with lambdas (#29) * Add lambda support to the RxNullabilityPropagator handler. (#12) Version 0.1.4 diff --git a/README.md b/README.md index 13769974d7..e968cdd2db 100644 --- a/README.md +++ b/README.md @@ -58,15 +58,9 @@ Snapshots of the development version are available in [Sonatype's snapshots repo #### Android -The configuration for an Android project is very similar to the Java case, with one key difference: The `com.google.code.findbugs:jsr305:3.0.2` dependency can be removed; you can use the `android.support.annotation.Nullable` annotation from the Android Support library. +Versions 3.0.0 and later of the Gradle Error Prone Plugin [no longer support Android](https://github.com/tbroyer/gradle-errorprone-plugin/releases/tag/v3.0.0). So if you're using a recent version of this plugin, you'll need to add some further configuration to run Error Prone and NullAway. Our [sample app `build.gradle` file](https://github.com/uber/NullAway/blob/master/sample-app/build.gradle) shows one way to do this, but your Android project may require tweaks. Alternately, 2.x versions of the Gradle Error Prone Plugin still support Android and may still work with your project. -```gradle -dependencies { - errorprone "com.uber.nullaway:nullaway:" - errorprone "com.google.errorprone:error_prone_core:" -} -``` -For a more complete example see our [sample app](https://github.com/uber/NullAway/blob/master/sample-app/). (The sample app's [`build.gradle`](https://github.com/uber/NullAway/blob/master/sample-app/) is not suitable for direct copy-pasting, as some configuration is inherited from the top-level `build.gradle`.) +Beyond that, compared to the Java configuration, the `com.google.code.findbugs:jsr305:3.0.2` dependency can be removed; you can use the `android.support.annotation.Nullable` annotation from the Android Support library instead. #### Annotation Processors / Generated Code diff --git a/buildSrc/src/main/groovy/nullaway.java-test-conventions.gradle b/buildSrc/src/main/groovy/nullaway.java-test-conventions.gradle index 07f8f2690e..6a0e6415fd 100644 --- a/buildSrc/src/main/groovy/nullaway.java-test-conventions.gradle +++ b/buildSrc/src/main/groovy/nullaway.java-test-conventions.gradle @@ -44,22 +44,6 @@ configurations.create('transitiveSourcesElements') { } } -// Share the coverage data to be aggregated for the whole product -configurations.create('coverageDataElements') { - visible = false - canBeResolved = false - canBeConsumed = true - extendsFrom(configurations.implementation) - attributes { - attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME)) - attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.DOCUMENTATION)) - attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType, 'jacoco-coverage-data')) - } - // This will cause the test task to run if the coverage data is requested by the aggregation task - outgoing.artifact(tasks.named("test").map { task -> - task.extensions.getByType(JacocoTaskExtension).destinationFile - }) -} test { maxHeapSize = "1024m" @@ -118,3 +102,23 @@ def testJdk21 = tasks.register("testJdk21", Test) { tasks.named('check').configure { dependsOn testJdk21 } + + +// Share the coverage data to be aggregated for the whole product +configurations.create('coverageDataElements') { + visible = false + canBeResolved = false + canBeConsumed = true + extendsFrom(configurations.implementation) + attributes { + attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME)) + attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.DOCUMENTATION)) + attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType, 'jacoco-coverage-data')) + } + // This will cause the test tasks to run if the coverage data is requested by the aggregation task + tasks.withType(Test).forEach {task -> + // tasks.named(task.name) is a hack to introduce the right task dependence in Gradle. We will fix this + // correctly when addressing https://github.com/uber/NullAway/issues/871 + outgoing.artifact(tasks.named(task.name).map { t -> t.extensions.getByType(JacocoTaskExtension).destinationFile }) + } +} diff --git a/gradle.properties b/gradle.properties index 39843d9e52..607131e58f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,7 +12,7 @@ org.gradle.caching=true org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m GROUP=com.uber.nullaway -VERSION_NAME=0.10.20-SNAPSHOT +VERSION_NAME=0.10.23-SNAPSHOT POM_DESCRIPTION=A fast annotation-based null checker for Java diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 01bbe70a62..bada5eedf1 100755 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -40,7 +40,7 @@ if (project.hasProperty("epApiVersion")) { def versions = [ asm : "9.3", - checkerFramework : "3.41.0", + checkerFramework : "3.40.0", // for comparisons in other parts of the build errorProneLatest : latestErrorProneVersion, // The version of Error Prone used to check NullAway's code. @@ -67,10 +67,12 @@ def build = [ asm : "org.ow2.asm:asm:${versions.asm}", asmTree : "org.ow2.asm:asm-tree:${versions.asm}", errorProneCheckApi : "com.google.errorprone:error_prone_check_api:${versions.errorProneApi}", + errorProneCheckApiOld : "com.google.errorprone:error_prone_check_api:${oldestErrorProneVersion}", errorProneCore : "com.google.errorprone:error_prone_core:${versions.errorProne}", errorProneCoreForApi : "com.google.errorprone:error_prone_core:${versions.errorProneApi}", errorProneJavac : "com.google.errorprone:javac:9+181-r4173-1", errorProneTestHelpers : "com.google.errorprone:error_prone_test_helpers:${versions.errorProneApi}", + errorProneTestHelpersOld: "com.google.errorprone:error_prone_test_helpers:${oldestErrorProneVersion}", checkerDataflow : "org.checkerframework:dataflow-nullaway:${versions.checkerFramework}", guava : "com.google.guava:guava:30.1-jre", javaxValidation : "javax.validation:validation-api:2.0.1.Final", diff --git a/nullaway/build.gradle b/nullaway/build.gradle index 90ac062418..15327aed88 100644 --- a/nullaway/build.gradle +++ b/nullaway/build.gradle @@ -21,7 +21,8 @@ plugins { } configurations { - nullawayJar + // A configuration holding the jars for the oldest supported version of Error Prone, to use with tests + errorProneOldest } dependencies { @@ -64,6 +65,14 @@ dependencies { testImplementation deps.test.mockito testImplementation deps.test.javaxAnnotationApi testImplementation deps.test.assertJ + // This is for a test exposing a CFG construction failure in the Checker Framework. We can probably remove it once + // the issue is fixed upstream and we update. See https://github.com/typetools/checker-framework/issues/6396. + testImplementation 'org.apache.spark:spark-sql_2.12:3.3.2' + + errorProneOldest deps.build.errorProneCheckApiOld + errorProneOldest(deps.build.errorProneTestHelpersOld) { + exclude group: "junit", module: "junit" + } } javadoc { @@ -93,12 +102,10 @@ apply plugin: 'com.vanniktech.maven.publish' // } // Create a task to test on JDK 8 +// NOTE: even when we drop JDK 8 support, we will still need a test task similar to this one for testing building +// against a recent JDK and Error Prone version but then running on the oldest supported JDK and Error Prone version, +// to check for binary compatibility issues. def jdk8Test = tasks.register("testJdk8", Test) { - onlyIf { - // Only if we are using a version of Error Prone compatible with JDK 8 - deps.versions.errorProneApi == "2.10.0" - } - javaLauncher = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(8) } @@ -108,7 +115,11 @@ def jdk8Test = tasks.register("testJdk8", Test) { // Copy inputs from normal Test task. def testTask = tasks.getByName("test") - classpath = testTask.classpath + // A bit of a hack: we add the dependencies of the oldest supported Error Prone version to the _beginning_ of the + // classpath, so that they are used instead of the latest version. This exercises the scenario of building + // NullAway against the latest supported Error Prone version but then running on the oldest supported version. + classpath = configurations.errorProneOldest + testTask.classpath + testClassesDirs = testTask.testClassesDirs jvmArgs "-Xbootclasspath/p:${configurations.errorproneJavac.asPath}" filter { @@ -124,14 +135,6 @@ tasks.named('check').configure { dependsOn(jdk8Test) } -tasks.named('testJdk21', Test).configure { - filter { - // JSpecify Generics tests do not yet pass on JDK 21 - // See https://github.com/uber/NullAway/issues/827 - excludeTestsMatching "com.uber.nullaway.NullAwayJSpecifyGenericsTests" - } -} - // Create a task to build NullAway with NullAway checking enabled tasks.register('buildWithNullAway', JavaCompile) { onlyIf { diff --git a/nullaway/src/main/java/com/uber/nullaway/ASTHelpersBackports.java b/nullaway/src/main/java/com/uber/nullaway/ASTHelpersBackports.java index 06a91f99c1..47dc99f0c7 100644 --- a/nullaway/src/main/java/com/uber/nullaway/ASTHelpersBackports.java +++ b/nullaway/src/main/java/com/uber/nullaway/ASTHelpersBackports.java @@ -1,12 +1,15 @@ package com.uber.nullaway; +import com.google.errorprone.util.ASTHelpers; import com.sun.tools.javac.code.Symbol; import java.util.List; /** * Methods backported from {@link com.google.errorprone.util.ASTHelpers} since we do not yet require * a recent-enough Error Prone version. The methods should be removed once we bump our minimum Error - * Prone version accordingly. + * Prone version accordingly. We also include methods where new overloads have been added in recent + * Error Prone versions, to ensure binary compatibility when compiling against a new version but + * running on an old version. */ public class ASTHelpersBackports { @@ -36,4 +39,19 @@ public static boolean isStatic(Symbol symbol) { public static List getEnclosedElements(Symbol symbol) { return symbol.getEnclosedElements(); } + + /** + * A wrapper for {@link ASTHelpers#hasDirectAnnotationWithSimpleName(Symbol, String)} to avoid + * binary compatibility issues with new overloads in recent Error Prone versions. NullAway code + * should only use this method and not call the corresponding ASTHelpers methods directly. + * + *

TODO: delete this method and switch to ASTHelpers once we can require Error Prone 2.24.0 + * + * @param sym the symbol + * @param simpleName the simple name + * @return {@code true} iff the symbol has a direct annotation with the given simple name + */ + public static boolean hasDirectAnnotationWithSimpleName(Symbol sym, String simpleName) { + return ASTHelpers.hasDirectAnnotationWithSimpleName(sym, simpleName); + } } diff --git a/nullaway/src/main/java/com/uber/nullaway/CodeAnnotationInfo.java b/nullaway/src/main/java/com/uber/nullaway/CodeAnnotationInfo.java index 013e8f4efe..37fcc0386f 100644 --- a/nullaway/src/main/java/com/uber/nullaway/CodeAnnotationInfo.java +++ b/nullaway/src/main/java/com/uber/nullaway/CodeAnnotationInfo.java @@ -22,6 +22,7 @@ package com.uber.nullaway; +import static com.uber.nullaway.ASTHelpersBackports.hasDirectAnnotationWithSimpleName; import static com.uber.nullaway.NullabilityUtil.castToNonNull; import com.google.common.base.Preconditions; @@ -81,7 +82,7 @@ private static boolean fromAnnotatedPackage( Symbol.PackageSymbol enclosingPackage = ASTHelpers.enclosingPackage(outermostClassSymbol); if (!config.fromExplicitlyAnnotatedPackage(className) && !(enclosingPackage != null - && ASTHelpers.hasDirectAnnotationWithSimpleName( + && hasDirectAnnotationWithSimpleName( enclosingPackage, NullabilityUtil.NULLMARKED_SIMPLE_NAME))) { // By default, unknown code is unannotated unless @NullMarked or configured as annotated by // package name @@ -89,7 +90,7 @@ private static boolean fromAnnotatedPackage( } if (config.fromExplicitlyUnannotatedPackage(className) || (enclosingPackage != null - && ASTHelpers.hasDirectAnnotationWithSimpleName( + && hasDirectAnnotationWithSimpleName( enclosingPackage, NullabilityUtil.NULLUNMARKED_SIMPLE_NAME))) { // Any code explicitly marked as unannotated in our configuration is unannotated, no matter // what. Similarly, any package annotated as @NullUnmarked is unannotated, even if @@ -120,7 +121,7 @@ public boolean isGenerated(Symbol symbol, Config config) { return false; } Symbol.ClassSymbol outermostClassSymbol = get(classSymbol, config).outermostClassSymbol; - return ASTHelpers.hasDirectAnnotationWithSimpleName(outermostClassSymbol, "Generated"); + return hasDirectAnnotationWithSimpleName(outermostClassSymbol, "Generated"); } /** @@ -216,10 +217,10 @@ private ClassCacheRecord get(Symbol.ClassSymbol classSymbol, Config config) { if (enclosingMethod != null) { isAnnotated = recordForEnclosing.isMethodNullnessAnnotated(enclosingMethod); } - if (ASTHelpers.hasDirectAnnotationWithSimpleName( + if (hasDirectAnnotationWithSimpleName( classSymbol, NullabilityUtil.NULLUNMARKED_SIMPLE_NAME)) { isAnnotated = false; - } else if (ASTHelpers.hasDirectAnnotationWithSimpleName( + } else if (hasDirectAnnotationWithSimpleName( classSymbol, NullabilityUtil.NULLMARKED_SIMPLE_NAME)) { isAnnotated = true; } @@ -244,7 +245,7 @@ private boolean shouldTreatAsUnannotated(Symbol.ClassSymbol classSymbol, Config // Generated code is or isn't excluded, depending on configuration // Note: In the future, we might want finer grain controls to distinguish code that is // generated with nullability info and without. - if (ASTHelpers.hasDirectAnnotationWithSimpleName(classSymbol, "Generated")) { + if (hasDirectAnnotationWithSimpleName(classSymbol, "Generated")) { return true; } ImmutableSet generatedCodeAnnotations = config.getGeneratedCodeAnnotations(); @@ -259,13 +260,11 @@ private boolean shouldTreatAsUnannotated(Symbol.ClassSymbol classSymbol, Config private boolean isAnnotatedTopLevelClass(Symbol.ClassSymbol classSymbol, Config config) { // First, check for an explicitly @NullUnmarked top level class - if (ASTHelpers.hasDirectAnnotationWithSimpleName( - classSymbol, NullabilityUtil.NULLUNMARKED_SIMPLE_NAME)) { + if (hasDirectAnnotationWithSimpleName(classSymbol, NullabilityUtil.NULLUNMARKED_SIMPLE_NAME)) { return false; } // Then, check if the class has a @NullMarked annotation or comes from an annotated package - if ((ASTHelpers.hasDirectAnnotationWithSimpleName( - classSymbol, NullabilityUtil.NULLMARKED_SIMPLE_NAME) + if ((hasDirectAnnotationWithSimpleName(classSymbol, NullabilityUtil.NULLMARKED_SIMPLE_NAME) || fromAnnotatedPackage(classSymbol, config))) { // make sure it's not explicitly configured as unannotated return !shouldTreatAsUnannotated(classSymbol, config); @@ -295,14 +294,12 @@ public boolean isMethodNullnessAnnotated(Symbol.MethodSymbol methodSymbol) { return methodNullnessCache.computeIfAbsent( methodSymbol, m -> { - if (ASTHelpers.hasDirectAnnotationWithSimpleName( - m, NullabilityUtil.NULLUNMARKED_SIMPLE_NAME)) { + if (hasDirectAnnotationWithSimpleName(m, NullabilityUtil.NULLUNMARKED_SIMPLE_NAME)) { return false; } else if (this.isNullnessAnnotated) { return true; } else { - return ASTHelpers.hasDirectAnnotationWithSimpleName( - m, NullabilityUtil.NULLMARKED_SIMPLE_NAME); + return hasDirectAnnotationWithSimpleName(m, NullabilityUtil.NULLMARKED_SIMPLE_NAME); } }); } diff --git a/nullaway/src/main/java/com/uber/nullaway/NullAway.java b/nullaway/src/main/java/com/uber/nullaway/NullAway.java index ba53be91b0..09b24d2c6d 100644 --- a/nullaway/src/main/java/com/uber/nullaway/NullAway.java +++ b/nullaway/src/main/java/com/uber/nullaway/NullAway.java @@ -28,6 +28,7 @@ import static com.sun.source.tree.Tree.Kind.OTHER; import static com.sun.source.tree.Tree.Kind.PARENTHESIZED; import static com.sun.source.tree.Tree.Kind.TYPE_CAST; +import static com.uber.nullaway.ASTHelpersBackports.hasDirectAnnotationWithSimpleName; import static com.uber.nullaway.ASTHelpersBackports.isStatic; import static com.uber.nullaway.ErrorBuilder.errMsgForInitializer; import static com.uber.nullaway.NullabilityUtil.castToNonNull; @@ -35,6 +36,7 @@ import com.google.auto.service.AutoService; import com.google.auto.value.AutoValue; import com.google.common.base.Preconditions; +import com.google.common.base.Verify; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableSet; @@ -400,16 +402,31 @@ public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState if (!withinAnnotatedCode(state)) { return Description.NO_MATCH; } - final Symbol.MethodSymbol methodSymbol = ASTHelpers.getSymbol(tree); - if (methodSymbol == null) { - throw new RuntimeException("not expecting unresolved method here"); - } + Symbol.MethodSymbol methodSymbol = getSymbolForMethodInvocation(tree, state); handler.onMatchMethodInvocation(this, tree, state, methodSymbol); // assuming this list does not include the receiver List actualParams = tree.getArguments(); return handleInvocation(tree, state, methodSymbol, actualParams); } + private static Symbol.MethodSymbol getSymbolForMethodInvocation( + MethodInvocationTree tree, VisitorState state) { + Symbol.MethodSymbol methodSymbol = ASTHelpers.getSymbol(tree); + Verify.verify(methodSymbol != null, "not expecting unresolved method here"); + // For interface methods, if the method is an implicit method corresponding to a method from + // java.lang.Object, use the symbol for the java.lang.Object method instead. We do this to + // properly treat the method as unannotated, which is particularly important for equals() + // methods. This is an adaptation to a change in JDK 18; see + // https://bugs.openjdk.org/browse/JDK-8272564 + if (methodSymbol.owner.isInterface()) { + Symbol.MethodSymbol baseSymbol = (Symbol.MethodSymbol) methodSymbol.baseSymbol(); + if (baseSymbol != methodSymbol && baseSymbol.owner == state.getSymtab().objectType.tsym) { + methodSymbol = baseSymbol; + } + } + return methodSymbol; + } + @Override public Description matchNewClass(NewClassTree tree, VisitorState state) { if (!withinAnnotatedCode(state)) { @@ -478,7 +495,7 @@ public Description matchAssignment(AssignmentTree tree, VisitorState state) { doUnboxingCheck(state, tree.getExpression()); } // generics check - if (lhsType != null && lhsType.getTypeArguments().length() > 0) { + if (lhsType != null && lhsType.getTypeArguments().length() > 0 && config.isJSpecifyMode()) { GenericsChecks.checkTypeParameterNullnessForAssignability(tree, this, state); } @@ -580,20 +597,20 @@ private void checkForMethodNullMarkedness(MethodTree tree, VisitorState state) { Symbol.MethodSymbol methodSymbol = ASTHelpers.getSymbol(tree); switch (nullMarkingForTopLevelClass) { case FULLY_MARKED: - if (ASTHelpers.hasDirectAnnotationWithSimpleName( + if (hasDirectAnnotationWithSimpleName( methodSymbol, NullabilityUtil.NULLUNMARKED_SIMPLE_NAME)) { nullMarkingForTopLevelClass = NullMarking.PARTIALLY_MARKED; } break; case FULLY_UNMARKED: - if (ASTHelpers.hasDirectAnnotationWithSimpleName( + if (hasDirectAnnotationWithSimpleName( methodSymbol, NullabilityUtil.NULLMARKED_SIMPLE_NAME)) { nullMarkingForTopLevelClass = NullMarking.PARTIALLY_MARKED; markedMethodInUnmarkedContext = true; } break; case PARTIALLY_MARKED: - if (ASTHelpers.hasDirectAnnotationWithSimpleName( + if (hasDirectAnnotationWithSimpleName( methodSymbol, NullabilityUtil.NULLMARKED_SIMPLE_NAME)) { // We still care here if this is a transition between @NullUnmarked and @NullMarked code, // within partially marked code, see checks below for markedMethodInUnmarkedContext. @@ -697,7 +714,9 @@ public Description matchParameterizedType(ParameterizedTypeTree tree, VisitorSta if (!withinAnnotatedCode(state)) { return Description.NO_MATCH; } - GenericsChecks.checkInstantiationForParameterizedTypedTree(tree, state, this, config); + if (config.isJSpecifyMode()) { + GenericsChecks.checkInstantiationForParameterizedTypedTree(tree, state, this, config); + } return Description.NO_MATCH; } @@ -1426,7 +1445,7 @@ public Description matchVariable(VariableTree tree, VisitorState state) { return Description.NO_MATCH; } VarSymbol symbol = ASTHelpers.getSymbol(tree); - if (tree.getInitializer() != null) { + if (tree.getInitializer() != null && config.isJSpecifyMode()) { GenericsChecks.checkTypeParameterNullnessForAssignability(tree, this, state); } @@ -1467,10 +1486,10 @@ public Description matchVariable(VariableTree tree, VisitorState state) { */ private boolean classAnnotationIntroducesPartialMarking(Symbol.ClassSymbol classSymbol) { return (nullMarkingForTopLevelClass == NullMarking.FULLY_UNMARKED - && ASTHelpers.hasDirectAnnotationWithSimpleName( + && hasDirectAnnotationWithSimpleName( classSymbol, NullabilityUtil.NULLMARKED_SIMPLE_NAME)) || (nullMarkingForTopLevelClass == NullMarking.FULLY_MARKED - && ASTHelpers.hasDirectAnnotationWithSimpleName( + && hasDirectAnnotationWithSimpleName( classSymbol, NullabilityUtil.NULLUNMARKED_SIMPLE_NAME)); } @@ -1579,7 +1598,9 @@ public Description matchUnary(UnaryTree tree, VisitorState state) { public Description matchConditionalExpression( ConditionalExpressionTree tree, VisitorState state) { if (withinAnnotatedCode(state)) { - GenericsChecks.checkTypeParameterNullnessForConditionalExpression(tree, this, state); + if (config.isJSpecifyMode()) { + GenericsChecks.checkTypeParameterNullnessForConditionalExpression(tree, this, state); + } doUnboxingCheck(state, tree.getCondition()); } return Description.NO_MATCH; @@ -1710,8 +1731,10 @@ private Description handleInvocation( : Nullness.NONNULL); } } - GenericsChecks.compareGenericTypeParameterNullabilityForCall( - formalParams, actualParams, methodSymbol.isVarArgs(), this, state); + if (config.isJSpecifyMode()) { + GenericsChecks.compareGenericTypeParameterNullabilityForCall( + formalParams, actualParams, methodSymbol.isVarArgs(), this, state); + } } // Allow handlers to override the list of non-null argument positions @@ -2236,7 +2259,7 @@ private boolean isConstructor(MethodTree methodTree) { } private boolean isInitializerMethod(VisitorState state, Symbol.MethodSymbol symbol) { - if (ASTHelpers.hasDirectAnnotationWithSimpleName(symbol, "Initializer") + if (hasDirectAnnotationWithSimpleName(symbol, "Initializer") || config.isKnownInitializerMethod(symbol)) { return true; } diff --git a/nullaway/src/main/java/com/uber/nullaway/fixserialization/FixSerializationConfig.java b/nullaway/src/main/java/com/uber/nullaway/fixserialization/FixSerializationConfig.java index 5ffca638ce..769c9eac0a 100644 --- a/nullaway/src/main/java/com/uber/nullaway/fixserialization/FixSerializationConfig.java +++ b/nullaway/src/main/java/com/uber/nullaway/fixserialization/FixSerializationConfig.java @@ -100,7 +100,7 @@ public FixSerializationConfig( public FixSerializationConfig(String configFilePath, int serializationVersion) { Document document; try { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilderFactory factory = XMLUtil.safeDocumentBuilderFactory(); DocumentBuilder builder = factory.newDocumentBuilder(); document = builder.parse(Files.newInputStream(Paths.get(configFilePath))); document.normalize(); diff --git a/nullaway/src/main/java/com/uber/nullaway/fixserialization/XMLUtil.java b/nullaway/src/main/java/com/uber/nullaway/fixserialization/XMLUtil.java index 91d45cd378..9c0154a812 100644 --- a/nullaway/src/main/java/com/uber/nullaway/fixserialization/XMLUtil.java +++ b/nullaway/src/main/java/com/uber/nullaway/fixserialization/XMLUtil.java @@ -24,6 +24,7 @@ import java.io.File; import javax.annotation.Nullable; +import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -94,6 +95,28 @@ public static DefaultXMLValueProvider getValueFromTag( return new DefaultXMLValueProvider<>(null, klass); } + /** + * Returns a secure DocumentBuilderFactory object for parsing XML documents. By setting a series + * of security features, it helps prevent common XML injection attacks and enhances the security + * of XML document parsing. + * + * @return A secure DocumentBuilderFactory object + */ + public static DocumentBuilderFactory safeDocumentBuilderFactory() { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + try { + dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); + dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + dbf.setFeature("http://apache.org/xml/features/dom/create-entity-ref-nodes", false); + dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + } catch (ParserConfigurationException e) { + throw new RuntimeException("Error happened in build doc.", e); + } + return dbf; + } + /** * Writes the {@link FixSerializationConfig} in {@code XML} format. * @@ -101,7 +124,7 @@ public static DefaultXMLValueProvider getValueFromTag( * @param path Path to write the config at. */ public static void writeInXMLFormat(FixSerializationConfig config, String path) { - DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilderFactory docFactory = safeDocumentBuilderFactory(); try { DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); Document doc = docBuilder.newDocument(); diff --git a/nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java b/nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java index 77f185e1f4..4754ed92a0 100644 --- a/nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java +++ b/nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java @@ -3,7 +3,6 @@ import static com.google.common.base.Verify.verify; import static com.uber.nullaway.NullabilityUtil.castToNonNull; -import com.google.common.base.Preconditions; import com.google.errorprone.VisitorState; import com.google.errorprone.suppliers.Supplier; import com.google.errorprone.suppliers.Suppliers; @@ -772,7 +771,7 @@ private static void checkTypeParameterNullnessForOverridingMethodParameterType( List overriddenMethodParameterTypes = overriddenMethodType.getParameterTypes(); // TODO handle varargs; they are not handled for now for (int i = 0; i < methodParameters.size(); i++) { - Type overridingMethodParameterType = ASTHelpers.getType(methodParameters.get(i)); + Type overridingMethodParameterType = getTreeType(methodParameters.get(i), state); Type overriddenMethodParameterType = overriddenMethodParameterTypes.get(i); if (overriddenMethodParameterType != null && overridingMethodParameterType != null) { if (!compareNullabilityAnnotations( @@ -801,11 +800,10 @@ private static void checkTypeParameterNullnessForOverridingMethodParameterType( private static void checkTypeParameterNullnessForOverridingMethodReturnType( MethodTree tree, Type overriddenMethodType, NullAway analysis, VisitorState state) { Type overriddenMethodReturnType = overriddenMethodType.getReturnType(); - Type overridingMethodReturnType = ASTHelpers.getType(tree.getReturnType()); - if (overriddenMethodReturnType == null) { + Type overridingMethodReturnType = getTreeType(tree.getReturnType(), state); + if (overriddenMethodReturnType == null || overridingMethodReturnType == null) { return; } - Preconditions.checkArgument(overridingMethodReturnType != null); if (!compareNullabilityAnnotations( overriddenMethodReturnType, overridingMethodReturnType, state)) { reportInvalidOverridingMethodReturnTypeError( diff --git a/nullaway/src/main/java/com/uber/nullaway/generics/PreservedAnnotationTreeVisitor.java b/nullaway/src/main/java/com/uber/nullaway/generics/PreservedAnnotationTreeVisitor.java index 2cde64f0ac..822fcf193c 100644 --- a/nullaway/src/main/java/com/uber/nullaway/generics/PreservedAnnotationTreeVisitor.java +++ b/nullaway/src/main/java/com/uber/nullaway/generics/PreservedAnnotationTreeVisitor.java @@ -14,6 +14,10 @@ import com.sun.tools.javac.code.Attribute; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.TypeMetadata; +import com.sun.tools.javac.util.ListBuffer; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -74,10 +78,10 @@ public Type visitParameterizedType(ParameterizedTypeTree tree, Void p) { new Attribute.TypeCompound( nullableType, com.sun.tools.javac.util.List.nil(), null))) : com.sun.tools.javac.util.List.nil(); - TypeMetadata typeMetadata = - new TypeMetadata(new TypeMetadata.Annotations(nullableAnnotationCompound)); + TypeMetadata typeMetadata = TYPE_METADATA_BUILDER.create(nullableAnnotationCompound); Type currentTypeArgType = curTypeArg.accept(this, null); - Type newTypeArgType = currentTypeArgType.cloneWithMetadata(typeMetadata); + Type newTypeArgType = + TYPE_METADATA_BUILDER.cloneTypeWithMetadata(currentTypeArgType, typeMetadata); newTypeArgs.add(newTypeArgType); } Type.ClassType finalType = @@ -91,4 +95,145 @@ public Type visitParameterizedType(ParameterizedTypeTree tree, Void p) { protected Type defaultAction(Tree node, Void unused) { return castToNonNull(ASTHelpers.getType(node)); } + + /** + * Abstracts over the different APIs for building {@link TypeMetadata} objects in different JDK + * versions. + */ + private interface TypeMetadataBuilder { + TypeMetadata create(com.sun.tools.javac.util.List attrs); + + Type cloneTypeWithMetadata(Type typeToBeCloned, TypeMetadata metaData); + } + + /** + * Provides implementations for methods under TypeMetadataBuilder compatible with JDK 17 and + * earlier versions. + */ + private static class JDK17AndEarlierTypeMetadataBuilder implements TypeMetadataBuilder { + + @Override + public TypeMetadata create(com.sun.tools.javac.util.List attrs) { + return new TypeMetadata(new TypeMetadata.Annotations(attrs)); + } + + /** + * Clones the given type with the specified Metadata for getting the right nullability + * annotations. + * + * @param typeToBeCloned The Type we want to clone with the required Nullability Metadata + * @param metadata The required Nullability metadata which is lost from the type + * @return Type after it has been cloned by applying the required Nullability metadata + */ + @Override + public Type cloneTypeWithMetadata(Type typeToBeCloned, TypeMetadata metadata) { + return typeToBeCloned.cloneWithMetadata(metadata); + } + } + + /** + * Provides implementations for methods under TypeMetadataBuilder compatible with the updates made + * to the library methods for Jdk 21. The implementation calls the logic specific to JDK 21 + * indirectly using MethodHandles since we still need the code to compile on earlier versions. + */ + private static class JDK21TypeMetadataBuilder implements TypeMetadataBuilder { + + private static final MethodHandle typeMetadataHandle = createHandle(); + private static final MethodHandle addMetadataHandle = + createVirtualMethodHandle(Type.class, TypeMetadata.class, Type.class, "addMetadata"); + private static final MethodHandle dropMetadataHandle = + createVirtualMethodHandle(Type.class, Class.class, Type.class, "dropMetadata"); + + private static MethodHandle createHandle() { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + MethodType mt = MethodType.methodType(void.class, com.sun.tools.javac.util.ListBuffer.class); + try { + return lookup.findConstructor(TypeMetadata.Annotations.class, mt); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + /** + * Used to get a MethodHandle for a virtual method from the specified class + * + * @param retTypeClass Class to indicate the return type of the desired method + * @param paramTypeClass Class to indicate the parameter type of the desired method + * @param refClass Class within which the desired method is contained + * @param methodName Name of the desired method + * @return The appropriate MethodHandle for the virtual method + */ + private static MethodHandle createVirtualMethodHandle( + Class retTypeClass, Class paramTypeClass, Class refClass, String methodName) { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + MethodType mt = MethodType.methodType(retTypeClass, paramTypeClass); + try { + return lookup.findVirtual(refClass, methodName, mt); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + @Override + public TypeMetadata create(com.sun.tools.javac.util.List attrs) { + ListBuffer b = new ListBuffer<>(); + b.appendList(attrs); + try { + return (TypeMetadata) typeMetadataHandle.invoke(b); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + /** + * Calls dropMetadata and addMetadata using MethodHandles for JDK 21, which removed the previous + * cloneWithMetadata method. + * + * @param typeToBeCloned The Type we want to clone with the required Nullability metadata + * @param metadata The required Nullability metadata + * @return Cloned Type with the necessary Nullability metadata + */ + @Override + public Type cloneTypeWithMetadata(Type typeToBeCloned, TypeMetadata metadata) { + try { + // In JDK 21 addMetadata works if there is no metadata associated with the type, so we + // create a copy without the existing metadata first and then add it + Type clonedTypeWithoutMetadata = + (Type) dropMetadataHandle.invoke(typeToBeCloned, metadata.getClass()); + return (Type) addMetadataHandle.invoke(clonedTypeWithoutMetadata, metadata); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + } + + /** The TypeMetadataBuilder to be used for the current JDK version. */ + private static final TypeMetadataBuilder TYPE_METADATA_BUILDER = + getVersion() >= 21 + ? new JDK21TypeMetadataBuilder() + : new JDK17AndEarlierTypeMetadataBuilder(); + + /** + * Utility method to get the current JDK version, that works on Java 8 and above. + * + *

TODO remove this method once we drop support for Java 8 + * + * @return the current JDK version + */ + private static int getVersion() { + String version = System.getProperty("java.version"); + if (version.startsWith("1.")) { + version = version.substring(2, 3); + } else { + int dot = version.indexOf("."); + if (dot != -1) { + version = version.substring(0, dot); + } + } + return Integer.parseInt(version); + } } diff --git a/nullaway/src/test/java/com/uber/nullaway/NullAwayCoreTests.java b/nullaway/src/test/java/com/uber/nullaway/NullAwayCoreTests.java index f8f3ed622c..3649de9564 100644 --- a/nullaway/src/test/java/com/uber/nullaway/NullAwayCoreTests.java +++ b/nullaway/src/test/java/com/uber/nullaway/NullAwayCoreTests.java @@ -960,4 +960,45 @@ public void primitiveCastsRememberNullChecks() { "}") .doTest(); } + + /** + * This test exposes a failure in CFG construction in Checker Framework 3.41.0 and above. Once a + * fix for this issue makes it to a Checker Framework release, we can probably remove this test. + * See https://github.com/typetools/checker-framework/issues/6396. + */ + @Test + public void cfgConstructionSymbolCompletionFailure() { + defaultCompilationHelper + .addSourceLines( + "Test.java", + "package com.uber;", + "import org.apache.spark.sql.SparkSession;", + "class Test {", + " static class X {", + " X(SparkSession session) {}", + " }", + " X run() {", + " try (SparkSession session = SparkSession.builder().getOrCreate()) {", + " return new X(session);", + " }", + " }", + "}") + .doTest(); + } + + @Test + public void testDefaultEqualsInInterfaceTakesNullable() { + defaultCompilationHelper + .addSourceLines( + "Test.java", + "package com.uber;", + "import javax.annotation.Nullable;", + "class Test {", + " public interface AnInterface {}", + " public static boolean foo(AnInterface a, @Nullable AnInterface b) {", + " return a.equals(b);", + " }", + "}") + .doTest(); + } } diff --git a/nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java b/nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java index 8e3452e96f..d146442e17 100644 --- a/nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java +++ b/nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java @@ -1615,6 +1615,31 @@ public void testForNullTypeRhsTypeForArrayType() { .doTest(); } + @Test + public void overrideWithRawType() { + makeHelper() + .addSourceLines( + "Test.java", + "package com.uber;", + "import org.jspecify.annotations.Nullable;", + "class Test {", + " interface Foo {}", + " interface Bar {", + " void add(Foo foo);", + " @Nullable Foo get();", + " }", + " static class Baz implements Bar {", + " @SuppressWarnings(\"rawtypes\")", + " @Override", + " public void add(Foo foo) {}", + " @SuppressWarnings(\"rawtypes\")", + " @Override", + " public @Nullable Foo get() { return null; }", + " }", + "}") + .doTest(); + } + private CompilationTestHelper makeHelper() { return makeTestHelperWithArgs( Arrays.asList( diff --git a/sample-app/build.gradle b/sample-app/build.gradle index 6c4deb2f8b..0ab1e6f7f0 100644 --- a/sample-app/build.gradle +++ b/sample-app/build.gradle @@ -45,27 +45,30 @@ android { variants.addAll(getUnitTestVariants()) variants.configureEach { variant -> variant.getJavaCompileProvider().configure { - options.errorprone { - check("NullAway", CheckSeverity.ERROR) - option("NullAway:AnnotatedPackages", "com.uber") - } + options.compilerArgs += [ + "-XDcompilePolicy=simple", + "-Xplugin:ErrorProne -XepOpt:NullAway:AnnotatedPackages=com.uber", + ] + options.fork = true + options.forkOptions.jvmArgs = [ + "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED" + ] } } - - // If you want to disable NullAway in just tests, you can do the below - // DomainObjectSet testVariants = getTestVariants() - // testVariants.addAll(getUnitTestVariants()) - // testVariants.configureEach { variant -> - // variant.getJavaCompileProvider().configure { - // options.errorprone { - // check("NullAway", CheckSeverity.OFF) - // } - // } - // } } dependencies { implementation deps.support.appcompat + annotationProcessor deps.build.errorProneCore annotationProcessor project(":nullaway") annotationProcessor project(path: ":sample-library-model") diff --git a/settings.gradle b/settings.gradle index bfafe10aff..575e1cf06a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -6,6 +6,11 @@ pluginManagement { } } +plugins { + // to automatically pull in relevant JDK toolchains when not present on the local machine + id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" +} + include ':annotations' include ':nullaway' include ':sample-library-model'