diff --git a/infer/tests/codetoanalyze/java/nullsafe/Lambdas.java b/infer/tests/codetoanalyze/java/nullsafe/Lambdas.java new file mode 100644 index 000000000..1fb168ec0 --- /dev/null +++ b/infer/tests/codetoanalyze/java/nullsafe/Lambdas.java @@ -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 { + @Nullable + public R apply(@Nullable T t); + } + + // Helper + @Nullable + private static R nullableApply(@Nullable T t, NullableFunction 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 stream) { + stream.map(s -> acceptNullableStringSuffix(s, "IAmNotNull")); + } + + private void useLambdaNullableFieldCapture_OK(Stream stream) { + stream.map(s -> acceptNullableStringSuffix(s, nullableStringField)); + } + + private void useLambdaNullableLocalCapture_OK(Stream stream) { + String localString = getNullableString(); + stream.map(s -> acceptNullableStringSuffix(s, localString)); + } + + private void useLambdaNullableLocalCaptureToNotNull_BAD_FN(Stream stream) { + String localString = getNullableString(); // @Nullable + stream.map(s -> acceptNotNullString(localString)); // BAM! needs NotNull + } + + private String useLambdaToSetFieldToNull_BAD_FN() { + Consumer 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()); + } + } +} diff --git a/infer/tests/codetoanalyze/java/nullsafe/issues.exp b/infer/tests/codetoanalyze/java/nullsafe/issues.exp index c94894b40..ee0103e7c 100644 --- a/infer/tests/codetoanalyze/java/nullsafe/issues.exp +++ b/infer/tests/codetoanalyze/java/nullsafe/issues.exp @@ -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, , 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).]