diff --git a/jdk17-unit-tests/build.gradle b/jdk17-unit-tests/build.gradle index 368fd2722b..506e81dd48 100644 --- a/jdk17-unit-tests/build.gradle +++ b/jdk17-unit-tests/build.gradle @@ -34,6 +34,8 @@ configurations { dependencies { testImplementation project(":nullaway") testImplementation deps.test.junit4 + testImplementation deps.test.junit5Jupiter + testImplementation deps.test.assertJ testImplementation(deps.build.errorProneTestHelpers) { exclude group: "junit", module: "junit" } diff --git a/jdk17-unit-tests/src/test/java/com/uber/nullaway/jdk17/NullAwayOptionalEmptyTests.java b/jdk17-unit-tests/src/test/java/com/uber/nullaway/jdk17/NullAwayOptionalEmptyTests.java index 719e11aa91..2e2ceee802 100644 --- a/jdk17-unit-tests/src/test/java/com/uber/nullaway/jdk17/NullAwayOptionalEmptyTests.java +++ b/jdk17-unit-tests/src/test/java/com/uber/nullaway/jdk17/NullAwayOptionalEmptyTests.java @@ -3,6 +3,7 @@ import com.google.errorprone.CompilationTestHelper; import com.uber.nullaway.NullAway; import java.util.Arrays; +import java.util.List; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -95,4 +96,102 @@ public void optionalIsEmptyPositive() { "}") .doTest(); } + + @Test + public void optionalIsEmptyHandleAssertionLibraryTruthAssertThat() { + makeTestHelperWithArgs( + Arrays.asList( + "-d", + temporaryFolder.getRoot().getAbsolutePath(), + "-XepOpt:NullAway:AnnotatedPackages=com.uber", + "-XepOpt:NullAway:CheckOptionalEmptiness=true", + "-XepOpt:NullAway:HandleTestAssertionLibraries=true")) + .addSourceLines( + "Test.java", + "package com.uber;", + "import java.util.Optional;", + "import com.google.common.truth.Truth;", + "", + "public class Test {", + " void truthAssertThatIsEmptyIsFalse() {", + " Optional b = Optional.empty();", + " Truth.assertThat(b.isEmpty()).isTrue(); // no impact", + " // BUG: Diagnostic contains: Invoking get() on possibly empty Optional b", + " b.get().toString();", + " Truth.assertThat(b.isEmpty()).isFalse();", + " b.get().toString();", + " }", + "}") + .doTest(); + } + + @Test + public void optionalIsEmptyHandleAssertionLibraryAssertJAssertThat() { + makeTestHelperWithArgs( + Arrays.asList( + "-d", + temporaryFolder.getRoot().getAbsolutePath(), + "-XepOpt:NullAway:AnnotatedPackages=com.uber", + "-XepOpt:NullAway:CheckOptionalEmptiness=true", + "-XepOpt:NullAway:HandleTestAssertionLibraries=true")) + .addSourceLines( + "Test.java", + "package com.uber;", + "import java.util.Optional;", + "import org.assertj.core.api.Assertions;", + "", + "public class Test {", + " void assertJAssertThatIsEmptyIsFalse() {", + " Optional b = Optional.empty();", + " Assertions.assertThat(b.isEmpty()).isTrue(); // no impact", + " // BUG: Diagnostic contains: Invoking get() on possibly empty Optional b", + " b.get().toString();", + " Assertions.assertThat(b.isEmpty()).isFalse();", + " b.get().toString();", + " }", + "}") + .doTest(); + } + + @Test + public void optionalIsEmptyHandleAssertionLibraryJUnitAssertions() { + makeTestHelperWithArgs( + Arrays.asList( + "-d", + temporaryFolder.getRoot().getAbsolutePath(), + "-XepOpt:NullAway:AnnotatedPackages=com.uber", + "-XepOpt:NullAway:CheckOptionalEmptiness=true", + "-XepOpt:NullAway:HandleTestAssertionLibraries=true")) + .addSourceLines( + "Test.java", + "package com.uber;", + "import java.util.Optional;", + "import org.junit.Assert;", + "import org.junit.jupiter.api.Assertions;", + "", + "public class Test {", + " void junit4AssertFalseIsEmpty() {", + " Optional b = Optional.empty();", + " Assert.assertTrue(b.isEmpty()); // no impact", + " // BUG: Diagnostic contains: Invoking get() on possibly empty Optional b", + " b.get().toString();", + " Assert.assertFalse(\"errormsg\", b.isEmpty());", + " b.get().toString();", + " }", + "", + " void junit5AssertFalseIsEmpty() {", + " Optional d = Optional.empty();", + " Assertions.assertTrue(d.isEmpty()); // no impact", + " // BUG: Diagnostic contains: Invoking get() on possibly empty Optional d", + " d.get().toString();", + " Assertions.assertFalse(d.isEmpty(), \"errormsg\");", + " d.get().toString();", + " }", + "}") + .doTest(); + } + + protected CompilationTestHelper makeTestHelperWithArgs(List args) { + return CompilationTestHelper.newInstance(NullAway.class, getClass()).setArgs(args); + } } diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/MethodNameUtil.java b/nullaway/src/main/java/com/uber/nullaway/handlers/MethodNameUtil.java index 849e8a9957..25a739529f 100644 --- a/nullaway/src/main/java/com/uber/nullaway/handlers/MethodNameUtil.java +++ b/nullaway/src/main/java/com/uber/nullaway/handlers/MethodNameUtil.java @@ -43,13 +43,25 @@ class MethodNameUtil { private static final String IS_NOT_NULL_OWNER_TRUTH = "com.google.common.truth.Subject"; private static final String IS_NOT_NULL_OWNER_ASSERTJ = "org.assertj.core.api.AbstractAssert"; private static final String IS_TRUE_METHOD = "isTrue"; - private static final String IS_TRUE_OWNER = "com.google.common.truth.BooleanSubject"; + private static final String IS_FALSE_METHOD = "isFalse"; + private static final String IS_TRUE_OWNER_TRUTH = "com.google.common.truth.BooleanSubject"; + private static final String IS_TRUE_OWNER_ASSERTJ = "org.assertj.core.api.AbstractBooleanAssert"; + private static final String BOOLEAN_VALUE_OF_METHOD = "valueOf"; + private static final String BOOLEAN_VALUE_OF_OWNER = "java.lang.Boolean"; + private static final String IS_PRESENT_METHOD = "isPresent"; + private static final String IS_NOT_EMPTY_METHOD = "isNotEmpty"; + private static final String IS_PRESENT_OWNER_ASSERTJ = + "org.assertj.core.api.AbstractOptionalAssert"; private static final String ASSERT_THAT_METHOD = "assertThat"; private static final String ASSERT_THAT_OWNER_TRUTH = "com.google.common.truth.Truth"; private static final String ASSERT_THAT_OWNER_ASSERTJ = "org.assertj.core.api.Assertions"; private static final String HAMCREST_ASSERT_CLASS = "org.hamcrest.MatcherAssert"; private static final String JUNIT_ASSERT_CLASS = "org.junit.Assert"; + private static final String JUNIT5_ASSERTION_CLASS = "org.junit.jupiter.api.Assertions"; + + private static final String ASSERT_TRUE_METHOD = "assertTrue"; + private static final String ASSERT_FALSE_METHOD = "assertFalse"; private static final String MATCHERS_CLASS = "org.hamcrest.Matchers"; private static final String CORE_MATCHERS_CLASS = "org.hamcrest.CoreMatchers"; @@ -67,7 +79,15 @@ class MethodNameUtil { private Name isNotNullOwnerAssertJ; private Name isTrue; - private Name isTrueOwner; + private Name isFalse; + private Name isTrueOwnerTruth; + private Name isTrueOwnerAssertJ; + private Name isPresent; + private Name isNotEmpty; + private Name isPresentOwnerAssertJ; + + private Name isBooleanValueOfMethod; + private Name isBooleanValueOfOwner; private Name assertThat; private Name assertThatOwnerTruth; @@ -76,6 +96,10 @@ class MethodNameUtil { // Names for junit assertion libraries. private Name hamcrestAssertClass; private Name junitAssertClass; + private Name junit5AssertionClass; + + private Name assertTrue; + private Name assertFalse; // Names for hamcrest matchers. private Name matchersClass; @@ -93,14 +117,27 @@ void initializeMethodNames(Name.Table table) { isNotNullOwnerAssertJ = table.fromString(IS_NOT_NULL_OWNER_ASSERTJ); isTrue = table.fromString(IS_TRUE_METHOD); - isTrueOwner = table.fromString(IS_TRUE_OWNER); + isFalse = table.fromString(IS_FALSE_METHOD); + isTrueOwnerTruth = table.fromString(IS_TRUE_OWNER_TRUTH); + isTrueOwnerAssertJ = table.fromString(IS_TRUE_OWNER_ASSERTJ); + + isBooleanValueOfMethod = table.fromString(BOOLEAN_VALUE_OF_METHOD); + isBooleanValueOfOwner = table.fromString(BOOLEAN_VALUE_OF_OWNER); assertThat = table.fromString(ASSERT_THAT_METHOD); assertThatOwnerTruth = table.fromString(ASSERT_THAT_OWNER_TRUTH); assertThatOwnerAssertJ = table.fromString(ASSERT_THAT_OWNER_ASSERTJ); + isPresent = table.fromString(IS_PRESENT_METHOD); + isNotEmpty = table.fromString(IS_NOT_EMPTY_METHOD); + isPresentOwnerAssertJ = table.fromString(IS_PRESENT_OWNER_ASSERTJ); + hamcrestAssertClass = table.fromString(HAMCREST_ASSERT_CLASS); junitAssertClass = table.fromString(JUNIT_ASSERT_CLASS); + junit5AssertionClass = table.fromString(JUNIT5_ASSERTION_CLASS); + + assertTrue = table.fromString(ASSERT_TRUE_METHOD); + assertFalse = table.fromString(ASSERT_FALSE_METHOD); matchersClass = table.fromString(MATCHERS_CLASS); coreMatchersClass = table.fromString(CORE_MATCHERS_CLASS); @@ -116,8 +153,35 @@ boolean isMethodIsNotNull(Symbol.MethodSymbol methodSymbol) { || matchesMethod(methodSymbol, isNotNull, isNotNullOwnerAssertJ); } + boolean isMethodAssertTrue(Symbol.MethodSymbol methodSymbol) { + return matchesMethod(methodSymbol, assertTrue, junitAssertClass) + || matchesMethod(methodSymbol, assertTrue, junit5AssertionClass); + } + + boolean isMethodAssertFalse(Symbol.MethodSymbol methodSymbol) { + return matchesMethod(methodSymbol, assertFalse, junitAssertClass) + || matchesMethod(methodSymbol, assertFalse, junit5AssertionClass); + } + + boolean isMethodThatEnsuresOptionalPresent(Symbol.MethodSymbol methodSymbol) { + // same owner + return matchesMethod(methodSymbol, isPresent, isPresentOwnerAssertJ) + || matchesMethod(methodSymbol, isNotEmpty, isPresentOwnerAssertJ); + } + boolean isMethodIsTrue(Symbol.MethodSymbol methodSymbol) { - return matchesMethod(methodSymbol, isTrue, isTrueOwner); + return matchesMethod(methodSymbol, isTrue, isTrueOwnerTruth) + || matchesMethod(methodSymbol, isTrue, isTrueOwnerAssertJ); + } + + boolean isMethodIsFalse(Symbol.MethodSymbol methodSymbol) { + // same owners as isTrue + return matchesMethod(methodSymbol, isFalse, isTrueOwnerTruth) + || matchesMethod(methodSymbol, isFalse, isTrueOwnerAssertJ); + } + + boolean isMethodBooleanValueOf(Symbol.MethodSymbol methodSymbol) { + return matchesMethod(methodSymbol, isBooleanValueOfMethod, isBooleanValueOfOwner); } boolean isMethodAssertThat(Symbol.MethodSymbol methodSymbol) { diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/OptionalEmptinessHandler.java b/nullaway/src/main/java/com/uber/nullaway/handlers/OptionalEmptinessHandler.java index 5de9999249..79c4e0e14a 100644 --- a/nullaway/src/main/java/com/uber/nullaway/handlers/OptionalEmptinessHandler.java +++ b/nullaway/src/main/java/com/uber/nullaway/handlers/OptionalEmptinessHandler.java @@ -49,6 +49,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.function.Consumer; import javax.annotation.Nullable; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; @@ -57,6 +58,7 @@ import javax.lang.model.element.Modifier; import javax.lang.model.element.Name; import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import org.checkerframework.nullaway.dataflow.cfg.node.MethodInvocationNode; import org.checkerframework.nullaway.dataflow.cfg.node.Node; @@ -122,9 +124,8 @@ public NullnessHint onDataflowVisitMethodInvocation( } else if (optionalIsEmptyCall(symbol, types)) { updateNonNullAPsForOptionalContent( state.context, elseUpdates, node.getTarget().getReceiver(), apContext); - } else if (config.handleTestAssertionLibraries() && methodNameUtil.isMethodIsTrue(symbol)) { - // we check for instance of AssertThat(optionalFoo.isPresent()).isTrue() - updateIfAssertIsPresentTrueOnOptional(state.context, node, types, apContext, bothUpdates); + } else if (config.handleTestAssertionLibraries()) { + handleTestAssertions(state, apContext, bothUpdates, node, symbol); } return NullnessHint.UNKNOWN; } @@ -133,8 +134,9 @@ public NullnessHint onDataflowVisitMethodInvocation( public Optional onExpressionDereference( ExpressionTree expr, ExpressionTree baseExpr, VisitorState state) { Preconditions.checkNotNull(analysis); - if (ASTHelpers.getSymbol(expr) instanceof Symbol.MethodSymbol - && optionalIsGetCall((Symbol.MethodSymbol) ASTHelpers.getSymbol(expr), state.getTypes()) + Symbol symbol = ASTHelpers.getSymbol(expr); + if (symbol instanceof Symbol.MethodSymbol + && optionalIsGetCall((Symbol.MethodSymbol) symbol, state.getTypes()) && isOptionalContentNullable(state, baseExpr, analysis.getNullnessAnalysis(state))) { final String message = "Invoking get() on possibly empty Optional " + baseExpr; return Optional.of( @@ -168,35 +170,102 @@ public boolean includeApInfoInSavedContext(AccessPath accessPath, VisitorState s return false; } - private void updateIfAssertIsPresentTrueOnOptional( - Context context, + private void handleTestAssertions( + VisitorState state, + AccessPath.AccessPathContext apContext, + AccessPathNullnessPropagation.Updates bothUpdates, MethodInvocationNode node, + Symbol.MethodSymbol symbol) { + + Consumer nonNullMarker = + nonNullNode -> + updateNonNullAPsForOptionalContent(state.context, bothUpdates, nonNullNode, apContext); + + boolean isAssertTrueMethod = methodNameUtil.isMethodAssertTrue(symbol); + boolean isAssertFalseMethod = methodNameUtil.isMethodAssertFalse(symbol); + boolean isTrueMethod = methodNameUtil.isMethodIsTrue(symbol); + boolean isFalseMethod = methodNameUtil.isMethodIsFalse(symbol); + if (isAssertTrueMethod || isAssertFalseMethod) { + // assertTrue(optionalFoo.isPresent()) + // assertFalse("optional was empty", optionalFoo.isEmpty()) + // note: in junit4 the optional string message comes first, but in junit5 it comes last + Optional assertedOnMethod = + node.getArguments().stream() + .filter(n -> TypeKind.BOOLEAN.equals(n.getType().getKind())) + .filter(n -> n instanceof MethodInvocationNode) + .map(n -> (MethodInvocationNode) n) + .findFirst(); + if (assertedOnMethod.isPresent()) { + handleBooleanAssertionOnMethod( + nonNullMarker, + state.getTypes(), + assertedOnMethod.get(), + isAssertTrueMethod, + isAssertFalseMethod); + } + } else if (isTrueMethod || isFalseMethod) { + // asertThat(optionalFoo.isPresent()).isTrue() + // asertThat(optionalFoo.isEmpty()).isFalse() + Optional wrappedMethod = + getNodeWrappedByAssertThat(node) + .filter(n -> n instanceof MethodInvocationNode) + .map(n -> (MethodInvocationNode) n) + .map(this::maybeUnwrapBooleanValueOf); + if (wrappedMethod.isPresent()) { + handleBooleanAssertionOnMethod( + nonNullMarker, state.getTypes(), wrappedMethod.get(), isTrueMethod, isFalseMethod); + } + } else if (methodNameUtil.isMethodThatEnsuresOptionalPresent(symbol)) { + // assertThat(optionalRef).isPresent() + // assertThat(methodReturningOptional()).isNotEmpty() + // assertThat(mapWithOptionalValues.get("key")).isNotEmpty() + getNodeWrappedByAssertThat(node).ifPresent(nonNullMarker); + } + } + + private void handleBooleanAssertionOnMethod( + Consumer nonNullMarker, Types types, - AccessPath.AccessPathContext apContext, - AccessPathNullnessPropagation.Updates bothUpdates) { + MethodInvocationNode node, + boolean assertsTrue, + boolean assertsFalse) { + Symbol.MethodSymbol methodSymbol = ASTHelpers.getSymbol(node.getTree()); + boolean ensuresIsPresent = assertsTrue && optionalIsPresentCall(methodSymbol, types); + boolean ensuresNotEmpty = assertsFalse && optionalIsEmptyCall(methodSymbol, types); + if (ensuresIsPresent || ensuresNotEmpty) { + nonNullMarker.accept(node.getTarget().getReceiver()); + } + } + + private Optional getNodeWrappedByAssertThat(MethodInvocationNode node) { Node receiver = node.getTarget().getReceiver(); if (receiver instanceof MethodInvocationNode) { MethodInvocationNode receiverMethod = (MethodInvocationNode) receiver; - Symbol.MethodSymbol receiverSymbol = ASTHelpers.getSymbol(receiverMethod.getTree()); - if (methodNameUtil.isMethodAssertThat(receiverSymbol)) { - // assertThat will always have at least one argument, So safe to extract from the arguments - Node arg = receiverMethod.getArgument(0); - if (arg instanceof MethodInvocationNode) { - // Since assertThat(a.isPresent()) changes to - // Truth.assertThat(Boolean.valueOf(a.isPresent())) - // need to be unwrapped from Boolean.valueOf - Node unwrappedArg = ((MethodInvocationNode) arg).getArgument(0); - if (unwrappedArg instanceof MethodInvocationNode) { - MethodInvocationNode argMethod = (MethodInvocationNode) unwrappedArg; - Symbol.MethodSymbol argSymbol = ASTHelpers.getSymbol(argMethod.getTree()); - if (optionalIsPresentCall(argSymbol, types)) { - updateNonNullAPsForOptionalContent( - context, bothUpdates, argMethod.getTarget().getReceiver(), apContext); - } - } + if (receiverMethod.getArguments().size() == 1) { + Symbol.MethodSymbol receiverSymbol = ASTHelpers.getSymbol(receiverMethod.getTree()); + if (methodNameUtil.isMethodAssertThat(receiverSymbol)) { + return Optional.of(receiverMethod.getArgument(0)); + } + } + } + return Optional.empty(); + } + + private MethodInvocationNode maybeUnwrapBooleanValueOf(MethodInvocationNode node) { + // Due to autoboxing in the java compiler + // Truth.assertThat(a.isPresent()) changes to + // Truth.assertThat(Boolean.valueOf(a.isPresent())) + // and we need to unwrap Boolean.valueOf here + if (node.getArguments().size() == 1) { + Symbol.MethodSymbol symbol = ASTHelpers.getSymbol(node.getTree()); + if (methodNameUtil.isMethodBooleanValueOf(symbol)) { + Node unwrappedArg = node.getArgument(0); + if (unwrappedArg instanceof MethodInvocationNode) { + return (MethodInvocationNode) unwrappedArg; } } } + return node; } private void updateNonNullAPsForOptionalContent( diff --git a/nullaway/src/test/java/com/uber/nullaway/NullAwayOptionalEmptinessTests.java b/nullaway/src/test/java/com/uber/nullaway/NullAwayOptionalEmptinessTests.java index a0cb723a17..32199f48d1 100644 --- a/nullaway/src/test/java/com/uber/nullaway/NullAwayOptionalEmptinessTests.java +++ b/nullaway/src/test/java/com/uber/nullaway/NullAwayOptionalEmptinessTests.java @@ -371,6 +371,155 @@ public void optionalEmptinessHandleAssertionLibraryTest() { .doTest(); } + @Test + public void optionalEmptinessHandleAssertionLibraryTruthAssertThat() { + makeTestHelperWithArgs( + Arrays.asList( + "-d", + temporaryFolder.getRoot().getAbsolutePath(), + "-XepOpt:NullAway:AnnotatedPackages=com.uber", + "-XepOpt:NullAway:CheckOptionalEmptiness=true", + "-XepOpt:NullAway:HandleTestAssertionLibraries=true")) + .addSourceLines( + "Test.java", + "package com.uber;", + "import java.util.Optional;", + "import com.google.common.truth.Truth;", + "", + "public class Test {", + " void truthAssertThatIsPresentIsTrue() {", + " Optional a = Optional.empty();", + " Truth.assertThat(a.isPresent()).isFalse(); // no impact", + " // BUG: Diagnostic contains: Invoking get() on possibly empty Optional a", + " a.get().toString();", + " Truth.assertThat(a.isPresent()).isTrue();", + " a.get().toString();", + " }", + "}") + .doTest(); + } + + @Test + public void optionalEmptinessHandleAssertionLibraryAssertJAssertThat() { + makeTestHelperWithArgs( + Arrays.asList( + "-d", + temporaryFolder.getRoot().getAbsolutePath(), + "-XepOpt:NullAway:AnnotatedPackages=com.uber", + "-XepOpt:NullAway:CheckOptionalEmptiness=true", + "-XepOpt:NullAway:HandleTestAssertionLibraries=true")) + .addSourceLines( + "Test.java", + "package com.uber;", + "import java.util.HashMap;", + "import java.util.Map;", + "import java.util.Optional;", + "import org.assertj.core.api.Assertions;", + "", + "public class Test {", + " void assertJAssertThatIsPresentIsTrue() {", + " Optional a = Optional.empty();", + " Assertions.assertThat(a.isPresent()).isFalse(); // no impact", + " // BUG: Diagnostic contains: Invoking get() on possibly empty Optional a", + " a.get().toString();", + " Assertions.assertThat(a.isPresent()).isTrue();", + " a.get().toString();", + " }", + "", + " void assertJAssertThatOptionalIsPresent() {", + " Optional c = Optional.empty();", + " // BUG: Diagnostic contains: Invoking get() on possibly empty Optional c", + " c.get().toString();", + " Assertions.assertThat(c).isPresent();", + " c.get().toString();", + " }", + "", + " void assertJAssertThatOptionalIsNotEmpty() {", + " Optional d = Optional.empty();", + " // BUG: Diagnostic contains: Invoking get() on possibly empty Optional d", + " d.get().toString();", + " Assertions.assertThat(d).isNotEmpty();", + " d.get().toString();", + " }", + "", + " static Optional aStaticField = Optional.empty();", + " Optional aField = Optional.empty();", + "", + " void assertJAssertThatOptionalFields() {", + " // BUG: Diagnostic contains: Invoking get() on possibly empty Optional aField", + " aField.get().toString();", + " // BUG: Diagnostic contains: Invoking get() on possibly empty Optional aStaticField", + " aStaticField.get().toString();", + " Assertions.assertThat(aStaticField).isPresent();", + " aStaticField.get().toString();", + " Assertions.assertThat(aField).isNotEmpty();", + " aField.get().toString();", + " }", + "", + " Optional getOptional() {", + " return Optional.of(2L);", + " }", + "", + " void assertJAssertThatOptionalGetter() {", + " // BUG: Diagnostic contains: Invoking get() on possibly empty Optional getOptional()", + " long x = 2L + getOptional().get();", + " Assertions.assertThat(getOptional()).isPresent();", + " long y = 2L + getOptional().get();", + " }", + "", + " Map> getMapWithOptional() {", + " return new HashMap<>();", + " }", + "", + " void assertJAssertThatMapWithOptional() {", + " Assertions.assertThat(getMapWithOptional().get(\"thekey\")).isNotNull();", + " // BUG: Diagnostic contains: Invoking get() on possibly empty Optional getMapWithOptional().get(\"thekey\")", + " int x = 1 + getMapWithOptional().get(\"thekey\").get();", + " Assertions.assertThat(getMapWithOptional().get(\"thekey\")).isPresent();", + " int y = 1 + getMapWithOptional().get(\"thekey\").get();", + " }", + "}") + .doTest(); + } + + @Test + public void optionalEmptinessHandleAssertionLibraryJUnitAssertions() { + makeTestHelperWithArgs( + Arrays.asList( + "-d", + temporaryFolder.getRoot().getAbsolutePath(), + "-XepOpt:NullAway:AnnotatedPackages=com.uber", + "-XepOpt:NullAway:CheckOptionalEmptiness=true", + "-XepOpt:NullAway:HandleTestAssertionLibraries=true")) + .addSourceLines( + "Test.java", + "package com.uber;", + "import java.util.Optional;", + "import org.junit.Assert;", + "import org.junit.jupiter.api.Assertions;", + "", + "public class Test {", + " void junit4AssertTrueIsPresent() {", + " Optional a = Optional.empty();", + " Assert.assertFalse(a.isPresent()); // no impact", + " // BUG: Diagnostic contains: Invoking get() on possibly empty Optional a", + " a.get().toString();", + " Assert.assertTrue(\"not present\", a.isPresent());", + " a.get().toString();", + " }", + "", + " void junit5AssertTrueIsPresent() {", + " Optional c = Optional.empty();", + " Assert.assertFalse(c.isPresent()); // no impact", + " // BUG: Diagnostic contains: Invoking get() on possibly empty Optional c", + " c.get().toString();", + " Assertions.assertTrue(c.isPresent(), \"not present\");", + " c.get().toString();", + " }", + "}") + .doTest(); + } + @Test public void optionalEmptinessAssignmentCheckNegativeTest() { makeTestHelperWithArgs(