|
|
|
/*
|
|
|
|
* 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 com.facebook.infer.annotation.NullsafeStrict;
|
|
|
|
import javax.annotation.Nonnull;
|
|
|
|
import javax.annotation.Nullable;
|
|
|
|
|
|
|
|
@NullsafeStrict
|
|
|
|
class Strict {
|
|
|
|
|
|
|
|
public String notInitializedIsBAD;
|
|
|
|
|
|
|
|
public @Nullable String nullable;
|
|
|
|
public @Nonnull String nonnull = "";
|
|
|
|
|
|
|
|
public @Nullable String getNullable() {
|
|
|
|
return nullable;
|
|
|
|
}
|
|
|
|
|
|
|
|
public String getNonnull() {
|
|
|
|
return nonnull;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static @Nullable String staticNullable() {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static String staticNonnull() {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
// 1. Inside the same class, we trust annotations.
|
|
|
|
|
|
|
|
private void sameClass_dereferenceNullableMethodIsBad() {
|
|
|
|
getNullable().toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void sameClass_dereferenceNonnullMethodIsOK() {
|
|
|
|
getNonnull().toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void sameClass_dereferenceNullableStaticMethodIsBad() {
|
|
|
|
staticNullable().toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void sameClass_dereferenceNonnullStaticMethodIsOK() {
|
|
|
|
staticNonnull().toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void sameClass_dereferenceNullableFieldIsBad() {
|
|
|
|
nullable.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void sameClass_dereferenceNonnullFieldIsOK() {
|
|
|
|
nonnull.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
private String sameClass_convertingNullableToNonnullIsBad() {
|
|
|
|
return getNullable();
|
|
|
|
}
|
|
|
|
|
|
|
|
private String sameClass_convertingNonnullToNonnullIsOK() {
|
|
|
|
return getNonnull();
|
|
|
|
}
|
|
|
|
|
|
|
|
// 2. We trust annotations that came from other classes annotated as strict
|
|
|
|
|
|
|
|
private void strictClass_dereferenceNullableMethodIsBad() {
|
|
|
|
(new OtherStrict()).getNullable().toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void strictClass_dereferenceNonnullMethodIsOK() {
|
|
|
|
(new OtherStrict()).getNonnull().toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void strictClass_dereferenceNullableStaticMethodIsBad() {
|
|
|
|
OtherStrict.staticNullable().toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void strictClass_dereferenceNonnullStaticMethodIsOK() {
|
|
|
|
OtherStrict.staticNonnull().toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void strictClass_dereferenceNullableFieldIsBad() {
|
|
|
|
(new OtherStrict()).nullable.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void strictClass_dereferenceNonnullFieldIsOK() {
|
|
|
|
(new OtherStrict()).nonnull.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
private String strictClass_convertingNullableToNonnullIsBad() {
|
|
|
|
return (new OtherStrict()).getNullable();
|
|
|
|
}
|
|
|
|
|
|
|
|
private String strictClass_convertingNonnullToNonnullIsOK() {
|
|
|
|
return (new OtherStrict()).getNonnull();
|
|
|
|
}
|
|
|
|
|
|
|
|
// 3. We DON'T trust annotations that came from other classes NOT annotated as strict
|
|
|
|
// when it comes to dereferencing or converting them to nullable.
|
|
|
|
|
|
|
|
private void nonStrictClass_dereferenceNullableMethodIsBad() {
|
|
|
|
(new NonStrict()).getNullable().toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void nonStrictClass_dereferenceNonnullMethodIsBad() {
|
|
|
|
// even that it is declared as nonnull, can not dereference it without checking before
|
|
|
|
(new NonStrict()).getNonnull().toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void nonStrictClass_dereferenceNullableStaticMethodIsBad() {
|
|
|
|
NonStrict.staticNullable().toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void nonStrictClass_dereferenceNonnullStaticMethodIsBad() {
|
|
|
|
// even that it is declared as nonnull, can not dereference it without checking before
|
|
|
|
NonStrict.staticNonnull().toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void nonStrictClass_dereferenceNullableFieldIsBad() {
|
|
|
|
(new NonStrict()).nullable.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void nonStrictClass_dereferenceNonnullFieldIsBad() {
|
|
|
|
// even that it is declared as nonnull, can not dereference it without checking before
|
|
|
|
(new NonStrict()).nonnull.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
private String nonStrictClass_convertingNullableToNonnullIsBad() {
|
|
|
|
return (new NonStrict()).getNullable();
|
|
|
|
}
|
|
|
|
|
|
|
|
public int usingPrimitiveTypesFromNonStrictIsOK() {
|
|
|
|
// Of course, primitive types can not be nullable so it
|
|
|
|
// does not matter if they are used from NonStrict
|
|
|
|
return NonStrict.getPrimitiveTypeValue();
|
|
|
|
}
|
|
|
|
|
|
|
|
// We don't require strictifying enums: their values are non-nullables
|
|
|
|
private SomeEnum usingEnumValuesAsNonnullIsOK() {
|
|
|
|
String str = SomeEnum.FIRST_VALUE.toString(); // can dereference
|
|
|
|
SomeEnum.FIRST_VALUE.someMethod(); // can dereference via calling a enum-specific method
|
|
|
|
return SomeEnum.SECOND_VALUE; // can convert to nonnull
|
|
|
|
}
|
|
|
|
|
|
|
|
// FAKE_VALUE is not a value, but just a static fields so should require strictification
|
|
|
|
// (but it does not)
|
|
|
|
private SomeEnum FN_usingNonStrictifiedStaticFieldsInEnumsShouldBeBad() {
|
|
|
|
String str = SomeEnum.FAKE_VALUE.toString(); // should not be able to dereference
|
|
|
|
SomeEnum.FAKE_VALUE.someMethod(); // should not be able to dereference
|
|
|
|
return SomeEnum.FAKE_VALUE; // should not be able to convert to nonnull
|
|
|
|
}
|
|
|
|
|
|
|
|
private SomeEnum[] enumValuesIsNonnullable() {
|
|
|
|
// values() is a special enum method which is never nullable
|
|
|
|
return SomeEnum.values();
|
|
|
|
}
|
|
|
|
|
|
|
|
private SomeEnum enumValueOfIsNonnullable() {
|
|
|
|
// valueOf() is a special enum method which is never nullable
|
|
|
|
return SomeEnum.valueOf("this will throw but won't return null");
|
|
|
|
}
|
|
|
|
|
|
|
|
private String nonStrictClass_convertingNonnullToNonnullIsBad() {
|
|
|
|
// even that it is declared as nonnull, can not convert it to nonnull it without checking before
|
|
|
|
return (new NonStrict()).getNonnull();
|
|
|
|
}
|
|
|
|
|
|
|
|
// 4. We don't completely prohibit using non-strict from strict, but we do require extra checks
|
|
|
|
// or adding defensive annotations.
|
|
|
|
|
|
|
|
private void nonStrictClass_dereferenceNonnullFieldAfterCheckIsOK() {
|
|
|
|
NonStrict o = new NonStrict();
|
|
|
|
if (o.nonnull != null) {
|
|
|
|
// This works because Nullsafe assumes that the field won't be modified between the calls
|
|
|
|
// (e.g. in multithreading context)
|
|
|
|
o.nonnull.toString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void nonStrictClass_dereferenceNonnullMethodAfterCheckIsOK() {
|
|
|
|
NonStrict o = new NonStrict();
|
|
|
|
if (o.getNonnull() != null) {
|
|
|
|
// This works because Nullsafe assumes that all methods are determenistic and side-effect
|
|
|
|
// free, so the second call won't return null.
|
|
|
|
o.getNonnull().toString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private @Nullable String nonStrictClass_convertingNonnullToNullableIsOK() {
|
|
|
|
return (new NonStrict()).getNonnull();
|
|
|
|
}
|
|
|
|
|
|
|
|
// 5. Main promise of strict mode: if the function is not annotated as nullable,
|
|
|
|
// it won't return null.
|
|
|
|
// So, if a function is annotated as strict, no extra check on caller's side is required.
|
|
|
|
// But strict mode DOES NOT guarantee there will be absolutely no NPE in callees:
|
|
|
|
// a) Even if strict mode calls only strict mode, there can be assertions that will throw NPE.
|
|
|
|
// b) We allow calling non-strict functions, and they internally can be inconsistent and throw
|
|
|
|
// NPE.
|
|
|
|
// c) We even allow passing values obtained from non-strict code, to other parts of non-strict
|
|
|
|
// code (e.g. we allow glueing 2 non-strict classes together inside a strict class, which might
|
|
|
|
// potentially lead to NPE if one of annotations is untrusted).
|
|
|
|
|
|
|
|
private void propagatingNonnullFromNonStrictToStrictIsBad() {
|
|
|
|
NonStrict nonStrict = new NonStrict();
|
|
|
|
OtherStrict strict = new OtherStrict();
|
|
|
|
nonStrict.nonnull = strict.getNonnull();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void propagatingNonnullBetweenTwoNonStrictObjectsIsOK() {
|
|
|
|
NonStrict nonStrict1 = new NonStrict();
|
|
|
|
NonStrict nonStrict2 = new NonStrict();
|
|
|
|
// Though getNonnull() is declared as non-nullable, is not strictly checked
|
|
|
|
// so there is a possibility that it can return null, e.g. if it calls to a third-party
|
|
|
|
// libraries. this null can leak to `nonnull` field, which might be a bad thing and lead to
|
|
|
|
// issues.
|
|
|
|
// This is OK for strict mode.
|
|
|
|
nonStrict1.nonnull = nonStrict2.getNonnull();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@NullsafeStrict
|
|
|
|
class OtherStrict {
|
|
|
|
public @Nullable String nullable;
|
|
|
|
public String nonnull = "";
|
|
|
|
|
|
|
|
public @Nullable String getNullable() {
|
|
|
|
return nullable;
|
|
|
|
}
|
|
|
|
|
|
|
|
public String getNonnull() {
|
|
|
|
return nonnull;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static @Nullable String staticNullable() {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static String staticNonnull() {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class NonStrict {
|
|
|
|
public @Nullable String nullable;
|
|
|
|
public String nonnull = "";
|
|
|
|
|
|
|
|
public @Nullable String getNullable() {
|
|
|
|
return nullable;
|
|
|
|
}
|
|
|
|
|
|
|
|
public String getNonnull() {
|
|
|
|
return nonnull;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static @Nullable String staticNullable() {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static String staticNonnull() {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
public static int getPrimitiveTypeValue() {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
enum SomeEnum {
|
|
|
|
FIRST_VALUE,
|
|
|
|
SECOND_VALUE;
|
|
|
|
|
|
|
|
public void someMethod() {}
|
|
|
|
|
|
|
|
// We currently have no way to distinct that guy from enum value.
|
|
|
|
// Using it will lead to NPE, but we won't detect it
|
|
|
|
// This should not occur in practice often, hopefully :)
|
|
|
|
public static SomeEnum FAKE_VALUE;
|
|
|
|
}
|