[nullsafe] Add tests around support for Java Lambdas

Summary:
Current handling of lambdas is quite rudimentary. Looking at test
results we can see that errors are all over the place: False Positives,
False Negatives and just plain wrong results.

These tests can be grouped in 2 sets:

1. Basic support which implies:
   - understanding method signatures,
   - providing comprehensible error messages.
2. Extended support with implies:
    - understanding scoping of values captures in lambdas (needs proper aliasing analysis).
    - understanding parametric nullability in generics (needs "some"
    support for Generics in our Java frontend).

With follow-up patches I'll attempt to implement "Basic" support for
Lambdas. "Extended" support will be out of scope unless there's
significant demand.

Reviewed By: mityal

Differential Revision: D23058673

fbshipit-source-id: 621551cca
master
Artem Pianykh 4 years ago committed by Facebook GitHub Bot
parent 570191741a
commit e4a7d1f19d

@ -0,0 +1,147 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package codetoanalyze.java.nullsafe;
import com.facebook.infer.annotation.Nullsafe;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.util.Random;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;
import javax.annotation.Nullable;
public class Lambdas {
// Helper
@Nullable private String nullableStringField;
private String notNullStringField = "NotNull";
// Helper
static interface NullableFunction<T, R> {
@Nullable
public R apply(@Nullable T t);
}
// Helper
@Nullable
private static <T, R> R nullableApply(@Nullable T t, NullableFunction<T, R> f) {
return f.apply(t);
}
// Helper
static interface NullableCallbackWithDefaultMethods {
default void onSuccess() {
// no-op
}
void onFailure(@Nullable Integer statusCode);
}
// Helper
private static void acceptNullableCallback(NullableCallbackWithDefaultMethods cb) {
cb.onFailure(null);
}
// Helper
private static String acceptNullableStringSuffix(String s, @Nullable String suffix) {
return suffix == null ? s : suffix;
}
// Helper
private static String acceptNotNullString(String s) {
return s;
}
// Helper
@Nullable
private static String getNullableString() {
return new Random().nextBoolean() ? "NotNull" : null;
}
private void useLambdaAllNotNull_OK(Stream<String> stream) {
stream.map(s -> acceptNullableStringSuffix(s, "IAmNotNull"));
}
private void useLambdaNullableFieldCapture_OK(Stream<String> stream) {
stream.map(s -> acceptNullableStringSuffix(s, nullableStringField));
}
private void useLambdaNullableLocalCapture_OK(Stream<String> stream) {
String localString = getNullableString();
stream.map(s -> acceptNullableStringSuffix(s, localString));
}
private void useLambdaNullableLocalCaptureToNotNull_BAD_FN(Stream<String> stream) {
String localString = getNullableString(); // @Nullable
stream.map(s -> acceptNotNullString(localString)); // BAM! needs NotNull
}
private String useLambdaToSetFieldToNull_BAD_FN() {
Consumer<String> f =
s -> {
if (s.length() < 42) {
notNullStringField = null; // BAM!
}
};
f.accept("TheString");
return notNullStringField;
}
@Nullable
private Integer useLambdaWithNullableInterface_BAD_WRONG(String paramString) {
// should be "nullable dereference" here, while now it's "inconsistent subclass param"
return nullableApply(paramString, lambdaString -> lambdaString.length());
}
@Nullable
private Integer useLambdaWithExplicitParamTypes_BAD(String paramString) {
// expected "inconsistent subclass annotation" here, since parent defined param as @Nullable
return nullableApply(paramString, (String lambdaString) -> lambdaString.length());
}
@Nullable
private Integer useLambdaWithExplicitParamTypesAndNullability_BAD_WRONG(String paramString) {
// should flag "nullable derefernce"; instead raises "inconsistent subclass"
// because it doesn't see the @Nullable annotation on the lambda's param
return nullableApply(paramString, (@Nullable String lambdaString) -> lambdaString.length());
}
private void useLambdaForInterfaceWithDefaultMethods_OK_FP() {
acceptNullableCallback(statusCode -> System.out.println(statusCode)); // should be OK
}
// Following tests demonstrates some "desired" behaviours of typechecker in
// "lambda pipelines". However, those will require significant investment to
// implement.
// Currently, it reports nothing because we lack a model for java.util.function.Function
private void useLambdasInStreamPipeline_WRONG() {
Stream.generate(() -> getNullableString())
.peek(s -> System.out.println(s.length())) // s is @Nullable => nullable dereference!
.flatMap(
s -> s == null ? Stream.empty() : Stream.of(s)) // should be a stream of notnull strings
.forEach(s -> System.out.println(s.length())); // fine
}
// Helper
// Starting with Java 8 annotations can be applied to type parameters.
// Both JSR-305's @Nullable and Android's @Nullable however lack necessary
// @Target qualifier, so we define our own here.
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
public @interface MyNullable {};
@Nullsafe(Nullsafe.Mode.LOCAL)
static class NullsafeClass {
// Cannot reasonably use without some tricky internal model
private String useJavaUtilFunction_UNSUPPORTED(Function<@MyNullable String, String> f) {
return f.apply(getNullableString());
}
}
}

@ -121,6 +121,13 @@ codetoanalyze/java/nullsafe/InheritanceForStrictMode.java, codetoanalyze.java.nu
codetoanalyze/java/nullsafe/InheritanceForStrictMode.java, codetoanalyze.java.nullsafe.InheritanceForStrictMode$StrictExtendingStrict.badToAddNullableInChildren():java.lang.String, 0, ERADICATE_INCONSISTENT_SUBCLASS_RETURN_ANNOTATION, no_bucket, ERROR, [Child method `InheritanceForStrictMode$StrictExtendingStrict.badToAddNullableInChildren()` is not substitution-compatible with its parent: the return type is declared as nullable, but parent method `InheritanceForStrictMode$StrictBase.badToAddNullableInChildren()` is missing `@Nullable` declaration. Either mark the parent as `@Nullable` or ensure the child does not return `null`.]
codetoanalyze/java/nullsafe/InheritanceForStrictMode.java, codetoanalyze.java.nullsafe.InheritanceForStrictMode$StrictExtendingStrict.params(java.lang.String,java.lang.String):void, 0, ERADICATE_INCONSISTENT_SUBCLASS_PARAMETER_ANNOTATION, no_bucket, ERROR, [First parameter `badToRemoveNullableInChildren` of method `InheritanceForStrictMode$StrictExtendingStrict.params(...)` is missing `@Nullable` declaration when overriding `InheritanceForStrictMode$StrictBase.params(...)`. The parent method declared it can handle `null` for this param, so the child should also declare that.]
codetoanalyze/java/nullsafe/JunitExample.java, Linters_dummy_method, 9, ERADICATE_META_CLASS_CAN_BE_NULLSAFE, no_bucket, ADVICE, [Congrats! `JunitExample` is free of nullability issues. Mark it `@Nullsafe(Nullsafe.Mode.LOCAL)` to prevent regressions.], JunitExample, <no package>, issues: 0, curr_mode: "Default", promote_mode: "LocalTrustAll"
codetoanalyze/java/nullsafe/Lambdas.java, Linters_dummy_method, 19, ERADICATE_META_CLASS_NEEDS_IMPROVEMENT, no_bucket, INFO, [], Lambdas, codetoanalyze.java.nullsafe, issues: 6, curr_mode: "Default"
codetoanalyze/java/nullsafe/Lambdas.java, codetoanalyze.java.nullsafe.Lambdas$Lambda$_22_0.onFailure(java.lang.Integer):void, 0, ERADICATE_INCONSISTENT_SUBCLASS_PARAMETER_ANNOTATION, no_bucket, WARNING, [First parameter `$bcvar1` of method `Lambdas$Lambda$_22_0.onFailure(...)` is missing `@Nullable` declaration when overriding `Lambdas$NullableCallbackWithDefaultMethods.onFailure(...)`. The parent method declared it can handle `null` for this param, so the child should also declare that.]
codetoanalyze/java/nullsafe/Lambdas.java, codetoanalyze.java.nullsafe.Lambdas$Lambda$_27_1.apply(java.lang.Object):java.lang.Object, 0, ERADICATE_INCONSISTENT_SUBCLASS_PARAMETER_ANNOTATION, no_bucket, WARNING, [First parameter `$bcvar1` of method `Lambdas$Lambda$_27_1.apply(...)` is missing `@Nullable` declaration when overriding `Lambdas$NullableFunction.apply(...)`. The parent method declared it can handle `null` for this param, so the child should also declare that.]
codetoanalyze/java/nullsafe/Lambdas.java, codetoanalyze.java.nullsafe.Lambdas$Lambda$_28_1.apply(java.lang.Object):java.lang.Object, 0, ERADICATE_INCONSISTENT_SUBCLASS_PARAMETER_ANNOTATION, no_bucket, WARNING, [First parameter `$bcvar1` of method `Lambdas$Lambda$_28_1.apply(...)` is missing `@Nullable` declaration when overriding `Lambdas$NullableFunction.apply(...)`. The parent method declared it can handle `null` for this param, so the child should also declare that.]
codetoanalyze/java/nullsafe/Lambdas.java, codetoanalyze.java.nullsafe.Lambdas$Lambda$_29_1.apply(java.lang.Object):java.lang.Object, 0, ERADICATE_INCONSISTENT_SUBCLASS_PARAMETER_ANNOTATION, no_bucket, WARNING, [First parameter `$bcvar1` of method `Lambdas$Lambda$_29_1.apply(...)` is missing `@Nullable` declaration when overriding `Lambdas$NullableFunction.apply(...)`. The parent method declared it can handle `null` for this param, so the child should also declare that.]
codetoanalyze/java/nullsafe/Lambdas.java, codetoanalyze.java.nullsafe.Lambdas$NullsafeClass.useJavaUtilFunction_UNSUPPORTED(java.util.function.Function):java.lang.String, 1, ERADICATE_PARAMETER_NOT_NULLABLE, no_bucket, ERROR, [Third-party `Function.apply(...)` is missing a signature that would allow passing a nullable to param #1. Actual argument `getNullableString()` is nullable. Consider adding the correct signature of `Function.apply(...)` to nullsafe/third-party-signatures/java.sig.]
codetoanalyze/java/nullsafe/Lambdas.java, codetoanalyze.java.nullsafe.Lambdas$NullsafeClass.useJavaUtilFunction_UNSUPPORTED(java.util.function.Function):java.lang.String, 1, ERADICATE_UNVETTED_THIRD_PARTY_IN_NULLSAFE, no_bucket, ERROR, [`Function.apply(...)`: `@NullsafeLocal(trust=all)` mode prohibits using values coming from not vetted third party methods without a check. Result of this call is used at line 143. Either add a local check for null or assertion, or add the correct signature to nullsafe/third-party-signatures/java.sig.]
codetoanalyze/java/nullsafe/LibraryCalls.java, Linters_dummy_method, 16, ERADICATE_META_CLASS_NEEDS_IMPROVEMENT, no_bucket, INFO, [], LibraryCalls, codetoanalyze.java.nullsafe, issues: 5, curr_mode: "Default"
codetoanalyze/java/nullsafe/LibraryCalls.java, codetoanalyze.java.nullsafe.LibraryCalls.badAtomicReferenceDereference(java.util.concurrent.atomic.AtomicReference):java.lang.String, 1, ERADICATE_NULLABLE_DEREFERENCE, no_bucket, WARNING, [`ref.get()` is nullable and is not locally checked for null when calling `toString()`: call to AtomicReference.get() at line 35 (nullable according to nullsafe internal models).]
codetoanalyze/java/nullsafe/LibraryCalls.java, codetoanalyze.java.nullsafe.LibraryCalls.badPhantomReferenceDereference(java.lang.ref.PhantomReference):java.lang.String, 1, ERADICATE_NULLABLE_DEREFERENCE, no_bucket, WARNING, [`ref.get()` is nullable and is not locally checked for null when calling `toString()`: call to PhantomReference.get() at line 27 (nullable according to nullsafe internal models).]

Loading…
Cancel
Save