Skip to content

Commit c44ab8d

Browse files
authored
Add support for AssertJ as() and describedAs() in AssertionHandler (#885)
Fixes #877
1 parent ce41599 commit c44ab8d

File tree

3 files changed

+117
-11
lines changed

3 files changed

+117
-11
lines changed

nullaway/src/main/java/com/uber/nullaway/handlers/AssertionHandler.java

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import com.uber.nullaway.dataflow.AccessPath;
3131
import com.uber.nullaway.dataflow.AccessPathNullnessPropagation;
3232
import java.util.List;
33+
import javax.annotation.Nullable;
3334
import org.checkerframework.nullaway.dataflow.cfg.node.MethodInvocationNode;
3435
import org.checkerframework.nullaway.dataflow.cfg.node.Node;
3536

@@ -60,17 +61,9 @@ public NullnessHint onDataflowVisitMethodInvocation(
6061
// assertThat(A).isInstanceOf(Foo.class)
6162
// A will not be NULL after this statement.
6263
if (methodNameUtil.isMethodIsNotNull(callee) || methodNameUtil.isMethodIsInstanceOf(callee)) {
63-
Node receiver = node.getTarget().getReceiver();
64-
if (receiver instanceof MethodInvocationNode) {
65-
MethodInvocationNode receiver_method = (MethodInvocationNode) receiver;
66-
Symbol.MethodSymbol receiver_symbol = ASTHelpers.getSymbol(receiver_method.getTree());
67-
if (methodNameUtil.isMethodAssertThat(receiver_symbol)) {
68-
Node arg = receiver_method.getArgument(0);
69-
AccessPath ap = AccessPath.getAccessPathForNode(arg, state, apContext);
70-
if (ap != null) {
71-
bothUpdates.set(ap, NONNULL);
72-
}
73-
}
64+
AccessPath ap = getAccessPathForNotNullAssertThatExpr(node, state, apContext);
65+
if (ap != null) {
66+
bothUpdates.set(ap, NONNULL);
7467
}
7568
}
7669

@@ -94,4 +87,31 @@ public NullnessHint onDataflowVisitMethodInvocation(
9487

9588
return NullnessHint.UNKNOWN;
9689
}
90+
91+
/**
92+
* Returns the AccessPath for the argument of an assertThat() call, if present as a valid nested
93+
* receiver expression of a method invocation
94+
*
95+
* @param node the method invocation node
96+
* @param state the visitor state
97+
* @param apContext the access path context
98+
* @return the AccessPath for the argument of the assertThat() call, if present, otherwise {@code
99+
* null}
100+
*/
101+
private @Nullable AccessPath getAccessPathForNotNullAssertThatExpr(
102+
MethodInvocationNode node, VisitorState state, AccessPath.AccessPathContext apContext) {
103+
Node receiver = node.getTarget().getReceiver();
104+
if (receiver instanceof MethodInvocationNode) {
105+
MethodInvocationNode receiver_method = (MethodInvocationNode) receiver;
106+
Symbol.MethodSymbol receiver_symbol = ASTHelpers.getSymbol(receiver_method.getTree());
107+
if (methodNameUtil.isMethodAssertThat(receiver_symbol)) {
108+
Node arg = receiver_method.getArgument(0);
109+
return AccessPath.getAccessPathForNode(arg, state, apContext);
110+
} else if (methodNameUtil.isMethodAssertJDescribedAs(receiver_symbol)) {
111+
// For calls to as() or describedAs(), we recursively search for the assertThat() call
112+
return getAccessPathForNotNullAssertThatExpr(receiver_method, state, apContext);
113+
}
114+
}
115+
return null;
116+
}
97117
}

nullaway/src/main/java/com/uber/nullaway/handlers/MethodNameUtil.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ class MethodNameUtil {
5656
private static final String IS_PRESENT_OWNER_ASSERTJ =
5757
"org.assertj.core.api.AbstractOptionalAssert";
5858
private static final String ASSERT_THAT_METHOD = "assertThat";
59+
private static final String AS_METHOD = "as";
60+
private static final String DESCRIBED_AS_METHOD = "describedAs";
61+
5962
private static final String ASSERT_THAT_OWNER_TRUTH = "com.google.common.truth.Truth";
6063
private static final String ASSERT_THAT_OWNER_ASSERTJ = "org.assertj.core.api.Assertions";
6164

@@ -101,6 +104,9 @@ class MethodNameUtil {
101104
private Name assertThatOwnerTruth;
102105
private Name assertThatOwnerAssertJ;
103106

107+
private Name as;
108+
private Name describedAs;
109+
104110
// Names for junit assertion libraries.
105111
private Name hamcrestAssertClass;
106112
private Name junitAssertClass;
@@ -141,6 +147,9 @@ void initializeMethodNames(Name.Table table) {
141147
assertThatOwnerTruth = table.fromString(ASSERT_THAT_OWNER_TRUTH);
142148
assertThatOwnerAssertJ = table.fromString(ASSERT_THAT_OWNER_ASSERTJ);
143149

150+
as = table.fromString(AS_METHOD);
151+
describedAs = table.fromString(DESCRIBED_AS_METHOD);
152+
144153
isPresent = table.fromString(IS_PRESENT_METHOD);
145154
isNotEmpty = table.fromString(IS_NOT_EMPTY_METHOD);
146155
isPresentOwnerAssertJ = table.fromString(IS_PRESENT_OWNER_ASSERTJ);
@@ -211,6 +220,18 @@ boolean isMethodAssertThat(Symbol.MethodSymbol methodSymbol) {
211220
|| matchesMethod(methodSymbol, assertThat, assertThatOwnerAssertJ);
212221
}
213222

223+
/**
224+
* Returns true if the method is describedAs() or as() from AssertJ. Note that this implementation
225+
* does not check the ower, as there are many possible implementations. This method should only be
226+
* used in a caller content where it is clear that the operation is related to use of AssertJ.
227+
*
228+
* @param methodSymbol symbol for the method
229+
* @return {@code true} iff the method is describedAs() or as() from AssertJ
230+
*/
231+
public boolean isMethodAssertJDescribedAs(Symbol.MethodSymbol methodSymbol) {
232+
return methodSymbol.name.equals(as) || methodSymbol.name.equals(describedAs);
233+
}
234+
214235
boolean isMethodHamcrestAssertThat(Symbol.MethodSymbol methodSymbol) {
215236
return matchesMethod(methodSymbol, assertThat, hamcrestAssertClass);
216237
}

nullaway/src/test/java/com/uber/nullaway/NullAwayAssertionLibsTests.java

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,71 @@ public void supportAssertJAssertThatIsNotNull_Object() {
365365
.doTest();
366366
}
367367

368+
@Test
369+
public void supportAssertJAssertThatIsNotNullWithDescription_Object() {
370+
makeTestHelperWithArgs(
371+
Arrays.asList(
372+
"-d",
373+
temporaryFolder.getRoot().getAbsolutePath(),
374+
"-XepOpt:NullAway:AnnotatedPackages=com.uber",
375+
"-XepOpt:NullAway:HandleTestAssertionLibraries=true"))
376+
.addSourceLines(
377+
"Test.java",
378+
"package com.uber;",
379+
"import java.lang.Object;",
380+
"import java.util.Objects;",
381+
"import javax.annotation.Nullable;",
382+
"import static org.assertj.core.api.Assertions.assertThat;",
383+
"class Test {",
384+
" private void foo(@Nullable Object o) {",
385+
" assertThat(o).as(\"test\").isNotNull();",
386+
" o.toString();",
387+
" }",
388+
" private void foo2(@Nullable Object o) {",
389+
" assertThat(o).describedAs(\"test\").isNotNull();",
390+
" o.toString();",
391+
" }",
392+
" private void foo3(@Nullable Object o) {",
393+
" assertThat(o).describedAs(\"test1\").as(\"test2\").isNotNull();",
394+
" o.toString();",
395+
" }",
396+
"}")
397+
.doTest();
398+
}
399+
400+
@Test
401+
public void assertJAssertThatIsNotNullUnhandled() {
402+
makeTestHelperWithArgs(
403+
Arrays.asList(
404+
"-d",
405+
temporaryFolder.getRoot().getAbsolutePath(),
406+
"-XepOpt:NullAway:AnnotatedPackages=com.uber",
407+
"-XepOpt:NullAway:HandleTestAssertionLibraries=true"))
408+
.addSourceLines(
409+
"Test.java",
410+
"package com.uber;",
411+
"import java.lang.Object;",
412+
"import java.util.Objects;",
413+
"import javax.annotation.Nullable;",
414+
"import static org.assertj.core.api.Assertions.assertThat;",
415+
"class Test {",
416+
" private void foo(@Nullable Object o) {",
417+
" org.assertj.core.api.ObjectAssert t = assertThat(o);",
418+
" t.isNotNull();",
419+
" // False positive",
420+
" // BUG: Diagnostic contains: dereferenced expression",
421+
" o.toString();",
422+
" }",
423+
" private void foo2(@Nullable Object o) {",
424+
" assertThat(o).isEqualToIgnoringNullFields(o).describedAs(\"test\").isNotNull();",
425+
" // False positive",
426+
" // BUG: Diagnostic contains: dereferenced expression",
427+
" o.toString();",
428+
" }",
429+
"}")
430+
.doTest();
431+
}
432+
368433
@Test
369434
public void supportAssertJAssertThatIsNotNull_String() {
370435
makeTestHelperWithArgs(

0 commit comments

Comments
 (0)