/* * 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 android.annotation.SuppressLint; import android.app.Activity; import android.os.Bundle; import android.support.annotation.NonNull; import com.facebook.infer.annotation.Assertions; import com.facebook.infer.annotation.Initializer; import com.facebook.infer.annotation.SuppressFieldNotInitialized; import com.facebook.infer.annotation.SuppressViewNullability; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.inject.Inject; // for butterknife @interface Bind {} public class FieldNotInitialized { // different ways to suppress the error class Suppression { String notNullIsBAD; // BAD: need to initialize it @Nonnull String nonnullIsBAD; // BAD: explicit annotation does not make it better @NonNull String nonNullIsBAD; // BAD: explicit annotation does not make it better @Nullable String nullableIsOK; // OK: will be init with null @SuppressFieldNotInitialized String suppressAnnotationIsOK; // OK: explicitly suppressed @SuppressLint("eradicate-field-not-initialized") String suppressLintIsOK; // OK: explicitly suppressed on lint level @SuppressLint("some-irrelevant-linter") String suppressWrongLintIsBAD; // BAD: this suppression is irrelevant @Inject String injectIsOK; // Means: assume it will be initialized via dependency injection @Bind String bindIsOK; // Means: assume it will be initialized, and ignore null assignment // Means: assume it will be initialized, and ignore null assignment @SuppressViewNullability String suppressViewNullabilityIsOK; Suppression() {} // Ensure that some suppressions suppress only field not initialized // and nothing else, but some suppress setting to null as well. void testNullifyFields() { bindIsOK = null; // OK: the framework could write null into the field suppressViewNullabilityIsOK = null; // OK: the framework could write null into the field nonnullIsBAD = null; // BAD: explicit nonnull annotation does not allow nullifying nonNullIsBAD = null; // BAD: explicit nonnull annotation does not allow nullifying injectIsOK = null; // BAD: inject suppressed only initialization issues suppressAnnotationIsOK = null; // BAD: only initialization issue was suppressed suppressLintIsOK = null; // BAD: only initialization issue was suppressed } } class OnlyRead { Object o; OnlyRead() { Object x = o; // BAD: we merely read this variable, but forgot to initialize } } class WriteItselfIsBAD { Object ok; Object bad; WriteItselfIsBAD() { ok = ""; bad = bad; // BAD: Can not initialize with itself } } class InitializationOrder { Object o1; Object o2; InitializationOrder(int a) { o1 = o2; // BAD: not initialized o2 = new Object(); } InitializationOrder(double a) { o1 = new Object(); // OK o2 = o1; } } class OnlyReadIndirect { Object o1; Object o2; private void indirect() { Object x = o1; // not initialized o2 = new Object(); } OnlyReadIndirect() { indirect(); } } class ShouldInitializeInAllBranches { Object f1; Object f2; Object f3; Object f4; Object f5; public ShouldInitializeInAllBranches(int a) { f4 = new Object(); Object f5 = new Object(); // BAD: shadowing; not an initialization if (a == 42) { f1 = new Object(); f2 = new Object(); } else { f3 = new Object(); if (a == 43) { f1 = new Object(); // BAD: f2 is not initialized in this branch } else { f1 = new Object(); f2 = new Object(); } } } } class InitIfNull { Object good; Object shouldBeGood_FIXME; public InitIfNull() { if (good == null) { good = new Object(); // bad is considered to be initialized not in all branches. // (which is bit weird: we know that good is always null by default) // TODO(T53537343) teach nullsafe that all fields are initially null in constructor shouldBeGood_FIXME = new Object(); } } } class InitCircular { String bad; String stillBad; String good; String knownNotNull = ""; InitCircular() { String tmp = bad; bad = tmp; // BAD: circular initialization stillBad = bad; // BAD: try to initialize from circularly initalized var String tmp2 = knownNotNull; good = tmp2; // OK } } class InitWithOtherClass { class OtherClass { String nonNull = ""; @Nullable String nullable = ""; } String bad; String good; InitWithOtherClass(OtherClass x) { bad = x.nullable; // BAD: might be null good = x.nonNull; // OK: we know can not be null } } // Check that Infer does not confuse things // in copy constuctors. class InitWithTheSameClass { String good; String bad; InitWithTheSameClass(InitWithTheSameClass x) { good = x.good; // OK: this is not a circular initialization bad = bad; // BAD: this is a circular initialization } } // A test to ensure suppressions work on constructor level class Suppressions { String f1; String f2; // BAD: forgot to initialize f2 Suppressions(int a) { f1 = ""; f(null); // Expect to see "parameter not nullable" issue } // Should suppress both field not initialized warning. // But actually suppresses all nullsafe issues. @SuppressLint("eradicate-field-not-initialized") Suppressions(int a, int b) { f(null); // FALSE NEGATIVE: this issue was unintentionally suppressed as well } // This annotation correctly suppresses only needed issues @SuppressFieldNotInitialized Suppressions(int a, int b, int c) { f(null); // Expect to see "parameter not nullable" issue - it should NOT be suppressed } void f(String a) {} } } /** * There is a predefined list of classes which have known methods that act like initializers. If a * class extends such special class and initializes a field in such whitelisted method, we don't * require initializing this field in constructor. (NOTE: To do the same in non whitelisted class * one can use @Initializer annotation instead). */ class TestKnownInitializers { // nullsafe knows that Activity.onCreate is a special initilizer. class KnownInitializers extends Activity { String initInConstructorIsOK; String initInSpecialMethodIsOK; String initInUnknownMethodIsBAD; KnownInitializers() { initInConstructorIsOK = ""; } // onCreate is a special method protected void onCreate(Bundle bundle) { initInSpecialMethodIsOK = ""; } protected void someOtherMethod(Bundle bundle) { // BAD: This method is unknown (and does not have @Initializer annotation either). initInUnknownMethodIsBAD = ""; } } abstract class FakeActivity { abstract void onCreate(Bundle bundle); } class SimplyOnCreateWontDoATrick extends FakeActivity { String initInUnknownMethodIsBAD; // Though we use onCreate method, the class does not extend one of the known // classes that are known to have such a property, so it does not count. protected void onCreate(Bundle bundle) { // BAD: This method is unknown (and does not have @Initializer annotation either). initInUnknownMethodIsBAD = ""; } } } /** * If a method is marked with @Initializer annotation, we essentially treat is as a constuctror: if * a field is initialized in one of such methods, we assume this method will be called before using * the field, so we don't consider it "not initialized" error. A popular usecase for that is a * Builder pattern, when required fields are set not in the constuctor, but in corresponding * setters, and then build() method checks in runtime that all fields are initialized. */ class TestInitializerAnnotation { String initInConstructorIsOK; String initInInitilizerMethod1IsOK; String initInInitilizerMethod2IsOK; String initInAnyOtherMethodIsBAD; String initByNullableInInitializedMethodIsBAD; String dontInitAtAllIsBAD; @Nullable String dontInitOptionalIsOK; TestInitializerAnnotation() { initInConstructorIsOK = ""; } @Initializer void set1(String value) { // OK: we assume set1() will be called before the class is actually used this.initInInitilizerMethod1IsOK = value; } @Initializer void set2(String value) { // OK: we assume set2() will be called before the class is actually used this.initInInitilizerMethod2IsOK = value; } void set3(String value) { // BAD: though the field is initialized here, set3 is not marked as @Initialized this.initInAnyOtherMethodIsBAD = value; } @Initializer void set4(@Nullable String value) { // BAD: method is marked as @Initializer, but the value can be null this.initByNullableInInitializedMethodIsBAD = value; } // Example of a typical usecase: // a build() method that is supposed to be called before the class is used. Object build() { // Fail hard if the required fields are not initialzed. // Unfortunately, this will lead to "condition redundant" warnings, despite the fact // that checking for this makes total sense. // TODO(T53531699) don't issue "condition redundant" warning in this case. Assertions.assertCondition( initInInitilizerMethod1IsOK != null && initInInitilizerMethod2IsOK != null); // ... do some stuff // return some meaninful object return ""; } abstract class TestFieldNotInitializedBase { @Initializer protected abstract void markedInitializerInBase(); protected abstract void notMarkedInitializerInBase1(); protected abstract void notMarkedInitializerInBase2(); } // Test to ensure we respect parent @Initializer annotations class TestFieldNotInitializedDerived extends TestFieldNotInitializedBase { private String field1_OK; private String field2_BAD; private String field3_OK; @Override public void markedInitializerInBase() { // OK: implicitly @Initializer (inherited from the base) field1_OK = ""; } @Override public void notMarkedInitializerInBase1() { // BAD: field not initialized field2_BAD = ""; } @Override @Initializer public void notMarkedInitializerInBase2() { // OK: explicitly marked as an initializer field3_OK = ""; } } // Ensure that chains with non-trivial length work as well class TestFieldNotInitializedDerivedDerived extends TestFieldNotInitializedDerived { private String field1_OK; private String field2_BAD; private String field3_OK; @Override public void markedInitializerInBase() { // OK: implicitly @Initializer (inherited from the base through the chain) field1_OK = ""; } @Override public void notMarkedInitializerInBase1() { // BAD: field not initialized, and the method is not marked as @Initializer in any of the // bases field2_BAD = ""; } @Override public void notMarkedInitializerInBase2() { // OK: explicitly marked as an initializer in the direct base field3_OK = ""; } } }