diff --git a/README.md b/README.md index ae6fd7306b..48596dafb8 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,20 @@ Some annotation processors like [Dagger](https://google.github.io/dagger/) and [ **Note for Dagger users**: Dagger versions older than 2.12 can have bad interactions with NullAway; see [here](https://github.com/uber/NullAway/issues/48#issuecomment-340018409). Please update to Dagger 2.12 to fix the problem. +#### Lombok + +Unlike other annotation processors above, Lombok modifies the in-memory AST of the code it processes, which is the source of numerous incompatibilities with Error Prone and, consequently, NullAway. + +We do not particularly recommend using NullAway with Lombok. However, NullAway encodes some knowledge of common Lombok annotations and we do try for best-effort compatibility. In particular, common usages like `@lombok.Builder` and `@Data` classes should be supported. + +In order for NullAway to successfully detect Lombok generated code within the in-memory Java AST, the following configuration option must be passed to Lombok as part of an applicable `lombok.config` file: + +``` +addLombokGeneratedAnnotation +``` + +This causes Lombok to add `@lombok.Generated` to the methods/classes it generates. NullAway will ignore (i.e. not check) the implementation of this generated code, treating it as unannotated. + ## Code Example Let's see how NullAway works on a simple code example: diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index b520aee6c5..d0da992c3d 100755 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -73,6 +73,7 @@ def test = [ rxjava2 : "io.reactivex.rxjava2:rxjava:2.1.2", commonsLang3 : "org.apache.commons:commons-lang3:3.8.1", commonsLang : "commons-lang:commons-lang:2.6", + lombok : "org.projectlombok:lombok:1.18.12", ] ext.deps = [ diff --git a/nullaway/build.gradle b/nullaway/build.gradle index 96881f80b6..2dfbcfa8ca 100644 --- a/nullaway/build.gradle +++ b/nullaway/build.gradle @@ -60,6 +60,7 @@ dependencies { testCompile deps.test.commonsLang testCompile deps.test.commonsLang3 testCompile project(":test-library-models") + testCompile deps.test.lombok } // We include and shade the checker framework jars into the NullAway jar, as we may have custom diff --git a/nullaway/src/main/java/com/uber/nullaway/ErrorProneCLIFlagsConfig.java b/nullaway/src/main/java/com/uber/nullaway/ErrorProneCLIFlagsConfig.java index 640286b525..ac4fed5d53 100644 --- a/nullaway/src/main/java/com/uber/nullaway/ErrorProneCLIFlagsConfig.java +++ b/nullaway/src/main/java/com/uber/nullaway/ErrorProneCLIFlagsConfig.java @@ -73,6 +73,9 @@ final class ErrorProneCLIFlagsConfig extends AbstractConfig { private static final String DELIMITER = ","; + static final ImmutableSet DEFAULT_CLASS_ANNOTATIONS_TO_EXCLUDE = + ImmutableSet.of("lombok.Generated"); + static final ImmutableSet DEFAULT_KNOWN_INITIALIZERS = ImmutableSet.of( "android.view.View.onFinishInflate", @@ -107,6 +110,8 @@ final class ErrorProneCLIFlagsConfig extends AbstractConfig { "org.junit.jupiter.api.BeforeAll", "org.junit.jupiter.api.BeforeEach"); // + Anything with @Initializer as its "simple name" + static final ImmutableSet DEFAULT_EXTERNAL_INIT_ANNOT = ImmutableSet.of("lombok.Builder"); + static final ImmutableSet DEFAULT_EXCLUDED_FIELD_ANNOT = ImmutableSet.of( "javax.inject.Inject", // no explicit initialization when there is dependency injection @@ -133,10 +138,13 @@ final class ErrorProneCLIFlagsConfig extends AbstractConfig { knownInitializers = getKnownInitializers( getFlagStringSet(flags, FL_KNOWN_INITIALIZERS, DEFAULT_KNOWN_INITIALIZERS)); - excludedClassAnnotations = getFlagStringSet(flags, FL_CLASS_ANNOTATIONS_TO_EXCLUDE); + excludedClassAnnotations = + getFlagStringSet( + flags, FL_CLASS_ANNOTATIONS_TO_EXCLUDE, DEFAULT_CLASS_ANNOTATIONS_TO_EXCLUDE); initializerAnnotations = getFlagStringSet(flags, FL_INITIALIZER_ANNOT, DEFAULT_INITIALIZER_ANNOT); - externalInitAnnotations = getFlagStringSet(flags, FL_EXTERNAL_INIT_ANNOT); + externalInitAnnotations = + getFlagStringSet(flags, FL_EXTERNAL_INIT_ANNOT, DEFAULT_EXTERNAL_INIT_ANNOT); isExhaustiveOverride = flags.getBoolean(FL_EXHAUSTIVE_OVERRIDE).orElse(false); isSuggestSuppressions = flags.getBoolean(FL_SUGGEST_SUPPRESSIONS).orElse(false); isAcknowledgeRestrictive = flags.getBoolean(FL_ACKNOWLEDGE_RESTRICTIVE).orElse(false); diff --git a/nullaway/src/test/java/com/uber/nullaway/NullAwayTest.java b/nullaway/src/test/java/com/uber/nullaway/NullAwayTest.java index 3c38c252e6..2083c8e456 100644 --- a/nullaway/src/test/java/com/uber/nullaway/NullAwayTest.java +++ b/nullaway/src/test/java/com/uber/nullaway/NullAwayTest.java @@ -95,6 +95,11 @@ public void coreNullabilitySkipClass() { .doTest(); } + @Test + public void lombokSupportTesting() { + compilationHelper.addSourceFile("lombok/LombokBuilderInit.java").doTest(); + } + @Test public void skipClass() { compilationHelper diff --git a/nullaway/src/test/resources/com/uber/nullaway/testdata/lombok/LombokBuilderInit.java b/nullaway/src/test/resources/com/uber/nullaway/testdata/lombok/LombokBuilderInit.java new file mode 100644 index 0000000000..1425abe0f1 --- /dev/null +++ b/nullaway/src/test/resources/com/uber/nullaway/testdata/lombok/LombokBuilderInit.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020 Uber Technologies, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.uber.nullaway.testdata.lombok; + +import javax.annotation.Nullable; +import lombok.Builder; + +@Builder +public class LombokBuilderInit { + private String field; + @Builder.Default private String fieldWithDefault = "Default"; + @Nullable private String nullableField; +} diff --git a/settings.gradle b/settings.gradle index fa8bede777..cb3c2fe79e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -18,6 +18,7 @@ include ':sample-library-model' include ':sample' include ':sample-app' include ':test-java-lib' +include ':test-java-lib-lombok' include ':test-library-models' include ':compile-bench' include ':jar-infer:android-jarinfer-models-sdk28' diff --git a/test-java-lib-lombok/build.gradle b/test-java-lib-lombok/build.gradle new file mode 100644 index 0000000000..1dc553c8cb --- /dev/null +++ b/test-java-lib-lombok/build.gradle @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2017. 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. + */ + +import net.ltgt.gradle.errorprone.CheckSeverity + +plugins { + id "java" +} + +sourceCompatibility = "1.8" +targetCompatibility = "1.8" + +dependencies { + annotationProcessor project(path: ":nullaway", configuration: "shadow") + annotationProcessor deps.test.lombok + + compileOnly deps.build.jsr305Annotations + compileOnly deps.build.javaxValidation + compileOnly deps.test.cfQual + compileOnly deps.test.lombok +} + +tasks.withType(JavaCompile) { + if (!name.toLowerCase().contains("test")) { + options.errorprone { + check("NullAway", CheckSeverity.ERROR) + check("UnusedVariable", CheckSeverity.OFF) // We are not the only checker that fails on Lombok + option("NullAway:AnnotatedPackages", "com.uber") + option("NullAway:UnannotatedSubPackages", "com.uber.lib.unannotated") + } + } +} diff --git a/test-java-lib-lombok/lombok.config b/test-java-lib-lombok/lombok.config new file mode 100644 index 0000000000..df71bb6a0f --- /dev/null +++ b/test-java-lib-lombok/lombok.config @@ -0,0 +1,2 @@ +config.stopBubbling = true +lombok.addLombokGeneratedAnnotation = true diff --git a/test-java-lib-lombok/src/main/java/com/uber/lombok/LombokBuilderInit.java b/test-java-lib-lombok/src/main/java/com/uber/lombok/LombokBuilderInit.java new file mode 100644 index 0000000000..58a9cb5c9c --- /dev/null +++ b/test-java-lib-lombok/src/main/java/com/uber/lombok/LombokBuilderInit.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020 Uber Technologies, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.uber.lombok; + +import javax.annotation.Nullable; +import lombok.Builder; +import lombok.Data; + +@Builder +@Data +public class LombokBuilderInit { + private String field; + @Builder.Default private String fieldWithDefault = "Default"; + @Nullable private String nullableField; +} diff --git a/test-java-lib-lombok/src/main/java/com/uber/lombok/UsesBuilder.java b/test-java-lib-lombok/src/main/java/com/uber/lombok/UsesBuilder.java new file mode 100644 index 0000000000..8ed452cf94 --- /dev/null +++ b/test-java-lib-lombok/src/main/java/com/uber/lombok/UsesBuilder.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020 Uber Technologies, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.uber.lombok; + +class UsesBuilder { + public static String foo(LombokBuilderInit lbi) { + String s = ""; + s += lbi.getField().toString(); + s += " "; + // Removing this nullness check produces a NullAway error + s += (lbi.getNullableField() == null ? "" : lbi.getNullableField().toString()); + return s; + } +}