|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
[nullsafe] Prepare to introduce gradual mode: split tests
Summary:
In next diff, we are going to introduce a new mode of nullsafe
(gradual). For testing, we are going to employ the strategy used by jvillard
for Pulse.
In this diff we split tests into two subfolders, one for the default and one for the gradual
mode.
We are planning to make the gradual mode default eventually. For that, most
new features will make sense for gradual mode, and we will mostly evolve
tests for that mode.
As for 'default' mode, we need to preserve tests mostly to ensure we don't introduce
regressions.
Occasionally, we might make changes that make sense for both modes, in
this (expected relatively rare) cases we will make changes to both set
of tests.
An alternative strategy would be to have two sets of issues.exp files,
one for gradual and one for default mode. This has an advantage of each
java file to be always tested twice, but disadvantage is that it will be
harder to write meaningful test code so that it makes sense for both
modes simultaneously.
Reviewed By: ngorogiannis
Differential Revision: D17156724
fbshipit-source-id: a92a9208f
5 years ago
|
|
|
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 "";
|
|
|
|
}
|
|
|
|
}
|