/* * 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_default; import android.text.TextUtils; import com.facebook.infer.annotation.FalseOnNull; import com.facebook.infer.annotation.TrueOnNull; import com.google.common.base.Strings; import javax.annotation.Nullable; /** Testing functionality related to @TrueOnNull and @FalseOnNull methods */ public class TrueFalseOnNull { static class StaticOneParam { @TrueOnNull static boolean trueOnNull(@Nullable Object o) { return o == null ? true : o.toString() == "something"; } @FalseOnNull static boolean falseOnNull(@Nullable Object o) { return o == null ? false : o.toString() == "something"; } static boolean notAnnotated(@Nullable Object o) { return o == null ? false : o.toString() == "something"; } } static class NonStaticOneParam { private String compareTo = "something"; @TrueOnNull boolean trueOnNull(@Nullable Object o) { return o == null ? true : o.toString() == compareTo; } @FalseOnNull boolean falseOnNull(@Nullable Object o) { return o == null ? false : o.toString() == compareTo; } boolean notAnnotated(@Nullable Object o) { return o == null ? false : o.toString() == compareTo; } } // @TrueOnNull and @FalseOnNull should expect true/false will be returned if ANY of input objects // is null. // In other words, they should infer that all input nullable objects are non-null in the // corresponding branch. static class NonStaticSeveralParams { private String compareTo = "something"; @TrueOnNull boolean trueOnNull( @Nullable Object nullable1, int primitive, Object nonnull, @Nullable Object nullable2) { if (nullable1 == null || nullable2 == null) { return true; } return nonnull == compareTo; } @FalseOnNull boolean falseOnNull( @Nullable Object nullable1, int primitive, Object nonnull, @Nullable Object nullable2) { if (nullable1 == null || nullable2 == null) { return false; } return nonnull == compareTo; } boolean notAnnotated( @Nullable Object nullable1, int primitive, Object nonnull, @Nullable Object nullable2) { if (nullable1 == null || nullable2 == null) { return false; } return nonnull == compareTo; } } class TestStaticOneParam { void trueOnNullPositiveBranchIsBAD(@Nullable String s) { if (StaticOneParam.trueOnNull(s)) { s.toString(); } } void trueOnNullNegativeBranchIsOK(@Nullable String s) { if (!StaticOneParam.trueOnNull(s)) { s.toString(); } } void falseOnNullPositiveBranchIsOK(@Nullable String s) { if (StaticOneParam.falseOnNull(s)) { s.toString(); } } void falseOnNullNegativeBranchIsBAD(@Nullable String s) { if (!StaticOneParam.falseOnNull(s)) { s.toString(); } } void notAnnotatedPositiveBranchIsBAD(@Nullable String s) { if (StaticOneParam.notAnnotated(s)) { s.toString(); } } void notAnnotatedNegativeBranchIsBAD(@Nullable String s) { if (!StaticOneParam.notAnnotated(s)) { s.toString(); } } } class TestNonStaticOneParam { private NonStaticOneParam object = new NonStaticOneParam(); void trueOnNullPositiveBranchIsBAD(@Nullable String s) { if (object.trueOnNull(s)) { s.toString(); } } void trueOnNullNegativeBranchIsOK(@Nullable String s) { if (!object.trueOnNull(s)) { s.toString(); } } void falseOnNullPositiveBranchIsOK(@Nullable String s) { if (object.falseOnNull(s)) { s.toString(); } } void falseOnNullNegativeBranchIsBAD(@Nullable String s) { if (!object.falseOnNull(s)) { s.toString(); } } void notAnnotatedPositiveBranchIsBAD(@Nullable String s) { if (object.notAnnotated(s)) { s.toString(); } } void notAnnotatedNegativeBranchIsBAD(@Nullable String s) { if (!object.notAnnotated(s)) { s.toString(); } } } class TestNonStaticSeveralParams { private NonStaticSeveralParams object = new NonStaticSeveralParams(); void trueOnNullPositiveBranchIsBAD(@Nullable String s1, @Nullable String s2) { if (object.trueOnNull(s1, 1, "123", s2)) { s1.toString(); s2.toString(); } } void trueOnNullNegativeBranchIsOK(@Nullable String s1, @Nullable String s2) { if (!object.trueOnNull(s1, 1, "123", s2)) { s1.toString(); s2.toString(); } } void falseOnNullPositiveBranchIsOK(@Nullable String s1, @Nullable String s2) { if (object.falseOnNull(s1, 1, "123", s2)) { s1.toString(); s2.toString(); } } void falseOnNullNegativeBranchIsBAD(@Nullable String s1, @Nullable String s2) { if (!object.falseOnNull(s1, 1, "123", s2)) { s1.toString(); s2.toString(); } } void notAnnotatedPositiveBranchIsBAD(@Nullable String s1, @Nullable String s2) { if (object.notAnnotated(s1, 1, "123", s2)) { s1.toString(); s2.toString(); } } void notAnnotatedNegativeBranchIsBAD(@Nullable String s1, @Nullable String s2) { if (!object.notAnnotated(s1, 1, "123", s2)) { s1.toString(); s2.toString(); } } } class TestModels { void testModelledTrueOnNull(@Nullable String s) { // TextUtils.isEmpty is modelled as TrueOnNull if (!TextUtils.isEmpty(s)) { s.toString(); // OK: if we are here, we know that `s` is not null } // Strings.isNullOrEmpty is modelled as TrueOnNull if (!Strings.isNullOrEmpty(s)) { s.toString(); // OK: if we are here, we know that `s` is not null } } } // nullsafe should assume Object.equals() and all overrides return false on `null` argument. static class TestEqualsIsFalseOnNull { // An example of class that overrides equals(). static class SomeObject { private int mContent; SomeObject(int content) { mContent = content; } @Override // No nullsafe warnings are expected public boolean equals(@Nullable Object src) { if (!super.equals(src)) { return false; } // at this point src should be a non-nullable: super.equals() would return false otherwise. src.toString(); // should be OK to dereference now if (!(src instanceof SomeObject)) { return false; } SomeObject asSomeObject = (SomeObject) src; return mContent == asSomeObject.mContent; } } private void testObjectEqualsIsFalseOnNull(Object x, @Nullable Object y) { if (!x.equals(y)) { return; } // OK to dereference y.toString(); } private void testOverrideEqualsIsFalseOnNull(SomeObject x, @Nullable Object y) { if (!x.equals(y)) { return; } // OK to dereference even that SomeObject.equals() is not annotated as @FalseOnNull y.toString(); } } // this is a common enough pattern to be tested separately class EarlyReturn { void testEarlyReturn(@Nullable CharSequence s1, @Nullable CharSequence s2) { if (StaticOneParam.trueOnNull(s1) || StaticOneParam.notAnnotated(s2)) { return; } s1.toString(); // OK: if `s1` was null, we would no rech this point s2.toString(); // BAD: no reason for `s2` to become non-nullable } } }