Skip to content
Open
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
8 changes: 8 additions & 0 deletions nullaway/src/main/java/com/uber/nullaway/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,14 @@ public interface Config {
*/
@Nullable String getCastToNonNullMethod();

/**
* Checks whether the <code>castToNonNull()</code> method should be treated as failing on null.
*
* @return true if <code>castToNonNull()</code> methods are guaranteed to throw when passed null,
* false otherwise
*/
boolean castToNonNullMethodFailsOnNull();

/**
* Gets the suppression name aliases.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,11 @@ public Set<String> getOptionalClassPaths() {
throw new IllegalStateException(ERROR_MESSAGE);
}

@Override
public boolean castToNonNullMethodFailsOnNull() {
throw new IllegalStateException(ERROR_MESSAGE);
}

@Override
public Set<String> getSuppressionNameAliases() {
throw new IllegalStateException(ERROR_MESSAGE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ final class ErrorProneCLIFlagsConfig implements Config {
static final String FL_NULLABLE_ANNOT = EP_FL_NAMESPACE + ":CustomNullableAnnotations";
static final String FL_NONNULL_ANNOT = EP_FL_NAMESPACE + ":CustomNonnullAnnotations";
static final String FL_CTNN_METHOD = EP_FL_NAMESPACE + ":CastToNonNullMethod";
static final String FL_CTNN_METHOD_FAILS_ON_NULL =
EP_FL_NAMESPACE + ":CastToNonNullMethodFailsOnNull";
static final String FL_EXTERNAL_INIT_ANNOT = EP_FL_NAMESPACE + ":ExternalInitAnnotations";
static final String FL_CONTRACT_ANNOT = EP_FL_NAMESPACE + ":CustomContractAnnotations";
static final String FL_UNANNOTATED_CLASSES = EP_FL_NAMESPACE + ":UnannotatedClasses";
Expand Down Expand Up @@ -243,6 +245,7 @@ final class ErrorProneCLIFlagsConfig implements Config {
private final ImmutableSet<String> externalInitAnnotations;
private final ImmutableSet<String> contractAnnotations;
private final @Nullable String castToNonNullMethod;
private final boolean castToNonNullMethodFailsOnNull;
private final String autofixSuppressionComment;
private final ImmutableSet<String> suppressionNameAliases;
private final ImmutableSet<String> skippedLibraryModels;
Expand Down Expand Up @@ -312,6 +315,7 @@ final class ErrorProneCLIFlagsConfig implements Config {
getPackagePattern(
getFlagStringSet(flags, FL_EXCLUDED_FIELD_ANNOT, DEFAULT_EXCLUDED_FIELD_ANNOT));
castToNonNullMethod = flags.get(FL_CTNN_METHOD).orElse(null);
castToNonNullMethodFailsOnNull = flags.getBoolean(FL_CTNN_METHOD_FAILS_ON_NULL).orElse(false);
legacyAnnotationLocation = flags.getBoolean(FL_LEGACY_ANNOTATION_LOCATION).orElse(false);
if (legacyAnnotationLocation && jspecifyMode) {
throw new IllegalStateException(
Expand Down Expand Up @@ -554,6 +558,11 @@ public boolean assertsEnabled() {
return castToNonNullMethod;
}

@Override
public boolean castToNonNullMethodFailsOnNull() {
return castToNonNullMethodFailsOnNull;
}

@Override
public ImmutableSet<String> getSuppressionNameAliases() {
return suppressionNameAliases;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -401,8 +401,32 @@ private void setUnconditionalArgumentNullness(
AccessPath.AccessPathContext apContext) {
ImmutableSet<Integer> requiredNonNullParameters =
getOptLibraryModels(state.context).failIfNullParameters(callee);
Set<Integer> allNonNullParams;
if (config.castToNonNullMethodFailsOnNull()) {
ImmutableSet<Integer> castToNonNullParameters =
getOptLibraryModels(state.context).castToNonNullMethod(callee);
String cliCastToNonNull = config.getCastToNonNullMethod();
boolean isCliCastToNonNull =
cliCastToNonNull != null
&& callee.getParameters().size() == 1
&& cliCastToNonNull.equals(
ASTHelpers.enclosingClass(callee) + "." + callee.getSimpleName());

if (castToNonNullParameters.isEmpty() && !isCliCastToNonNull) {
allNonNullParams = requiredNonNullParameters;
} else {
allNonNullParams = new HashSet<>(requiredNonNullParameters);
allNonNullParams.addAll(castToNonNullParameters);
if (isCliCastToNonNull) {
allNonNullParams.add(0);
}
}
} else {
allNonNullParams = requiredNonNullParameters;
}

for (AccessPath accessPath :
accessPathsAtIndexes(requiredNonNullParameters, arguments, state, apContext)) {
accessPathsAtIndexes(allNonNullParams, arguments, state, apContext)) {
bothUpdates.set(accessPath, NONNULL);
}
}
Expand Down
118 changes: 118 additions & 0 deletions nullaway/src/test/java/com/uber/nullaway/CoreTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
package com.uber.nullaway;

import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
Expand Down Expand Up @@ -397,6 +398,123 @@ Object test4(@Nullable Object o) {
.doTest();
}

@SuppressWarnings("deprecation")
@Test
public void testCastToNonNullPropagationDefault() {
defaultCompilationHelper
.addSourceFile("testdata/Util.java")
.addSourceLines(
"Test.java",
"""
package com.uber;
import javax.annotation.Nullable;
import static com.uber.nullaway.testdata.Util.castToNonNull;
class Test {
static class Foo {
@Nullable String getToken() { return ""; }
}
void test(Foo value) {
if (castToNonNull(value.getToken()).contains("abc")) {
// BUG: Diagnostic contains: dereferenced expression 'value.getToken()' is @Nullable
value.getToken().length();
}
}
}
""")
.doTest();
}

@SuppressWarnings("deprecation")
@Test
public void testCastToNonNullPropagationExplicitFalse() {
makeTestHelperWithArgs(
List.of(
"-d",
temporaryFolder.getRoot().getAbsolutePath(),
"-XepOpt:NullAway:AnnotatedPackages=com.uber",
"-XepOpt:NullAway:CastToNonNullMethod=com.uber.nullaway.testdata.Util.castToNonNull",
"-XepOpt:NullAway:CastToNonNullMethodFailsOnNull=false"))
.addSourceFile("testdata/Util.java")
.addSourceLines(
"Test.java",
"""
package com.uber;
import javax.annotation.Nullable;
import static com.uber.nullaway.testdata.Util.castToNonNull;
class Test {
static class Foo {
@Nullable String getToken() { return ""; }
}
void test(Foo value) {
if (castToNonNull(value.getToken()).contains("abc")) {
// BUG: Diagnostic contains: dereferenced expression 'value.getToken()' is @Nullable
value.getToken().length();
}
}
}
""")
.doTest();
}

@SuppressWarnings("deprecation")
@Test
public void testCastToNonNullPropagationFailsOnNullEnabled() {
makeTestHelperWithArgs(
List.of(
"-d",
temporaryFolder.getRoot().getAbsolutePath(),
"-XepOpt:NullAway:AnnotatedPackages=com.uber",
"-XepOpt:NullAway:CastToNonNullMethod=com.uber.nullaway.testdata.Util.castToNonNull",
"-XepOpt:NullAway:CastToNonNullMethodFailsOnNull=true"))
.addSourceFile("testdata/Util.java")
.addSourceLines(
"Test.java",
"""
package com.uber;
import javax.annotation.Nullable;
import static com.uber.nullaway.testdata.Util.castToNonNull;
class Test {
static class Foo {
@Nullable String getToken() { return ""; }
}
void test(Foo value) {
if (castToNonNull(value.getToken()).contains("abc")) {
value.getToken().length();
}
}
}
""")
.doTest();
}

@Test
public void testObjectsRequireNonNullPropagationUnchanged() {
makeTestHelperWithArgs(
List.of(
"-d",
temporaryFolder.getRoot().getAbsolutePath(),
"-XepOpt:NullAway:AnnotatedPackages=com.uber",
"-XepOpt:NullAway:CastToNonNullMethodFailsOnNull=false"))
.addSourceLines(
"Test.java",
"""
package com.uber;
import java.util.Objects;
import javax.annotation.Nullable;
class Test {
static class Foo {
@Nullable String getToken() { return ""; }
}
void test(Foo value) {
if (Objects.requireNonNull(value.getToken()).contains("abc")) {
value.getToken().length();
}
}
}
""")
.doTest();
}

@Test
public void testReadStaticInConstructor() {
defaultCompilationHelper
Expand Down