|
|
|
/*
|
|
|
|
* 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.v4.app.Fragment;
|
|
|
|
import com.facebook.infer.annotation.Assertions;
|
|
|
|
import com.facebook.infer.annotation.Cleanup;
|
|
|
|
import com.facebook.infer.annotation.Initializer;
|
|
|
|
import javax.annotation.Nullable;
|
|
|
|
|
|
|
|
abstract class A {
|
|
|
|
final String fld;
|
|
|
|
|
|
|
|
A(String s) {
|
|
|
|
this.fld = s;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* It is common in Android code to recycle the objects (e.g. views) by nullifying them in the
|
|
|
|
* onDestroy() or onDestroyView() methods. This allows the GC to recycle it not waiting the outer
|
|
|
|
* object to be freed. This is safe because the lifecycle of the object is over so those field are
|
|
|
|
* not going to be accessed. so it is not necessary to annotate those fields with @Nullable.
|
|
|
|
*/
|
|
|
|
class CanAssignNullInDestroyMethods extends Fragment {
|
|
|
|
|
|
|
|
String someObject = "";
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onDestroyView() {
|
|
|
|
someObject = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onDestroy() {
|
|
|
|
someObject = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void assignNullDissalowedInAnyOtherMethod() {
|
|
|
|
someObject = null; // BAD: field not nullable
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public class FieldNotNullable extends A {
|
|
|
|
@Nullable String x;
|
|
|
|
String y;
|
|
|
|
String fld; // Shadow the field defined in A
|
|
|
|
String static_s = null; // Static initializer error
|
|
|
|
|
|
|
|
FieldNotNullable(String s) {
|
|
|
|
super(s);
|
|
|
|
x = null;
|
|
|
|
y = s;
|
|
|
|
this.fld = s;
|
|
|
|
}
|
|
|
|
|
|
|
|
void setXNull() {
|
|
|
|
x = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
void setXNullable(@Nullable String s) {
|
|
|
|
x = s;
|
|
|
|
}
|
|
|
|
|
|
|
|
void setYNull() {
|
|
|
|
y = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
void setYNullable(@Nullable String s) {
|
|
|
|
y = s;
|
|
|
|
}
|
|
|
|
|
|
|
|
FieldNotNullable(Integer n) {
|
|
|
|
super("");
|
|
|
|
this.fld = "";
|
|
|
|
y = x == null ? "abc" : "def";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class MixedInitializers extends Activity {
|
|
|
|
|
|
|
|
private String field1 = "1";
|
|
|
|
private String field2;
|
|
|
|
private String field3;
|
|
|
|
|
|
|
|
MixedInitializers() {
|
|
|
|
field2 = "2";
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void onCreate(Bundle bundle) {
|
|
|
|
field3 = "3";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class TestInitializerBuilder {
|
|
|
|
String required1;
|
|
|
|
String required2;
|
|
|
|
@Nullable String optional;
|
|
|
|
|
|
|
|
// No FIELD_NOT_INITIALIZED error should be reported, because of the @Initializer annotations.
|
|
|
|
TestInitializerBuilder() {}
|
|
|
|
|
|
|
|
// This is an initializer and must always be called before build().
|
|
|
|
@Initializer
|
|
|
|
TestInitializerBuilder setRequired1(String required1) {
|
|
|
|
this.required1 = required1;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
// This is an initializer and must always be called before build().
|
|
|
|
@Initializer
|
|
|
|
TestInitializerBuilder setRequired2(String required2) {
|
|
|
|
this.required2 = required2;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
TestInitializerBuilder setOptional(String optional) {
|
|
|
|
this.optional = optional;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
TestInitializer build() {
|
|
|
|
// Fail hard if the required fields are not initialzed
|
|
|
|
Assertions.assertCondition(required1 != null && required2 != null);
|
|
|
|
|
|
|
|
return new TestInitializer(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Cleanup
|
|
|
|
void testCleanup() {
|
|
|
|
this.required1 = null;
|
|
|
|
this.required2 = null;
|
|
|
|
this.optional = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class TestInitializer {
|
|
|
|
String required1; // should always be set
|
|
|
|
String required2; // should always be set
|
|
|
|
@Nullable String optional; // optionally set
|
|
|
|
|
|
|
|
TestInitializer(TestInitializerBuilder b) {
|
|
|
|
required1 = b.required1;
|
|
|
|
required2 = b.required2;
|
|
|
|
optional = b.optional;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void testInitializerClientA() {
|
|
|
|
TestInitializerBuilder b = new TestInitializerBuilder();
|
|
|
|
b.setRequired1("hello");
|
|
|
|
b.setRequired2("world");
|
|
|
|
TestInitializer x = b.build();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void testInitializerClientB() {
|
|
|
|
TestInitializerBuilder b = new TestInitializerBuilder();
|
|
|
|
b.setRequired1("a");
|
|
|
|
b.setRequired2("b");
|
|
|
|
b.setOptional("c");
|
|
|
|
TestInitializer x = b.build();
|
|
|
|
}
|
|
|
|
}
|