Skip to content

Commit b2f8480

Browse files
committed
Merge branch 'generic-method-lambda-arg-wildcard' of https://github.com/dhruv-agr/NullAway into generic-method-lambda-arg-wildcard
2 parents e91b9c1 + 30fcbc8 commit b2f8480

File tree

11 files changed

+260
-14
lines changed

11 files changed

+260
-14
lines changed

.github/workflows/continuous-integration.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ jobs:
6161
url: https://github.com/ben-manes/caffeine.git
6262
- project: spring-framework
6363
url: https://github.com/spring-projects/spring-framework.git
64+
- project: junit-framework
65+
url: https://github.com/junit-team/junit-framework.git
6466
permissions:
6567
contents: read
6668
runs-on: ubuntu-latest

AGENTS.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,11 @@ test class or method within that module, you can use the `--tests` flag. For exa
88
```bash
99
./gradlew :nullaway:test --tests "com.uber.nullaway.NullAwayTest"
1010
```
11+
12+
# Changelog
13+
14+
Our `CHANGELOG.md` file should be formatted as follows:
15+
16+
* Link to PRs just via their number, e.g. `#1234`, not a full GitHub URL
17+
* Don't credit `@msridhar` in changelog entries, but credit all other contributors
18+
* Under maintenance, list sub-bullets with a `-` rather than a `*`

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
Changelog
22
=========
33

4+
Version 0.13.1
5+
--------------
6+
7+
* Improve verification of !null -> !null contracts (#1441)
8+
* Substitute inferred `@NonNull` types for generic method inference (#1445)
9+
* Better support for some contracts with boolean argument constraints (#1447)
10+
* Maintenance
11+
- Add junit-framework as another integration test (#1446)
12+
413
Version 0.13.0
514
--------------
615

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ org.gradle.caching=true
1212
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m
1313

1414
GROUP=com.uber.nullaway
15-
VERSION_NAME=0.13.1-SNAPSHOT
15+
VERSION_NAME=0.13.2-SNAPSHOT
1616

1717
POM_DESCRIPTION=A fast annotation-based null checker for Java
1818

nullaway/src/main/java/com/uber/nullaway/NullabilityUtil.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,13 @@ public static boolean hasAnyAnnotationMatching(
211211
return true;
212212
}
213213
}
214+
// we need this loop over the type's annotation mirrors in cases like explicitly-annotated
215+
// lambda parameters, possibly due to bugs in javac
216+
for (AnnotationMirror annotationMirror : symbol.type.getAnnotationMirrors()) {
217+
if (predicate.test(annotationMirror.getAnnotationType().toString())) {
218+
return true;
219+
}
220+
}
214221
// to handle bytecodes, also check direct type-use annotations stored in attributes
215222
Symbol typeAnnotationOwner =
216223
symbol.getKind().equals(ElementKind.PARAMETER) ? symbol.owner : symbol;

nullaway/src/main/java/com/uber/nullaway/generics/GenericTypePrettyPrintingVisitor.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,13 @@ public String visitArrayType(Type.ArrayType t, @Nullable Void unused) {
8989
return sb.append(" []").toString();
9090
}
9191

92+
@Override
93+
public String visitErrorType(Type.ErrorType t, @Nullable Void unused) {
94+
// this arises for our synthetic @Nullable and @NonNull annotations; we just return the simple
95+
// name
96+
return t.tsym.getSimpleName().toString();
97+
}
98+
9299
@Override
93100
public String visitType(Type t, @Nullable Void s) {
94101
return t.toString();

nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.java

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -224,18 +224,20 @@ private static Type substituteInferredNullabilityForTypeVariables(
224224
ListBuffer<Type> inferredTypes = new ListBuffer<>();
225225
for (Map.Entry<Element, ConstraintSolver.InferredNullability> entry :
226226
typeVarNullability.entrySet()) {
227-
if (entry.getValue() == NULLABLE) {
228-
// find all TypeVars occurring in targetType with the same symbol and substitute for those.
229-
// we can have multiple such TypeVars due to previous substitutions that modified the type
230-
// in some way, e.g., by changing its bounds
231-
Element symbol = entry.getKey();
232-
TypeVarWithSymbolCollector tvc = new TypeVarWithSymbolCollector(symbol);
233-
targetType.accept(tvc, null);
234-
for (Type.TypeVar tv : tvc.getMatches()) {
235-
typeVars.append(tv);
236-
inferredTypes.append(
237-
typeWithAnnot(tv, GenericsChecks.getSyntheticNullableAnnotType(state)));
238-
}
227+
// find all TypeVars occurring in targetType with the same symbol and substitute for those.
228+
// we can have multiple such TypeVars due to previous substitutions that modified the type
229+
// in some way, e.g., by changing its bounds
230+
Element symbol = entry.getKey();
231+
TypeVarWithSymbolCollector tvc = new TypeVarWithSymbolCollector(symbol);
232+
targetType.accept(tvc, null);
233+
for (Type.TypeVar tv : tvc.getMatches()) {
234+
typeVars.append(tv);
235+
inferredTypes.append(
236+
typeWithAnnot(
237+
tv,
238+
entry.getValue() == NULLABLE
239+
? GenericsChecks.getSyntheticNullableAnnotType(state)
240+
: GenericsChecks.getSyntheticNonNullAnnotType(state)));
239241
}
240242
}
241243
List<Type> typeVarsToReplace = typeVars.toList();

nullaway/src/main/java/com/uber/nullaway/handlers/contract/ContractHandler.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import com.google.errorprone.VisitorState;
3131
import com.google.errorprone.util.ASTHelpers;
3232
import com.sun.source.tree.ClassTree;
33+
import com.sun.source.tree.ExpressionTree;
3334
import com.sun.source.tree.MethodInvocationTree;
3435
import com.sun.tools.javac.code.Symbol;
3536
import com.uber.nullaway.Config;
@@ -209,6 +210,18 @@ public NullnessHint onDataflowVisitMethodInvocation(
209210
if (valueConstraint.equals("_")) {
210211
// do nothing
211212
} else if (valueConstraint.equals("false") || valueConstraint.equals("true")) {
213+
ExpressionTree argumentTree = tree.getArguments().get(i);
214+
Object constValue = ASTHelpers.constValue(argumentTree);
215+
if (constValue instanceof Boolean booleanValue) {
216+
boolean booleanConstraintValue = valueConstraint.equals("true");
217+
if (booleanValue == booleanConstraintValue) {
218+
// Antecedent is satisfied by a compile-time boolean constant
219+
continue;
220+
}
221+
// constant passed is opposite of antecedent
222+
supported = false;
223+
break;
224+
}
212225
// We handle boolean constraints in the case that the boolean argument is the result
213226
// of a null or not-null check. For example,
214227
// '@Contract("true -> true") boolean func(boolean v)'

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

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,149 @@ String test(Object required) {
732732
.doTest();
733733
}
734734

735+
@Test
736+
public void booleanToNotNullContract() {
737+
makeTestHelperWithArgs(
738+
withJSpecifyModeArgs(
739+
Arrays.asList(
740+
"-d",
741+
temporaryFolder.getRoot().getAbsolutePath(),
742+
"-XepOpt:NullAway:OnlyNullMarked=true")))
743+
.addSourceLines(
744+
"Test.java",
745+
"""
746+
import org.jspecify.annotations.NullMarked;
747+
import org.jspecify.annotations.Nullable;
748+
import org.jetbrains.annotations.Contract;
749+
@NullMarked
750+
class Test {
751+
@Contract("false -> !null")
752+
@Nullable String nonNullWhenPassedFalse(boolean returnNull) {
753+
if (returnNull) {
754+
return null;
755+
}
756+
return "foo";
757+
}
758+
@Contract("true -> !null")
759+
@Nullable String nonNullWhenPassedTrue(boolean dontReturnNull) {
760+
if (dontReturnNull) {
761+
return "foo";
762+
}
763+
return null;
764+
}
765+
void testNegative() {
766+
nonNullWhenPassedFalse(false).hashCode();
767+
nonNullWhenPassedTrue(true).hashCode();
768+
}
769+
void testPositive(boolean b) {
770+
// BUG: Diagnostic contains: dereferenced expression
771+
nonNullWhenPassedFalse(true).hashCode();
772+
// false positive expected here since we do not do boolean reasoning
773+
// BUG: Diagnostic contains: dereferenced expression
774+
nonNullWhenPassedFalse(b && !b).hashCode();
775+
// BUG: Diagnostic contains: dereferenced expression
776+
nonNullWhenPassedTrue(false).hashCode();
777+
// false positive expected here since we do not do boolean reasoning
778+
// BUG: Diagnostic contains: dereferenced expression
779+
nonNullWhenPassedTrue(b || !b).hashCode();
780+
}
781+
}
782+
""")
783+
.doTest();
784+
}
785+
786+
@Test
787+
public void multiArgBooleanToNotNullContract() {
788+
makeTestHelperWithArgs(
789+
withJSpecifyModeArgs(
790+
Arrays.asList(
791+
"-d",
792+
temporaryFolder.getRoot().getAbsolutePath(),
793+
"-XepOpt:NullAway:OnlyNullMarked=true")))
794+
.addSourceLines(
795+
"Test.java",
796+
"""
797+
import org.jspecify.annotations.NullMarked;
798+
import org.jspecify.annotations.Nullable;
799+
import org.jetbrains.annotations.Contract;
800+
@NullMarked
801+
class Test {
802+
@Contract("false, _ -> !null")
803+
@Nullable String nonNullWhenFirstFalse(boolean returnNull, @Nullable String value) {
804+
if (returnNull) {
805+
return null;
806+
}
807+
return "foo";
808+
}
809+
@Contract("false, true -> !null")
810+
@Nullable String nonNullWhenFalseTrue(boolean returnNull, boolean forceNonNull) {
811+
if (returnNull || !forceNonNull) {
812+
return null;
813+
}
814+
return "baz";
815+
}
816+
@Contract("_, true -> !null")
817+
@Nullable String nonNullWhenSecondTrue(@Nullable String value, boolean dontReturnNull) {
818+
if (dontReturnNull) {
819+
return "bar";
820+
}
821+
return null;
822+
}
823+
void testNegative() {
824+
nonNullWhenFirstFalse(false, null).hashCode();
825+
nonNullWhenFalseTrue(false, true).hashCode();
826+
nonNullWhenSecondTrue(null, true).hashCode();
827+
}
828+
void testPositive(boolean b) {
829+
// BUG: Diagnostic contains: dereferenced expression
830+
nonNullWhenFirstFalse(true, null).hashCode();
831+
// BUG: Diagnostic contains: dereferenced expression
832+
nonNullWhenFalseTrue(false, false).hashCode();
833+
// BUG: Diagnostic contains: dereferenced expression
834+
nonNullWhenSecondTrue(null, false).hashCode();
835+
}
836+
}
837+
""")
838+
.doTest();
839+
}
840+
841+
@Test
842+
public void multiArgBooleanAndNullToNotNullContract() {
843+
makeTestHelperWithArgs(
844+
withJSpecifyModeArgs(
845+
Arrays.asList(
846+
"-d",
847+
temporaryFolder.getRoot().getAbsolutePath(),
848+
"-XepOpt:NullAway:OnlyNullMarked=true")))
849+
.addSourceLines(
850+
"Test.java",
851+
"""
852+
import org.jspecify.annotations.NullMarked;
853+
import org.jspecify.annotations.Nullable;
854+
import org.jetbrains.annotations.Contract;
855+
@NullMarked
856+
class Test {
857+
@Contract("false, !null -> !null")
858+
@Nullable String nonNullWhenFalseAndNonNull(boolean returnNull, @Nullable Object obj) {
859+
if (returnNull || obj == null) {
860+
return null;
861+
}
862+
return "ok";
863+
}
864+
void testNegative() {
865+
nonNullWhenFalseAndNonNull(false, new Object()).hashCode();
866+
}
867+
void testPositive(boolean b, @Nullable Object obj) {
868+
// BUG: Diagnostic contains: dereferenced expression
869+
nonNullWhenFalseAndNonNull(true, new Object()).hashCode();
870+
// BUG: Diagnostic contains: dereferenced expression
871+
nonNullWhenFalseAndNonNull(false, null).hashCode();
872+
}
873+
}
874+
""")
875+
.doTest();
876+
}
877+
735878
@Test
736879
public void checkNotNullToNotNullContract() {
737880
makeTestHelperWithArgs(

nullaway/src/test/java/com/uber/nullaway/jspecify/GenericLambdaTests.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,31 @@ void testNegative(Foo<String> foo) {
133133
.doTest();
134134
}
135135

136+
@Test
137+
public void issue1448() {
138+
makeHelper()
139+
.addSourceLines(
140+
"Test.java",
141+
"""
142+
package org.example;
143+
144+
import org.jspecify.annotations.NullMarked;
145+
import org.jspecify.annotations.Nullable;
146+
147+
@NullMarked
148+
class Test {
149+
interface Func<T> {
150+
void apply( @Nullable T t );
151+
}
152+
153+
class Bar {
154+
Func<Integer> baz = ( @Nullable Integer i ) -> {};
155+
}
156+
}
157+
""")
158+
.doTest();
159+
}
160+
136161
private CompilationTestHelper makeHelper() {
137162
return makeTestHelperWithArgs(
138163
JSpecifyJavacConfig.withJSpecifyModeArgs(

0 commit comments

Comments
 (0)