-
Notifications
You must be signed in to change notification settings - Fork 299
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
…#765) When given code such as: ``` @builder public class LombokDTO { @nullable @Builder.Default private String fieldWithNullDefault = null; } ``` Lombok internally generates the following method: ``` @java.lang.SuppressWarnings(value = "all") @lombok.Generated() private static String $default$fieldWithNullDefault() { return null; } ``` which does not propagate `@Nullable` to the method's return type! While this method is marked as `@Generated` code and `@SuppressWarnings("all")`, that does not suppress NullAway under all configurations. In fact, we sometimes want to check generated code (setters/getters for auto-annotation, for example), so just counting all Lombok Generated code as unannotated is not always the desired behavior (it's optional behavior, enabled by the `TreatGeneratedAsUnannotated=true` flag). Instead, we want to internally and implicitly propagate the `@Nullable` annotation from `fieldWithNullDefault` to the generated `$default$fieldWithNullDefault()`. We do this in two steps: 1. We modify our checking of return statements to allow handlers' existing overriding of method return nullability to be taken into account when deciding if `return [nullable expression];` should result in a NullAway error. 2. We add a new handler for fixing the Lombok nullability propagation, by detecting these `$default$foo()` methods and looking at the nullability of `foo` to determine if the method should also be `@Nullable`. In addition to this, we can suggest a fix upstream in Lombok to propagate `@Nullable` to these `$default$` methods when present on the field, but this would not obviate the need for this PR, since we are keeping compatibility with multiple older Lombok releases internally. Edit: Also, note that coverage on `LombokHandler` is bad in terms of unit tests, but that's because coveralls doesn't count `test-java-lib-lombok/` as a test case. We need to build with Lombok to exercise most of this handler, so we can't really exercise it in unit tests (vs integration) without significantly artificial workarounds (i.e. manually replicating the Lombok-generated code).
- Loading branch information
1 parent
c1741b3
commit d09ff9b
Showing
11 changed files
with
166 additions
and
74 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
95 changes: 95 additions & 0 deletions
95
nullaway/src/main/java/com/uber/nullaway/handlers/LombokHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package com.uber.nullaway.handlers; | ||
|
||
import com.google.common.base.Preconditions; | ||
import com.google.common.collect.ImmutableList; | ||
import com.google.errorprone.VisitorState; | ||
import com.google.errorprone.util.ASTHelpers; | ||
import com.sun.source.tree.ExpressionTree; | ||
import com.sun.source.tree.Tree; | ||
import com.sun.tools.javac.code.Symbol; | ||
import com.uber.nullaway.Config; | ||
import com.uber.nullaway.NullAway; | ||
import com.uber.nullaway.Nullness; | ||
import java.util.stream.StreamSupport; | ||
import javax.annotation.Nullable; | ||
import javax.lang.model.element.ElementKind; | ||
|
||
/** | ||
* A general handler for Lombok generated code and its internal semantics. | ||
* | ||
* <p>Currently used to propagate @Nullable in cases where the Lombok annotation processor fails to | ||
* do so consistently. | ||
*/ | ||
public class LombokHandler extends BaseNoOpHandler { | ||
|
||
private static String LOMBOK_GENERATED_ANNOTATION_NAME = "lombok.Generated"; | ||
private static String LOMBOK_BUILDER_DEFAULT_METHOD_PREFIX = "$default$"; | ||
|
||
private final Config config; | ||
|
||
public LombokHandler(Config config) { | ||
this.config = config; | ||
} | ||
|
||
@SuppressWarnings("ASTHelpersSuggestions") // Suggested API doesn't exist in EP 2.4.0 | ||
private boolean isLombokMethodWithMissingNullableAnnotation( | ||
Symbol.MethodSymbol methodSymbol, VisitorState state) { | ||
if (!ASTHelpers.hasAnnotation(methodSymbol, LOMBOK_GENERATED_ANNOTATION_NAME, state)) { | ||
return false; | ||
} | ||
String methodNameString = methodSymbol.name.toString(); | ||
if (!methodNameString.startsWith(LOMBOK_BUILDER_DEFAULT_METHOD_PREFIX)) { | ||
return false; | ||
} | ||
String originalFieldName = | ||
methodNameString.substring(LOMBOK_BUILDER_DEFAULT_METHOD_PREFIX.length()); | ||
ImmutableList<Symbol> matchingMembers = | ||
StreamSupport.stream( | ||
methodSymbol | ||
.enclClass() | ||
.members() | ||
.getSymbols( | ||
sym -> | ||
sym.name.contentEquals(originalFieldName) | ||
&& sym.getKind().equals(ElementKind.FIELD)) | ||
.spliterator(), | ||
false) | ||
.collect(ImmutableList.toImmutableList()); | ||
Preconditions.checkArgument( | ||
matchingMembers.size() == 1, | ||
String.format( | ||
"Found %d fields matching Lombok generated builder default method %s", | ||
matchingMembers.size(), methodNameString)); | ||
return Nullness.hasNullableAnnotation(matchingMembers.get(0), config); | ||
} | ||
|
||
@Override | ||
public boolean onOverrideMayBeNullExpr( | ||
NullAway analysis, | ||
ExpressionTree expr, | ||
@Nullable Symbol exprSymbol, | ||
VisitorState state, | ||
boolean exprMayBeNull) { | ||
if (exprMayBeNull) { | ||
return true; | ||
} | ||
Tree.Kind exprKind = expr.getKind(); | ||
if (exprSymbol != null && exprKind == Tree.Kind.METHOD_INVOCATION) { | ||
Symbol.MethodSymbol methodSymbol = (Symbol.MethodSymbol) exprSymbol; | ||
return isLombokMethodWithMissingNullableAnnotation(methodSymbol, state); | ||
} | ||
return false; | ||
} | ||
|
||
@Override | ||
public Nullness onOverrideMethodReturnNullability( | ||
Symbol.MethodSymbol methodSymbol, | ||
VisitorState state, | ||
boolean isAnnotated, | ||
Nullness returnNullness) { | ||
if (isLombokMethodWithMissingNullableAnnotation(methodSymbol, state)) { | ||
return Nullness.NULLABLE; | ||
} | ||
return returnNullness; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters