You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

273 lines
6.5 KiB

/*
* 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.app.Activity;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.widget.EditText;
import com.facebook.infer.annotation.Assertions;
import com.facebook.infer.annotation.Initializer;
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 {
String a;
@Nullable String b;
@Nonnull String c; // Means: assume it will be initialized to a nonnull value somewhere else.
@Inject String d; // Means: assume it will be initialized via dependency injection
@NonNull String e;
@Bind EditText f; // Means: assume it will be initialized, and ignore null assignment
@SuppressViewNullability EditText g;
// Eradicate should only report one initialization error
FieldNotInitialized() {}
void testNullifyFields() {
f = null; // OK the framework could write null into the field
g = null; // OK the framework could write null into the field
}
class OnlyRead {
Object o;
OnlyRead() {
Object x = o; // not initialized
}
}
class WriteItself {
Object o;
WriteItself() {
o = o; // not initialized
}
}
class Swap {
Object o1;
Object o2;
Swap() {
o1 = o2; // not initialized
o2 = new Object();
}
}
class SwapOK {
Object o1;
Object o2;
SwapOK() {
o1 = new Object();
o2 = o1;
}
}
class OnlyReadIndirect {
Object o1;
Object o2;
private void indirect() {
Object x = o1; // not initialized
o2 = new Object();
}
OnlyReadIndirect() {
indirect();
}
}
class ConditionalFieldInit {
Object o1;
@Nullable Object o2 = null;
public ConditionalFieldInit() {
if (o2 != null) {
o1 = new Object(); // Not always initialized
}
}
}
class InitIfNull {
Object o;
public InitIfNull() {
if (o == null) o = new Object();
}
}
class InitIfNull2 {
Object o;
public InitIfNull2(Object x) {
if (o == null) o = x;
}
}
class InitIfNull3 {
Object o;
Object getNotNull() {
return new Object();
}
public InitIfNull3() {
if (o == null) o = getNotNull();
}
}
class InitCircular {
String s;
InitCircular() {
String tmp = s;
s = tmp; // s is not initialized: circular initialization
}
}
class InitWithOtherClass {
class OtherClass {
String s = "";
}
String s;
InitWithOtherClass(OtherClass x) {
s = x.s;
}
}
class InitWithThisClass {
String s;
InitWithThisClass(InitWithThisClass x) {
s = x.s;
}
}
}
/**
* 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 "";
}
}