[eradicate] Add support for new annotation @PropagatesNullable

Summary:
One limitation of Eradicate is that certain nullability patterns are not expressible using simply the `Nullable` annotation.
One such pattern is using the knowledge that a function returns null when passed null, but returns an object otherwise.
The annotation `PropagatesNullable` is a variant of `Nullable` applied to parameters when their value propagates to the return value.
A method annotated
```
  B m(PropagatesNullable A x) { return x == null ? x : B(x); }
```
indicates that `m` returns null if `x` is null, or an object of class `B` if the argument is not null.
Examples with multiple parameters are in the test cases.

This diff builds some infrastructure for annotation transformers: the example above represents the identity function on nullability annotations.

Reviewed By: jvillard

Differential Revision: D4705938

fbshipit-source-id: 9f6194e
master
Cristiano Calcagno 8 years ago committed by Facebook Github Bot
parent 69fe80346c
commit 434cfbfb15

@ -0,0 +1,23 @@
/*
* Copyright (c) 2017 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.infer.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation to indicate that when the parameter is null, the result is also null.
*/
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.PARAMETER})
public @interface PropagatesNullable {
}

@ -15,11 +15,13 @@ let module L = Logging;
let module F = Format; let module F = Format;
type parameters = list string [@@deriving compare];
/** Type to represent one @Annotation. */ /** Type to represent one @Annotation. */
type t = { type t = {
class_name: string, /** name of the annotation */ class_name: string, /** name of the annotation */
parameters: list string /** currently only one string parameter */ parameters: parameters /** currently only one string parameter */
} }
[@@deriving compare]; [@@deriving compare];

@ -13,11 +13,13 @@ open! IStd;
/** The Smallfoot Intermediate Language: Annotations */ /** The Smallfoot Intermediate Language: Annotations */
let module F = Format; let module F = Format;
type parameters = list string;
/** Type to represent one @Annotation. */ /** Type to represent one @Annotation. */
type t = { type t = {
class_name: string, /** name of the annotation */ class_name: string, /** name of the annotation */
parameters: list string /** currently only one string parameter */ parameters: parameters /** currently only one string parameter */
} }
[@@deriving compare]; [@@deriving compare];

@ -49,6 +49,7 @@ let performance_critical = "PerformanceCritical"
let present = "Present" let present = "Present"
let privacy_source = "PrivacySource" let privacy_source = "PrivacySource"
let privacy_sink = "PrivacySink" let privacy_sink = "PrivacySink"
let propagates_nullable = "PropagatesNullable"
let strict = "com.facebook.infer.annotation.Strict" let strict = "com.facebook.infer.annotation.Strict"
let returns_ownership = "ReturnsOwnership" let returns_ownership = "ReturnsOwnership"
let suppress_lint = "SuppressLint" let suppress_lint = "SuppressLint"
@ -120,8 +121,12 @@ let struct_typ_has_annot (struct_typ : Typ.Struct.t) predicate =
let ia_is_not_thread_safe ia = let ia_is_not_thread_safe ia =
ia_ends_with ia not_thread_safe ia_ends_with ia not_thread_safe
let ia_is_propagates_nullable ia =
ia_ends_with ia propagates_nullable
let ia_is_nullable ia = let ia_is_nullable ia =
ia_ends_with ia nullable ia_ends_with ia nullable ||
ia_is_propagates_nullable ia
let ia_is_present ia = let ia_is_present ia =
ia_ends_with ia present ia_ends_with ia present

@ -61,6 +61,7 @@ val ia_is_verify : Annot.Item.t -> bool
val ia_is_expensive : Annot.Item.t -> bool val ia_is_expensive : Annot.Item.t -> bool
val ia_is_functional : Annot.Item.t -> bool val ia_is_functional : Annot.Item.t -> bool
val ia_is_performance_critical : Annot.Item.t -> bool val ia_is_performance_critical : Annot.Item.t -> bool
val ia_is_propagates_nullable : Annot.Item.t -> bool
val ia_is_no_allocation : Annot.Item.t -> bool val ia_is_no_allocation : Annot.Item.t -> bool
val ia_is_ignore_allocations : Annot.Item.t -> bool val ia_is_ignore_allocations : Annot.Item.t -> bool
val ia_is_inject : Annot.Item.t -> bool val ia_is_inject : Annot.Item.t -> bool

@ -84,7 +84,9 @@ struct
annotated_signature.AnnotatedSignature.params in annotated_signature.AnnotatedSignature.params in
(* Check the nullable flag computed for the return value and report inconsistencies. *) (* Check the nullable flag computed for the return value and report inconsistencies. *)
let check_return find_canonical_duplicate exit_node final_typestate ret_ia ret_type loc : unit = let check_return
find_canonical_duplicate exit_node final_typestate annotated_signature loc : unit =
let _, ret_type = annotated_signature.AnnotatedSignature.ret in
let ret_pvar = Procdesc.get_ret_var curr_pdesc in let ret_pvar = Procdesc.get_ret_var curr_pdesc in
let ret_range = TypeState.lookup_pvar ret_pvar final_typestate in let ret_range = TypeState.lookup_pvar ret_pvar final_typestate in
let typ_found_opt = match ret_range with let typ_found_opt = match ret_range with
@ -101,7 +103,7 @@ struct
if checks.TypeCheck.eradicate then if checks.TypeCheck.eradicate then
EradicateChecks.check_return_annotation tenv EradicateChecks.check_return_annotation tenv
find_canonical_duplicate curr_pdesc ret_range find_canonical_duplicate curr_pdesc ret_range
ret_ia ret_implicitly_nullable loc in annotated_signature ret_implicitly_nullable loc in
let do_before_dataflow initial_typestate = let do_before_dataflow initial_typestate =
if Config.eradicate_verbose then if Config.eradicate_verbose then
@ -110,8 +112,8 @@ struct
let do_after_dataflow find_canonical_duplicate final_typestate = let do_after_dataflow find_canonical_duplicate final_typestate =
let exit_node = Procdesc.get_exit_node curr_pdesc in let exit_node = Procdesc.get_exit_node curr_pdesc in
let ia, ret_type = annotated_signature.AnnotatedSignature.ret in check_return
check_return find_canonical_duplicate exit_node final_typestate ia ret_type proc_loc in find_canonical_duplicate exit_node final_typestate annotated_signature proc_loc in
let module DFTypeCheck = MakeDF(struct let module DFTypeCheck = MakeDF(struct
type t = Extension.extension TypeState.t type t = Extension.extension TypeState.t

@ -347,11 +347,18 @@ let spec_make_return_nullable curr_pname =
(** Check the annotations when returning from a method. *) (** Check the annotations when returning from a method. *)
let check_return_annotation tenv let check_return_annotation tenv
find_canonical_duplicate curr_pdesc ret_range find_canonical_duplicate curr_pdesc ret_range
ret_ia ret_implicitly_nullable loc : unit = (annotated_signature : AnnotatedSignature.t) ret_implicitly_nullable loc : unit =
let ret_ia, _ = annotated_signature.ret in
let curr_pname = Procdesc.get_proc_name curr_pdesc in let curr_pname = Procdesc.get_proc_name curr_pdesc in
let ret_annotated_nullable = Annotations.ia_is_nullable ret_ia in let ret_annotated_nullable =
let ret_annotated_present = Annotations.ia_is_present ret_ia in Annotations.ia_is_nullable ret_ia ||
let ret_annotated_nonnull = Annotations.ia_is_nonnull ret_ia in List.exists
~f:(fun (_, ia, _) -> Annotations.ia_is_propagates_nullable ia)
annotated_signature.params in
let ret_annotated_present =
Annotations.ia_is_present ret_ia in
let ret_annotated_nonnull =
Annotations.ia_is_nonnull ret_ia in
match ret_range with match ret_range with
| Some (_, final_ta, _) -> | Some (_, final_ta, _) ->
let final_nullable = TypeAnnotation.get_value AnnotatedSignature.Nullable final_ta in let final_nullable = TypeAnnotation.get_value AnnotatedSignature.Nullable final_ta in
@ -442,62 +449,62 @@ let check_call_receiver tenv
end end
| [] -> () | [] -> ()
type param = { type resolved_param = {
num : int;
formal : Mangled.t * TypeAnnotation.t * Typ.t; formal : Mangled.t * TypeAnnotation.t * Typ.t;
actual : Exp.t * TypeAnnotation.t; actual : Exp.t * TypeAnnotation.t;
propagates_nullable : bool;
} }
(** Check the parameters of a call. *) (** Check the parameters of a call. *)
let check_call_parameters tenv let check_call_parameters tenv
find_canonical_duplicate curr_pdesc node callee_attributes find_canonical_duplicate curr_pdesc node callee_attributes
params loc instr_ref : unit = resolved_params loc instr_ref : unit =
let callee_pname = callee_attributes.ProcAttributes.proc_name in let callee_pname = callee_attributes.ProcAttributes.proc_name in
let tot_param_num = List.length params in let tot_param_num = List.length resolved_params in
let check i = function let check
| {formal = (s1, ta1, t1); actual = (orig_e2, ta2)} -> {num = param_num; formal = (s1, ta1, t1); actual = (orig_e2, ta2)} =
let param_num = i + 1 in let report ann =
let description =
match explain_expr tenv node orig_e2 with
| Some descr -> descr
| None -> "formal parameter " ^ (Mangled.to_string s1) in
let origin_descr = TypeAnnotation.descr_origin tenv ta2 in
let report ann = let callee_loc = callee_attributes.ProcAttributes.loc in
let description = report_error tenv
match explain_expr tenv node orig_e2 with find_canonical_duplicate
| Some descr -> descr (TypeErr.Parameter_annotation_inconsistent (
| None -> "formal parameter " ^ (Mangled.to_string s1) in ann,
let origin_descr = TypeAnnotation.descr_origin tenv ta2 in description,
param_num,
let callee_loc = callee_attributes.ProcAttributes.loc in callee_pname,
report_error tenv callee_loc,
find_canonical_duplicate origin_descr))
(TypeErr.Parameter_annotation_inconsistent ( (Some instr_ref)
ann, loc curr_pdesc in
description,
param_num, let check_ann ann =
callee_pname, let b1 = TypeAnnotation.get_value ann ta1 in
callee_loc, let b2 = TypeAnnotation.get_value ann ta2 in
origin_descr)) match ann, b1, b2 with
(Some instr_ref) | AnnotatedSignature.Nullable, false, true ->
loc curr_pdesc in report ann;
if Models.Inference.enabled then
let check_ann ann = Models.Inference.proc_add_parameter_nullable callee_pname param_num tot_param_num
let b1 = TypeAnnotation.get_value ann ta1 in | AnnotatedSignature.Present, true, false ->
let b2 = TypeAnnotation.get_value ann ta2 in report ann
match ann, b1, b2 with | _ ->
| AnnotatedSignature.Nullable, false, true -> () in
report ann;
if Models.Inference.enabled then if PatternMatch.type_is_class t1
Models.Inference.proc_add_parameter_nullable callee_pname param_num tot_param_num then begin
| AnnotatedSignature.Present, true, false -> check_ann AnnotatedSignature.Nullable;
report ann if Config.eradicate_optional_present
| _ -> then check_ann AnnotatedSignature.Present;
() in end in
if PatternMatch.type_is_class t1
then begin
check_ann AnnotatedSignature.Nullable;
if Config.eradicate_optional_present
then check_ann AnnotatedSignature.Present;
end in
let should_check_parameters = let should_check_parameters =
if check_library_calls then true if check_library_calls then true
else else
@ -507,7 +514,7 @@ let check_call_parameters tenv
if should_check_parameters then if should_check_parameters then
(* left to right to avoid guessing the different lengths *) (* left to right to avoid guessing the different lengths *)
List.iteri ~f:check params List.iter ~f:check resolved_params
(** Checks if the annotations are consistent with the inherited class or with the (** Checks if the annotations are consistent with the inherited class or with the
implemented interfaces *) implemented interfaces *)

@ -621,20 +621,11 @@ let typecheck_instr
let is_anonymous_inner_class_constructor = let is_anonymous_inner_class_constructor =
Typ.Procname.java_is_anonymous_inner_class_constructor callee_pname in Typ.Procname.java_is_anonymous_inner_class_constructor callee_pname in
let do_return loc' typestate' = let do_return (ret_ta, ret_typ) loc' typestate' =
let mk_return_range () = let mk_return_range () =
let (ia, ret_typ) = annotated_signature.AnnotatedSignature.ret in
let is_library = Specs.proc_is_library callee_attributes in
let origin = TypeOrigin.Proc
{
TypeOrigin.pname = callee_pname;
loc = loc';
annotated_signature;
is_library;
} in
( (
ret_typ, ret_typ,
TypeAnnotation.from_item_annotation ia origin, ret_ta,
[loc'] [loc']
) in ) in
@ -802,101 +793,161 @@ let typecheck_instr
| _ -> | _ ->
typestate' in typestate' in
let typestate2 = let typestate_after_call, resolved_ret =
let prepare_params sig_params call_params = let resolve_param i (sparam, cparam) =
let rec f acc sparams cparams = match sparams, cparams with let (s1, ia1, t1) = sparam in
| (s1, ia1, t1) :: sparams', ((orig_e2, e2), t2) :: cparams' -> let ((orig_e2, e2), t2) = cparam in
let param_is_this = String.equal (Mangled.to_string s1) "this" in let ta1 = TypeAnnotation.from_item_annotation ia1 (TypeOrigin.Formal s1) in
let acc' = let (_, ta2, _) =
if param_is_this typecheck_expr
then acc find_canonical_duplicate calls_this checks
else begin tenv node instr_ref curr_pdesc typestate e2
let ta1 = TypeAnnotation.from_item_annotation ia1 (TypeOrigin.Formal s1) in (t2,
let (_, ta2, _) = TypeAnnotation.const AnnotatedSignature.Nullable false TypeOrigin.ONone,
typecheck_expr [])
find_canonical_duplicate calls_this checks loc in
tenv node instr_ref curr_pdesc typestate e2 let formal = (s1, ta1, t1) in
(t2, let actual = (orig_e2, ta2) in
TypeAnnotation.const AnnotatedSignature.Nullable false TypeOrigin.ONone, let num = i+1 in
[]) let formal_is_propagates_nullable = Annotations.ia_is_propagates_nullable ia1 in
loc in let actual_is_nullable = TypeAnnotation.get_value AnnotatedSignature.Nullable ta2 in
let propagates_nullable = formal_is_propagates_nullable && actual_is_nullable in
EradicateChecks.{ EradicateChecks.{num; formal; actual; propagates_nullable} in
formal = (s1, ta1, t1);
actual = (orig_e2, ta2)} (* Apply a function that operates on annotations *)
:: acc let apply_annotation_transformer
end in resolved_ret (resolved_params : EradicateChecks.resolved_param list) =
f acc' sparams' cparams' let rec handle_params resolved_ret params =
| _ -> match (params : EradicateChecks.resolved_param list) with
acc in | param :: params'
f [] (List.rev sig_params) (List.rev call_params) in when param.propagates_nullable ->
let (_, actual_ta) = param.actual in
if not is_anonymous_inner_class_constructor then let resolved_ret' =
begin let (ret_ta, ret_typ) = resolved_ret in
if Config.eradicate_debug then let ret_ta' =
begin let actual_nullable =
let unique_id = Typ.Procname.to_unique_id callee_pname in TypeAnnotation.get_value AnnotatedSignature.Nullable actual_ta in
let classification = let old_nullable =
EradicateChecks.classify_procedure callee_attributes in TypeAnnotation.get_value AnnotatedSignature.Nullable ret_ta in
L.stdout " %s unique id: %s@." classification unique_id let new_nullable =
end; old_nullable || actual_nullable in
if cflags.CallFlags.cf_virtual && checks.eradicate then TypeAnnotation.set_value
EradicateChecks.check_call_receiver tenv AnnotatedSignature.Nullable
find_canonical_duplicate new_nullable
curr_pdesc ret_ta in
node (ret_ta', ret_typ) in
typestate1 handle_params resolved_ret' params'
call_params | _ :: params' ->
callee_pname handle_params resolved_ret params'
instr_ref | [] ->
loc resolved_ret in
(typecheck_expr find_canonical_duplicate calls_this checks); handle_params resolved_ret resolved_params in
if checks.eradicate then
EradicateChecks.check_call_parameters tenv let resolved_ret_ =
find_canonical_duplicate let (ret_ia, ret_typ) = annotated_signature.AnnotatedSignature.ret in
curr_pdesc let is_library = Specs.proc_is_library callee_attributes in
node let origin = TypeOrigin.Proc
callee_attributes {
(prepare_params signature_params call_params) TypeOrigin.pname = callee_pname;
loc loc;
instr_ref; annotated_signature;
let typestate2 = is_library;
if checks.check_extension then } in
let etl' = List.map ~f:(fun ((_, e), t) -> (e, t)) call_params in let ret_ta = TypeAnnotation.from_item_annotation ret_ia origin in
let extension = TypeState.get_extension typestate1 in (ret_ta, ret_typ) in
let extension' =
ext.TypeState.check_instr let sig_len = List.length signature_params in
tenv get_proc_desc curr_pname curr_pdesc extension instr etl' in let call_len = List.length call_params in
TypeState.set_extension typestate1 extension' let min_len = min sig_len call_len in
else typestate1 in let slice l =
let has_method pn name = match pn with let len = List.length l in
| Typ.Procname.Java pn_java -> if len > min_len
String.equal (Typ.Procname.java_get_method pn_java) name then List.slice l (len - min_len) 0
| _ -> else l in
false in let sig_slice = slice signature_params in
if Models.is_check_not_null callee_pname then let call_slice = slice call_params in
do_preconditions_check_not_null let sig_call_params =
(Models.get_check_not_null_parameter callee_pname) List.filter
~is_vararg:false ~f:(fun (sparam, _) ->
typestate2 let (s, _, _) = sparam in
else let param_is_this =
if has_method callee_pname "checkNotNull" String.equal (Mangled.to_string s) "this" ||
&& Typ.Procname.java_is_vararg callee_pname String.is_prefix ~prefix:"this$" (Mangled.to_string s) in
then not param_is_this)
let last_parameter = List.length call_params in (List.zip_exn sig_slice call_slice) in
do_preconditions_check_not_null
last_parameter let resolved_params = List.mapi ~f:resolve_param sig_call_params in
~is_vararg:true let resolved_ret =
typestate2 apply_annotation_transformer resolved_ret_ resolved_params in
else if Models.is_check_state callee_pname ||
Models.is_check_argument callee_pname then let typestate_after_call =
do_preconditions_check_state typestate2 if not is_anonymous_inner_class_constructor then
else if Models.is_mapPut callee_pname then begin
do_map_put typestate2 if Config.eradicate_debug then
else typestate2 begin
end let unique_id = Typ.Procname.to_unique_id callee_pname in
else typestate1 in let classification =
do_return loc typestate2 EradicateChecks.classify_procedure callee_attributes in
L.stdout " %s unique id: %s@." classification unique_id
end;
if cflags.CallFlags.cf_virtual && checks.eradicate then
EradicateChecks.check_call_receiver tenv
find_canonical_duplicate
curr_pdesc
node
typestate1
call_params
callee_pname
instr_ref
loc
(typecheck_expr find_canonical_duplicate calls_this checks);
if checks.eradicate then
EradicateChecks.check_call_parameters tenv
find_canonical_duplicate
curr_pdesc
node
callee_attributes
resolved_params
loc
instr_ref;
let typestate2 =
if checks.check_extension then
let etl' = List.map ~f:(fun ((_, e), t) -> (e, t)) call_params in
let extension = TypeState.get_extension typestate1 in
let extension' =
ext.TypeState.check_instr
tenv get_proc_desc curr_pname curr_pdesc extension instr etl' in
TypeState.set_extension typestate1 extension'
else typestate1 in
let has_method pn name = match pn with
| Typ.Procname.Java pn_java ->
String.equal (Typ.Procname.java_get_method pn_java) name
| _ ->
false in
if Models.is_check_not_null callee_pname then
do_preconditions_check_not_null
(Models.get_check_not_null_parameter callee_pname)
~is_vararg:false
typestate2
else
if has_method callee_pname "checkNotNull"
&& Typ.Procname.java_is_vararg callee_pname
then
let last_parameter = List.length call_params in
do_preconditions_check_not_null
last_parameter
~is_vararg:true
typestate2
else if Models.is_check_state callee_pname ||
Models.is_check_argument callee_pname then
do_preconditions_check_state typestate2
else if Models.is_mapPut callee_pname then
do_map_put typestate2
else typestate2
end
else typestate1 in
typestate_after_call, resolved_ret in
do_return resolved_ret loc typestate_after_call
| Sil.Call _ -> | Sil.Call _ ->
typestate typestate
| Sil.Prune (cond, loc, true_branch, _) -> | Sil.Prune (cond, loc, true_branch, _) ->

@ -16,11 +16,10 @@ open Javalib_pack
(** Translate an annotation. *) (** Translate an annotation. *)
let translate a : Annot.t = let translate a : Annot.t =
let class_name = JBasics.cn_name a.JBasics.kind in let class_name = JBasics.cn_name a.JBasics.kind in
let translate_value_pair acc (_, value) = let rec translate_value_pair acc (x, value) =
match value with match value with
| JBasics.EVArray [JBasics.EVCstString s] -> | JBasics.EVArray (JBasics.EVCstString s :: l) ->
(* TODO (t16352423) see if this case is used *) translate_value_pair (s::acc) (x, JBasics.EVArray l)
s :: acc
| JBasics.EVCstString s -> | JBasics.EVCstString s ->
s :: acc s :: acc
| JBasics.EVCstBoolean 0 -> | JBasics.EVCstBoolean 0 ->

@ -0,0 +1,129 @@
/*
* Copyright (c) 2013 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package codetoanalyze.java.eradicate;
import android.text.TextUtils;
import javax.annotation.Nullable;
import com.facebook.infer.annotation.Assertions;
import com.facebook.infer.annotation.FalseOnNull;
import com.facebook.infer.annotation.PropagatesNullable;
import com.facebook.infer.annotation.TrueOnNull;
public class CustomAnnotations {
static class MyTextUtils {
@TrueOnNull
static boolean isEmpty(@Nullable java.lang.CharSequence s) {
return s == null || s.equals("");
}
@FalseOnNull
static boolean isNotEmpty(@Nullable java.lang.CharSequence s) {
return s != null && s.length() > 0;
}
}
class TestTextUtilsIsEmpty {
void textUtilsNotIsEmpty(@Nullable CharSequence s) {
if (!TextUtils.isEmpty(s)) {
s.toString(); // OK
}
}
void textUtilsIsEmpty(@Nullable CharSequence s) {
if (TextUtils.isEmpty(s)) {
s.toString(); // BAD
}
}
void myTextUtilsNotIsEmpty(@Nullable CharSequence s) {
if (!MyTextUtils.isEmpty(s)) {
s.toString(); // OK
}
}
void myTextUtilsIsEmpty(@Nullable CharSequence s) {
if (MyTextUtils.isEmpty(s)) {
s.toString(); // BAD
}
}
void myTextUtilsIsNotEmpty(@Nullable CharSequence s) {
if (MyTextUtils.isNotEmpty(s)) {
s.toString(); // OK
}
}
void myTextUtilsNotIsNotEmpty(@Nullable CharSequence s) {
if (!MyTextUtils.isNotEmpty(s)) {
s.toString(); // BAD
}
}
}
// Tests for the annotation @PropagatesNullable
class TestPropagatesNullable {
// one parameter: means return null iff s is null
String m(@PropagatesNullable String s) {
return s;
}
void mBad() {
m(null).length(); // bad: m returns null
}
void mGood() {
m("").length();
}
// limitation: we currently cannot check the body, and just trust the annotation
String cannotCheckBody(@PropagatesNullable String s) {
return null; // nothing is reported here
}
void illustrateFalseNegativeAsCannotCheckBody() {
cannotCheckBody("").length(); // this is an NPE but is not found
}
// second parameter: means return null iff s2 is null
String m2(@Nullable String s1, @PropagatesNullable String s2) {
return s2;
}
void m2Bad() {
m2(null, null).length(); // bad: m2 returns null
}
void m2Good() {
m2(null, "").length();
}
// two parameters: means return null iff either s1 or s2 is null
String m12(@PropagatesNullable String s1, @PropagatesNullable String s2) {
return s1 == null ? s1 : s2;
}
void m12Bad1() {
m12(null, "").length(); // bad: m12 returns null
}
void m12Bad2() {
m12("", null).length(); // bad: m12 returns null
}
void m12Good() {
m12("", "").length();
}
}
}

@ -9,14 +9,11 @@
package codetoanalyze.java.eradicate; package codetoanalyze.java.eradicate;
import android.text.TextUtils;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import java.lang.System; import java.lang.System;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import com.facebook.infer.annotation.Assertions; import com.facebook.infer.annotation.Assertions;
import com.facebook.infer.annotation.FalseOnNull;
import com.facebook.infer.annotation.TrueOnNull;
public class NullMethodCall { public class NullMethodCall {
@ -242,57 +239,6 @@ public class NullMethodCall {
int n = s.length(); int n = s.length();
} }
static class MyTextUtils {
@TrueOnNull
static boolean isEmpty(@Nullable java.lang.CharSequence s) {
return s == null || s.equals("");
}
@FalseOnNull
static boolean isNotEmpty(@Nullable java.lang.CharSequence s) {
return s != null && s.length() > 0;
}
}
class TestTextUtilsIsEmpty {
void textUtilsNotIsEmpty(@Nullable CharSequence s) {
if (!TextUtils.isEmpty(s)) {
s.toString(); // OK
}
}
void textUtilsIsEmpty(@Nullable CharSequence s) {
if (TextUtils.isEmpty(s)) {
s.toString(); // BAD
}
}
void myTextUtilsNotIsEmpty(@Nullable CharSequence s) {
if (!MyTextUtils.isEmpty(s)) {
s.toString(); // OK
}
}
void myTextUtilsIsEmpty(@Nullable CharSequence s) {
if (MyTextUtils.isEmpty(s)) {
s.toString(); // BAD
}
}
void myTextUtilsIsNotEmpty(@Nullable CharSequence s) {
if (MyTextUtils.isNotEmpty(s)) {
s.toString(); // OK
}
}
void myTextUtilsNotIsNotEmpty(@Nullable CharSequence s) {
if (!MyTextUtils.isNotEmpty(s)) {
s.toString(); // BAD
}
}
}
class SystemExitDoesNotReturn { class SystemExitDoesNotReturn {
native boolean whoknows(); native boolean whoknows();

@ -85,4 +85,17 @@ public class ParameterNotNullable {
threeParameters(s, null, s); threeParameters(s, null, s);
threeParameters(s, s, null); threeParameters(s, s, null);
} }
class ConstructorCall {
ConstructorCall(int x, String s) {
}
ConstructorCall() {
this(3, ""); // OK
}
ConstructorCall(int x) {
this(3, null); // NPE
}
}
} }

@ -1,4 +1,11 @@
codetoanalyze/java/eradicate/ActivityFieldNotInitialized.java, ActivityFieldNotInitialized$BadActivityWithOnCreate.<init>(ActivityFieldNotInitialized), 0, ERADICATE_FIELD_NOT_INITIALIZED, [Field `ActivityFieldNotInitialized$BadActivityWithOnCreate.field` is not initialized in the constructor and is not declared `@Nullable`] codetoanalyze/java/eradicate/ActivityFieldNotInitialized.java, ActivityFieldNotInitialized$BadActivityWithOnCreate.<init>(ActivityFieldNotInitialized), 0, ERADICATE_FIELD_NOT_INITIALIZED, [Field `ActivityFieldNotInitialized$BadActivityWithOnCreate.field` is not initialized in the constructor and is not declared `@Nullable`]
codetoanalyze/java/eradicate/CustomAnnotations.java, void CustomAnnotations$TestPropagatesNullable.m12Bad1(), 1, ERADICATE_NULL_METHOD_CALL, [origin,The value of `m12(...)` in the call to `length()` could be null. (Origin: call to m12(...) at line 118)]
codetoanalyze/java/eradicate/CustomAnnotations.java, void CustomAnnotations$TestPropagatesNullable.m12Bad2(), 1, ERADICATE_NULL_METHOD_CALL, [origin,The value of `m12(...)` in the call to `length()` could be null. (Origin: call to m12(...) at line 122)]
codetoanalyze/java/eradicate/CustomAnnotations.java, void CustomAnnotations$TestPropagatesNullable.m2Bad(), 1, ERADICATE_NULL_METHOD_CALL, [origin,The value of `m2(...)` in the call to `length()` could be null. (Origin: call to m2(...) at line 105)]
codetoanalyze/java/eradicate/CustomAnnotations.java, void CustomAnnotations$TestPropagatesNullable.mBad(), 1, ERADICATE_NULL_METHOD_CALL, [origin,The value of `m(...)` in the call to `length()` could be null. (Origin: call to m(...) at line 83)]
codetoanalyze/java/eradicate/CustomAnnotations.java, void CustomAnnotations$TestTextUtilsIsEmpty.myTextUtilsIsEmpty(CharSequence), 2, ERADICATE_NULL_METHOD_CALL, [The value of `s` in the call to `toString()` could be null. (Origin: method parameter s)]
codetoanalyze/java/eradicate/CustomAnnotations.java, void CustomAnnotations$TestTextUtilsIsEmpty.myTextUtilsNotIsNotEmpty(CharSequence), 2, ERADICATE_NULL_METHOD_CALL, [The value of `s` in the call to `toString()` could be null. (Origin: method parameter s)]
codetoanalyze/java/eradicate/CustomAnnotations.java, void CustomAnnotations$TestTextUtilsIsEmpty.textUtilsIsEmpty(CharSequence), 2, ERADICATE_NULL_METHOD_CALL, [The value of `s` in the call to `toString()` could be null. (Origin: method parameter s)]
codetoanalyze/java/eradicate/FieldNotInitialized.java, FieldNotInitialized$ConditionalFieldInit.<init>(FieldNotInitialized), 0, ERADICATE_FIELD_NOT_INITIALIZED, [Field `FieldNotInitialized$ConditionalFieldInit.o1` is not initialized in the constructor and is not declared `@Nullable`] codetoanalyze/java/eradicate/FieldNotInitialized.java, FieldNotInitialized$ConditionalFieldInit.<init>(FieldNotInitialized), 0, ERADICATE_FIELD_NOT_INITIALIZED, [Field `FieldNotInitialized$ConditionalFieldInit.o1` is not initialized in the constructor and is not declared `@Nullable`]
codetoanalyze/java/eradicate/FieldNotInitialized.java, FieldNotInitialized$InitCircular.<init>(FieldNotInitialized), 0, ERADICATE_FIELD_NOT_INITIALIZED, [Field `FieldNotInitialized$InitCircular.s` is not initialized in the constructor and is not declared `@Nullable`] codetoanalyze/java/eradicate/FieldNotInitialized.java, FieldNotInitialized$InitCircular.<init>(FieldNotInitialized), 0, ERADICATE_FIELD_NOT_INITIALIZED, [Field `FieldNotInitialized$InitCircular.s` is not initialized in the constructor and is not declared `@Nullable`]
codetoanalyze/java/eradicate/FieldNotInitialized.java, FieldNotInitialized$OnlyRead.<init>(FieldNotInitialized), 0, ERADICATE_FIELD_NOT_INITIALIZED, [Field `FieldNotInitialized$OnlyRead.o` is not initialized in the constructor and is not declared `@Nullable`] codetoanalyze/java/eradicate/FieldNotInitialized.java, FieldNotInitialized$OnlyRead.<init>(FieldNotInitialized), 0, ERADICATE_FIELD_NOT_INITIALIZED, [Field `FieldNotInitialized$OnlyRead.o` is not initialized in the constructor and is not declared `@Nullable`]
@ -30,15 +37,13 @@ codetoanalyze/java/eradicate/NullFieldAccess.java, int NullFieldAccess.arrayLeng
codetoanalyze/java/eradicate/NullFieldAccess.java, int NullFieldAccess.useInterface(NullFieldAccess$I), 2, ERADICATE_NULL_FIELD_ACCESS, [origin,Object `c` could be null when accessing field `NullFieldAccess$C.n`. (Origin: field NullFieldAccess$I.c at line 52)] codetoanalyze/java/eradicate/NullFieldAccess.java, int NullFieldAccess.useInterface(NullFieldAccess$I), 2, ERADICATE_NULL_FIELD_ACCESS, [origin,Object `c` could be null when accessing field `NullFieldAccess$C.n`. (Origin: field NullFieldAccess$I.c at line 52)]
codetoanalyze/java/eradicate/NullFieldAccess.java, int NullFieldAccess.useX(), 2, ERADICATE_NULL_FIELD_ACCESS, [origin,Object `c` could be null when accessing field `NullFieldAccess$C.n`. (Origin: field NullFieldAccess.x at line 37)] codetoanalyze/java/eradicate/NullFieldAccess.java, int NullFieldAccess.useX(), 2, ERADICATE_NULL_FIELD_ACCESS, [origin,Object `c` could be null when accessing field `NullFieldAccess$C.n`. (Origin: field NullFieldAccess.x at line 37)]
codetoanalyze/java/eradicate/NullFieldAccess.java, int NullFieldAccess.useZ(), 2, ERADICATE_NULL_FIELD_ACCESS, [origin,Object `c` could be null when accessing field `NullFieldAccess$C.n`. (Origin: field NullFieldAccess.z at line 47)] codetoanalyze/java/eradicate/NullFieldAccess.java, int NullFieldAccess.useZ(), 2, ERADICATE_NULL_FIELD_ACCESS, [origin,Object `c` could be null when accessing field `NullFieldAccess$C.n`. (Origin: field NullFieldAccess.z at line 47)]
codetoanalyze/java/eradicate/NullMethodCall.java, int NullMethodCall$Inner.outerField(), 2, ERADICATE_NULL_METHOD_CALL, [origin,The value of `s` in the call to `length()` could be null. (Origin: field NullMethodCall.fld at line 74)] codetoanalyze/java/eradicate/NullMethodCall.java, int NullMethodCall$Inner.outerField(), 2, ERADICATE_NULL_METHOD_CALL, [origin,The value of `s` in the call to `length()` could be null. (Origin: field NullMethodCall.fld at line 71)]
codetoanalyze/java/eradicate/NullMethodCall.java, int NullMethodCall$Inner.outerPrivateField(), 2, ERADICATE_NULL_METHOD_CALL, [origin,The value of `s` in the call to `length()` could be null. (Origin: field NullMethodCall.pfld at line 85)] codetoanalyze/java/eradicate/NullMethodCall.java, int NullMethodCall$Inner.outerPrivateField(), 2, ERADICATE_NULL_METHOD_CALL, [origin,The value of `s` in the call to `length()` could be null. (Origin: field NullMethodCall.pfld at line 82)]
codetoanalyze/java/eradicate/NullMethodCall.java, void NullMethodCall$TestTextUtilsIsEmpty.myTextUtilsIsEmpty(CharSequence), 2, ERADICATE_NULL_METHOD_CALL, [The value of `s` in the call to `toString()` could be null. (Origin: method parameter s)] codetoanalyze/java/eradicate/NullMethodCall.java, void NullMethodCall.callOnNull(), 2, ERADICATE_NULL_METHOD_CALL, [origin,The value of `s` in the call to `length()` could be null. (Origin: null constant at line 22)]
codetoanalyze/java/eradicate/NullMethodCall.java, void NullMethodCall$TestTextUtilsIsEmpty.myTextUtilsNotIsNotEmpty(CharSequence), 2, ERADICATE_NULL_METHOD_CALL, [The value of `s` in the call to `toString()` could be null. (Origin: method parameter s)] codetoanalyze/java/eradicate/NullMethodCall.java, void NullMethodCall.testExceptionPerInstruction(int), 6, ERADICATE_NULL_METHOD_CALL, [origin,The value of `s` in the call to `length()` could be null. (Origin: null constant at line 183)]
codetoanalyze/java/eradicate/NullMethodCall.java, void NullMethodCall$TestTextUtilsIsEmpty.textUtilsIsEmpty(CharSequence), 2, ERADICATE_NULL_METHOD_CALL, [The value of `s` in the call to `toString()` could be null. (Origin: method parameter s)] codetoanalyze/java/eradicate/NullMethodCall.java, void NullMethodCall.testFieldAssignmentIfThenElse(String), 2, ERADICATE_NULL_METHOD_CALL, [origin,The value of `s` in the call to `length()` could be null. (Origin: null constant at line 174)]
codetoanalyze/java/eradicate/NullMethodCall.java, void NullMethodCall.callOnNull(), 2, ERADICATE_NULL_METHOD_CALL, [origin,The value of `s` in the call to `length()` could be null. (Origin: null constant at line 25)] codetoanalyze/java/eradicate/NullMethodCall.java, void NullMethodCall.testSystemGetPropertyReturn(), 2, ERADICATE_NULL_METHOD_CALL, [origin,The value of `s` in the call to `length()` could be null. (Origin: call to getProperty(...) modelled in eradicate/modelTables.ml at line 238)]
codetoanalyze/java/eradicate/NullMethodCall.java, void NullMethodCall.testExceptionPerInstruction(int), 6, ERADICATE_NULL_METHOD_CALL, [origin,The value of `s` in the call to `length()` could be null. (Origin: null constant at line 186)] codetoanalyze/java/eradicate/ParameterNotNullable.java, ParameterNotNullable$ConstructorCall.<init>(ParameterNotNullable,int), 1, ERADICATE_PARAMETER_NOT_NULLABLE, [origin,`ParameterNotNullable$ConstructorCall(...)` needs a non-null value in parameter 2 but argument `null` can be null. (Origin: null constant at line 98)]
codetoanalyze/java/eradicate/NullMethodCall.java, void NullMethodCall.testFieldAssignmentIfThenElse(String), 2, ERADICATE_NULL_METHOD_CALL, [origin,The value of `s` in the call to `length()` could be null. (Origin: null constant at line 177)]
codetoanalyze/java/eradicate/NullMethodCall.java, void NullMethodCall.testSystemGetPropertyReturn(), 2, ERADICATE_NULL_METHOD_CALL, [origin,The value of `s` in the call to `length()` could be null. (Origin: call to getProperty(...) modelled in eradicate/modelTables.ml at line 241)]
codetoanalyze/java/eradicate/ParameterNotNullable.java, String ParameterNotNullable.testSystemGetPropertyArgument(), 1, ERADICATE_PARAMETER_NOT_NULLABLE, [origin,`getProperty(...)` needs a non-null value in parameter 1 but argument `null` can be null. (Origin: null constant at line 71)] codetoanalyze/java/eradicate/ParameterNotNullable.java, String ParameterNotNullable.testSystemGetPropertyArgument(), 1, ERADICATE_PARAMETER_NOT_NULLABLE, [origin,`getProperty(...)` needs a non-null value in parameter 1 but argument `null` can be null. (Origin: null constant at line 71)]
codetoanalyze/java/eradicate/ParameterNotNullable.java, URL ParameterNotNullable.testClassGetResourceArgument(Class), 1, ERADICATE_PARAMETER_NOT_NULLABLE, [origin,`getResource(...)` needs a non-null value in parameter 1 but argument `null` can be null. (Origin: null constant at line 76)] codetoanalyze/java/eradicate/ParameterNotNullable.java, URL ParameterNotNullable.testClassGetResourceArgument(Class), 1, ERADICATE_PARAMETER_NOT_NULLABLE, [origin,`getResource(...)` needs a non-null value in parameter 1 but argument `null` can be null. (Origin: null constant at line 76)]
codetoanalyze/java/eradicate/ParameterNotNullable.java, void ParameterNotNullable.callNull(), 2, ERADICATE_PARAMETER_NOT_NULLABLE, [origin,`test(...)` needs a non-null value in parameter 1 but argument `s` can be null. (Origin: null constant at line 38)] codetoanalyze/java/eradicate/ParameterNotNullable.java, void ParameterNotNullable.callNull(), 2, ERADICATE_PARAMETER_NOT_NULLABLE, [origin,`test(...)` needs a non-null value in parameter 1 but argument `s` can be null. (Origin: null constant at line 38)]

Loading…
Cancel
Save