Summary: See motivation below. This diff is dealing with FieldNotNullable: - move not relevant subclasses into dedicated classes and files - modify the tests so they comply with the standards below --Motivation-- Gradual mode we are going to introduce is an invasive change in how Infer treats nullability semantics. In order to make the change in a controllable way, we need the tests to comply with the following standards and conventions. 1. For each code peace where we expect a bug to happen, the there should be corresponding (minimally different from above) peace of code where we expect a bug to NOT happen. (This is to ensure bug is happening for exact reason we think it is happening). 2. Conversely: for each peace of code where we expect a bug to be NOT present, there shuold be a peace of code where the bug IS happening. (Otherwise there can be too many reasons for a bug NOT to happen). 3. Convention: end corresponding methods IsOK and IsBUG correspondingly. 4. Keep code examples as small as possible. Reviewed By: ngorogiannis Differential Revision: D17183222 fbshipit-source-id: 83d03e67fmaster
parent
3250ff35d2
commit
8add080e4a
@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* 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 javax.annotation.Nullable;
|
||||||
|
|
||||||
|
@interface InjectView {}
|
||||||
|
|
||||||
|
/** Support assignments of null to @InjectView fields, generated by butterknife. */
|
||||||
|
public class ButterKnife {
|
||||||
|
@InjectView String injected;
|
||||||
|
String normal = ""; // assign to suppress not initialized warning
|
||||||
|
@Nullable String nullable = ""; // assign to suppress not initialized warning
|
||||||
|
|
||||||
|
void f(String nonNullable) {}
|
||||||
|
|
||||||
|
// When dereferencing, injected should behave as not nullable
|
||||||
|
|
||||||
|
void dereferencingInjectedIsOK() {
|
||||||
|
int n = injected.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
void dereferencingNormalIsOK() {
|
||||||
|
int n = normal.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
void dereferencingNullableIsBAD() {
|
||||||
|
int n = nullable.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
// When returning a value, injected should be treated as non nullable
|
||||||
|
|
||||||
|
String convertingToNotNullableForInjectedIsOK() {
|
||||||
|
return injected;
|
||||||
|
}
|
||||||
|
|
||||||
|
String convertingToNotNullableForNormalIsOK() {
|
||||||
|
return normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
String convertingToNotNullableForNullableIsBAD() {
|
||||||
|
return nullable;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When passing to a non nullable param, injected should be treated as non nullable
|
||||||
|
|
||||||
|
void passingToNullableForInjectedIsOK() {
|
||||||
|
f(injected);
|
||||||
|
}
|
||||||
|
|
||||||
|
void passingToNullableForNormalIsOK() {
|
||||||
|
f(normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
void passingToNullableForNullableIsBAD() {
|
||||||
|
f(nullable);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assigning null to Injected should be allowed (as if it was nullable)
|
||||||
|
// (Those assignments are generated by Butterknife framework.)
|
||||||
|
|
||||||
|
void assignNullToInjectedIsOK() {
|
||||||
|
injected = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void assignNullToNullableIsOK() {
|
||||||
|
nullable = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void assignNullToNormalIsBAD() {
|
||||||
|
normal = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestNotInitialized {
|
||||||
|
@InjectView String notInitializedInjectedIsOK;
|
||||||
|
@Nullable String notInitializedNullableIsOK;
|
||||||
|
String notInitializedNormalIsBAD;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* 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 javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/** Nullability checks for captured params */
|
||||||
|
public class CapturedParam {
|
||||||
|
|
||||||
|
void dereferencingNullableIsBAD(@Nullable Object parameter) {
|
||||||
|
parameter.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void dereferencingCapturedNullableShouldBeBAD_FIXME(@Nullable Object parameter) {
|
||||||
|
Object object =
|
||||||
|
new Object() {
|
||||||
|
void foo() {
|
||||||
|
// Should be disallowed, but it is not the case
|
||||||
|
// TODO(T53473076) fix the FN.
|
||||||
|
parameter.toString();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,140 @@
|
|||||||
|
/*
|
||||||
|
* 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 javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/** Check how we model the behavior of Map nullability */
|
||||||
|
public class MapNullability {
|
||||||
|
|
||||||
|
class TestThatGetIsAllowedOnlyAfterContainsKeyWasChecked {
|
||||||
|
|
||||||
|
void usingGetAfterKeyWasCheckedIsOK(java.util.Map<Integer, String> m) {
|
||||||
|
if (m.containsKey(3)) {
|
||||||
|
m.get(3).isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void usingGetWithoutCheckingKeyIsBAD(java.util.Map<Integer, String> m) {
|
||||||
|
m.get(3).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void usingGetAfterWrongKeyWasCheckedIsBAD(java.util.Map<Integer, String> m) {
|
||||||
|
if (m.containsKey(3)) {
|
||||||
|
m.get(4).isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void usingGetAfterKeyWasCheckedInWhileLoopIsOK(java.util.Map<Integer, String> m) {
|
||||||
|
while (true) {
|
||||||
|
if (m.containsKey(3)) {
|
||||||
|
m.get(3).isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void usingGetAfterWrongKeyWasCheckedInWhileLoopIsBAD(java.util.Map<Integer, String> m) {
|
||||||
|
while (true) {
|
||||||
|
if (m.containsKey(3)) {
|
||||||
|
m.get(4).isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void immutableMap_usingGetAfterKeyWasCheckedIsOK(
|
||||||
|
com.google.common.collect.ImmutableMap<Integer, String> m) {
|
||||||
|
if (m.containsKey(3)) {
|
||||||
|
m.get(3).isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void immutableMap_usingGetAfterWrongKeyWasCheckedIsBAD(
|
||||||
|
com.google.common.collect.ImmutableMap<Integer, String> m) {
|
||||||
|
if (m.containsKey(3)) {
|
||||||
|
m.get(4).isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestThatGetAfterPutIsAllowed {
|
||||||
|
String dontAssignNull = "";
|
||||||
|
|
||||||
|
public void getAfterPutIsOK(java.util.Map<String, String> map, String key) {
|
||||||
|
map.put(key, "abc");
|
||||||
|
dontAssignNull = map.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getWithoutPutIsBAD(java.util.Map<String, String> map, String key) {
|
||||||
|
dontAssignNull = map.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getAfterPutWrongKeyIsBAD(
|
||||||
|
java.util.Map<String, String> map, String key, String wrongKey) {
|
||||||
|
map.put(key, "abc");
|
||||||
|
dontAssignNull = map.get(wrongKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getAfterPutSeveralKeysIsOK(java.util.Map<String, String> map) {
|
||||||
|
map.put("key1", "value1");
|
||||||
|
map.put("key2", "value1");
|
||||||
|
dontAssignNull = map.get("key2");
|
||||||
|
dontAssignNull = map.get("key1");
|
||||||
|
dontAssignNull = map.get("key2");
|
||||||
|
map.put("key3", "value1");
|
||||||
|
dontAssignNull = map.get("key1");
|
||||||
|
dontAssignNull = map.get("key2");
|
||||||
|
dontAssignNull = map.get("key3");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getAfterPutSeveralKeysButGetWrongOneIsBAD(java.util.Map<String, String> map) {
|
||||||
|
map.put("key1", "value1");
|
||||||
|
map.put("key2", "value1");
|
||||||
|
dontAssignNull = map.get("key2"); // OK
|
||||||
|
dontAssignNull = map.get("wrong_key"); // BAD
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getAfterPutNonnullIsOK(java.util.Map<String, String> map, String nonnullValue) {
|
||||||
|
map.put("key", nonnullValue);
|
||||||
|
dontAssignNull = map.get("key");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getAfterPutNullableIsBAD(
|
||||||
|
java.util.Map<String, String> map, @Nullable String nullableValue) {
|
||||||
|
map.put("key", nullableValue);
|
||||||
|
dontAssignNull = map.get("key");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void overwriteKeyByNullIsBAD(java.util.Map<String, String> map, String key) {
|
||||||
|
map.put(key, "abc");
|
||||||
|
map.put(key, null); // Parameter not nullable
|
||||||
|
dontAssignNull = map.get(key); // BAD
|
||||||
|
}
|
||||||
|
|
||||||
|
public void overwriteKeyByNonnullIsOK(java.util.Map<String, String> map, String key) {
|
||||||
|
map.put(key, null); // Parameter not nullable
|
||||||
|
map.put(key, "abc");
|
||||||
|
dontAssignNull = map.get(key); // OK
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getAfterConditionalPutIsOK(java.util.Map<String, String> map, String key) {
|
||||||
|
if (!map.containsKey(key)) {
|
||||||
|
map.put(key, "abc");
|
||||||
|
}
|
||||||
|
// OK: map either already contained a key, or we've just put it here!
|
||||||
|
dontAssignNull = map.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getAfterConditionalPutWrongKeyIsBAD(
|
||||||
|
java.util.Map<String, String> map, String key, String wrongKey) {
|
||||||
|
if (!map.containsKey(key)) {
|
||||||
|
map.put(wrongKey, "abc");
|
||||||
|
}
|
||||||
|
dontAssignNull = map.get(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,209 @@
|
|||||||
|
/*
|
||||||
|
* 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 javax.annotation.Nullable;
|
||||||
|
|
||||||
|
public class NestedFieldAccess {
|
||||||
|
|
||||||
|
class C {
|
||||||
|
@Nullable String s;
|
||||||
|
}
|
||||||
|
|
||||||
|
class CC {
|
||||||
|
@Nullable C c;
|
||||||
|
}
|
||||||
|
|
||||||
|
class CCC {
|
||||||
|
@Nullable CC cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests nullability check patterns for f1.f2.f3, when all components in the chain are nullable.
|
||||||
|
* (it should require checking of all components in the chain)
|
||||||
|
*/
|
||||||
|
public class TestNullableChains {
|
||||||
|
@Nullable String s;
|
||||||
|
C myc;
|
||||||
|
|
||||||
|
TestNullableChains() {
|
||||||
|
myc = new C();
|
||||||
|
}
|
||||||
|
|
||||||
|
void field_AccessAfterNullCheckIsOK() {
|
||||||
|
if (s != null) {
|
||||||
|
int n = s.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void field_AccessWithoutNullCheckIsBad() {
|
||||||
|
int n = s.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
void nestedField_AccessAfterNullCheckIsOK() {
|
||||||
|
if (myc.s != null) {
|
||||||
|
int n = myc.s.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void nestedField_AccessWithoutNullCheckIsBad() {
|
||||||
|
int n = myc.s.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
void param_AccessAfterNullCheckIsOK(C c) {
|
||||||
|
if (c.s != null) {
|
||||||
|
int n = c.s.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void param_AccessWithoutNullCheckIsBad(C c) {
|
||||||
|
int n = c.s.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
void local_AccessAfterNullCheckIsOK() {
|
||||||
|
C c = new C();
|
||||||
|
if (c.s != null) {
|
||||||
|
int n = c.s.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void local_AccessWithoutNullCheckIsBad() {
|
||||||
|
C c = new C();
|
||||||
|
int n = c.s.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
void deep_AccessWithNullCheckIsOK(CC cc) {
|
||||||
|
if (cc.c != null && cc.c.s != null) {
|
||||||
|
int n = cc.c.s.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void deep_AccessWithoutNullCheckIsBad(CC cc) {
|
||||||
|
if (cc.c != null /* && cc.c.s != null */) {
|
||||||
|
int n = cc.c.s.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void veryDeep_AccessWithNullCheckIsOK(CCC ccc) {
|
||||||
|
if (ccc.cc != null && ccc.cc.c != null && ccc.cc.c.s != null) {
|
||||||
|
int n = ccc.cc.c.s.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void veryDeep_AccessWithoutNullCheckIsBad(CCC ccc) {
|
||||||
|
if (ccc.cc != null && ccc.cc.c != null /* && ccc.cc.c.s != null */) {
|
||||||
|
int n = ccc.cc.c.s.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void veryDeep_AccessViaOrEarlyReturnIsOK(@Nullable CCC ccc) {
|
||||||
|
if (ccc == null || ccc.cc == null || ccc.cc.c == null || ccc.cc.c.s == null) {
|
||||||
|
} else {
|
||||||
|
int n = ccc.cc.c.s.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void veryDeep_IncompleteAccessViaOrEarlyReturnIsBad(@Nullable CCC ccc) {
|
||||||
|
if (ccc == null || ccc.cc == null || ccc.cc.c == null /*|| ccc.cc.c.s == null*/) {
|
||||||
|
} else {
|
||||||
|
int n = ccc.cc.c.s.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests nullability patterns for chains a().a().a().a().nullable(). Basically nullsafe needs to
|
||||||
|
* realize that objects returned by a().a() and a().a().a() are different so it should not learn
|
||||||
|
* anything about the nullability of one based on evidence about the other one.
|
||||||
|
*/
|
||||||
|
class TestFunctionsIdempotent {
|
||||||
|
@Nullable String s;
|
||||||
|
String dontAssignNull = "";
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
String nullable(int n) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
TestFunctionsIdempotent getSelf() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void chainOf0VsChainOf0IsOK() {
|
||||||
|
if (nullable(3) != null) {
|
||||||
|
dontAssignNull = nullable(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void chainOf0VsChainOf0ParamsMismatchIsBad() {
|
||||||
|
if (nullable(3) != null) {
|
||||||
|
dontAssignNull = nullable(4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void otherObjVsItselfIsOK(TestFunctionsIdempotent otherObj) {
|
||||||
|
if (otherObj.nullable(3) != null) {
|
||||||
|
dontAssignNull = otherObj.nullable(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void otherObjVsItselfIsOKParamsMismatchIsBAD(TestFunctionsIdempotent otherObj) {
|
||||||
|
if (otherObj.nullable(3) != null) {
|
||||||
|
dontAssignNull = otherObj.nullable(4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void selfVsOtherObjectIsBAD(TestFunctionsIdempotent otherObj) {
|
||||||
|
if (otherObj.nullable(3) != null) {
|
||||||
|
dontAssignNull = nullable(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void chainOf0VsChainOf1IsBad() {
|
||||||
|
if (nullable(3) != null) {
|
||||||
|
dontAssignNull = getSelf().nullable(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void chainOf1VsChainOf0IsBad() {
|
||||||
|
if (getSelf().nullable(3) != null) {
|
||||||
|
dontAssignNull = nullable(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void chainOf1VsChainOf1IsOK() {
|
||||||
|
if (getSelf().nullable(3) != null) {
|
||||||
|
dontAssignNull = getSelf().nullable(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void chainOf1VsChainOf1ParamMismatchIsBad() {
|
||||||
|
if (getSelf().nullable(3) != null) {
|
||||||
|
dontAssignNull = getSelf().nullable(4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void chainOf2VsChainOf2IsOK() {
|
||||||
|
if (getSelf().getSelf().nullable(3) != null) {
|
||||||
|
dontAssignNull = getSelf().getSelf().nullable(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void chainOf1VsChainOf2IsBad() {
|
||||||
|
if (getSelf().nullable(3) != null) {
|
||||||
|
dontAssignNull = getSelf().getSelf().nullable(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void chainOf2VsChainOf1IsBad() {
|
||||||
|
if (getSelf().getSelf().nullable(3) != null) {
|
||||||
|
dontAssignNull = getSelf().nullable(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue