Summary: These were not used (and were actually activated byt the same config param). They both are in experimental stage that never reached maturity. Since the team does not have immediate plans to work on ObjC nullability checker; and since "eradicate" (now known as nullsafe) is the main solution for Java, removing it is sensible. Reviewed By: jvillard Differential Revision: D20279866 fbshipit-source-id: 79e64992bmaster
parent
fa4f9ef5f3
commit
bd83813b3e
@ -1,339 +0,0 @@
|
|||||||
(*
|
|
||||||
* 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.
|
|
||||||
*)
|
|
||||||
|
|
||||||
open! IStd
|
|
||||||
module F = Format
|
|
||||||
module L = Logging
|
|
||||||
module MF = MarkupFormatter
|
|
||||||
module CallSites = AbstractDomain.FiniteSet (CallSite)
|
|
||||||
module NullableAP = AbstractDomain.Map (AccessPath) (CallSites)
|
|
||||||
module NullCheckedPname = AbstractDomain.InvertedSet (Procname)
|
|
||||||
module Domain = AbstractDomain.Pair (NullableAP) (NullCheckedPname)
|
|
||||||
|
|
||||||
module TransferFunctions (CFG : ProcCfg.S) = struct
|
|
||||||
module CFG = CFG
|
|
||||||
module Domain = Domain
|
|
||||||
|
|
||||||
type extras = unit
|
|
||||||
|
|
||||||
let rec is_pointer_subtype tenv typ1 typ2 =
|
|
||||||
match (typ1.Typ.desc, typ2.Typ.desc) with
|
|
||||||
| Typ.Tptr (t1, _), Typ.Tptr (t2, _) -> (
|
|
||||||
match (t1.Typ.desc, t2.Typ.desc) with
|
|
||||||
| Typ.Tstruct n1, Typ.Tstruct n2 ->
|
|
||||||
Subtype.is_known_subtype tenv n1 n2
|
|
||||||
| _ ->
|
|
||||||
Typ.equal t1 t2 || is_pointer_subtype tenv t1 t2 )
|
|
||||||
| _ ->
|
|
||||||
false
|
|
||||||
|
|
||||||
|
|
||||||
let is_non_objc_instance_method callee_pname =
|
|
||||||
match callee_pname with
|
|
||||||
| Procname.Java java_pname ->
|
|
||||||
not (Procname.Java.is_static java_pname)
|
|
||||||
| _ ->
|
|
||||||
Option.exists
|
|
||||||
~f:(fun attributes ->
|
|
||||||
ClangMethodKind.equal attributes.ProcAttributes.clang_method_kind
|
|
||||||
ClangMethodKind.CPP_INSTANCE )
|
|
||||||
(Summary.OnDisk.proc_resolve_attributes callee_pname)
|
|
||||||
|
|
||||||
|
|
||||||
let is_objc_instance_method callee_pname =
|
|
||||||
Option.exists
|
|
||||||
~f:(fun attributes ->
|
|
||||||
ClangMethodKind.equal attributes.ProcAttributes.clang_method_kind
|
|
||||||
ClangMethodKind.OBJC_INSTANCE )
|
|
||||||
(Summary.OnDisk.proc_resolve_attributes callee_pname)
|
|
||||||
|
|
||||||
|
|
||||||
let is_blacklisted_method : Procname.t -> bool =
|
|
||||||
let blacklist = ["URLWithString:"; "objectForKeyedSubscript:"] in
|
|
||||||
fun proc_name ->
|
|
||||||
let simplified_callee_pname = Procname.to_simplified_string proc_name in
|
|
||||||
List.exists ~f:(String.equal simplified_callee_pname) blacklist
|
|
||||||
|
|
||||||
|
|
||||||
let container_method_regex =
|
|
||||||
Str.regexp @@ "^\\(NS.*::\\(arrayByAddingObject\\|arrayWithObjects\\|"
|
|
||||||
^ "dictionaryWithObjects\\|dictionaryWithObjectsAndKeys\\|initWithObjectsAndKeys\\|"
|
|
||||||
^ "addObject\\|insertObject\\|setObject\\|"
|
|
||||||
^ "stringWithUTF8String\\|stringWithString\\|initWithFormat\\|stringByAppendingString\\):\\|"
|
|
||||||
^ "std::basic_string\\).*"
|
|
||||||
|
|
||||||
|
|
||||||
let is_objc_container_add_method proc_name =
|
|
||||||
let callee_pname = Procname.to_string proc_name in
|
|
||||||
Str.string_match container_method_regex callee_pname 0
|
|
||||||
|
|
||||||
|
|
||||||
let is_conflicting_report summary report_location =
|
|
||||||
(* FIXME(T54950303) replace use of filtering with deduplicate *)
|
|
||||||
if not Config.filtering then false
|
|
||||||
else
|
|
||||||
Errlog.fold
|
|
||||||
(fun {Errlog.err_name; err_desc} {Errlog.loc} found_confict ->
|
|
||||||
found_confict
|
|
||||||
|| IssueType.equal err_name IssueType.null_dereference
|
|
||||||
&& Location.equal loc report_location
|
|
||||||
&& Localise.error_desc_is_reportable_bucket err_desc )
|
|
||||||
(Summary.get_err_log summary) false
|
|
||||||
|
|
||||||
|
|
||||||
(* On Clang languages, the annotations like _Nullabe can be found on the declaration
|
|
||||||
or on the implementation without the two being necessarily consistent.
|
|
||||||
Here, we explicitely want to lookup the annotations locally: either form
|
|
||||||
the implementation when defined locally, or from the included headers *)
|
|
||||||
let lookup_local_attributes = function
|
|
||||||
| Procname.Java _ as pname ->
|
|
||||||
(* Looking up the attribute according to the classpath *)
|
|
||||||
Summary.OnDisk.proc_resolve_attributes pname
|
|
||||||
| pname ->
|
|
||||||
(* Looking up the attributes locally, i.e. either from the file of from the includes *)
|
|
||||||
Option.map ~f:Procdesc.get_attributes (Ondemand.get_proc_desc pname)
|
|
||||||
|
|
||||||
|
|
||||||
let report_nullable_dereference ap call_sites {ProcData.summary} loc =
|
|
||||||
if is_conflicting_report summary loc then ()
|
|
||||||
else
|
|
||||||
let pname = Summary.get_proc_name summary in
|
|
||||||
let annotation = Localise.nullable_annotation_name pname in
|
|
||||||
let call_site =
|
|
||||||
try CallSites.min_elt call_sites
|
|
||||||
with Caml.Not_found ->
|
|
||||||
L.(die InternalError)
|
|
||||||
"Expecting a least one element in the set of call sites when analyzing %a" Procname.pp
|
|
||||||
pname
|
|
||||||
in
|
|
||||||
let simplified_pname =
|
|
||||||
Procname.to_simplified_string ~withclass:true (CallSite.pname call_site)
|
|
||||||
in
|
|
||||||
let is_direct_dereference =
|
|
||||||
match ap with
|
|
||||||
| (Var.LogicalVar _, _), _ ->
|
|
||||||
true
|
|
||||||
| (Var.ProgramVar pvar, _), _ ->
|
|
||||||
Pvar.is_frontend_tmp pvar
|
|
||||||
in
|
|
||||||
let message =
|
|
||||||
if is_direct_dereference then
|
|
||||||
(* direct dereference without intermediate variable *)
|
|
||||||
F.asprintf
|
|
||||||
"The return value of %s is annotated with %a and is dereferenced without being checked \
|
|
||||||
for null at %a"
|
|
||||||
(MF.monospaced_to_string simplified_pname)
|
|
||||||
MF.pp_monospaced annotation Location.pp loc
|
|
||||||
else
|
|
||||||
(* dereference with intermediate variable *)
|
|
||||||
F.asprintf
|
|
||||||
"Variable %a is indirectly annotated with %a (source %a) and is dereferenced without \
|
|
||||||
being checked for null at %a"
|
|
||||||
(MF.wrap_monospaced AccessPath.pp)
|
|
||||||
ap MF.pp_monospaced annotation (MF.wrap_monospaced CallSite.pp) call_site Location.pp
|
|
||||||
loc
|
|
||||||
in
|
|
||||||
let trace =
|
|
||||||
let with_origin_site =
|
|
||||||
let callee_pname = CallSite.pname call_site in
|
|
||||||
match lookup_local_attributes callee_pname with
|
|
||||||
| None ->
|
|
||||||
[]
|
|
||||||
| Some attributes ->
|
|
||||||
let description = F.asprintf "definition of %s" (Procname.get_method callee_pname) in
|
|
||||||
let trace_element =
|
|
||||||
Errlog.make_trace_element 1 attributes.ProcAttributes.loc description []
|
|
||||||
in
|
|
||||||
[trace_element]
|
|
||||||
in
|
|
||||||
let with_assignment_site =
|
|
||||||
let call_site_loc = CallSite.loc call_site in
|
|
||||||
if Location.equal call_site_loc loc then with_origin_site
|
|
||||||
else
|
|
||||||
let trace_element =
|
|
||||||
Errlog.make_trace_element 0 call_site_loc "assignment of the nullable value" []
|
|
||||||
in
|
|
||||||
trace_element :: with_origin_site
|
|
||||||
in
|
|
||||||
let dereference_site =
|
|
||||||
let description =
|
|
||||||
if is_direct_dereference then
|
|
||||||
F.asprintf "dereferencing the return of %s" simplified_pname
|
|
||||||
else F.asprintf "dereference of %a" AccessPath.pp ap
|
|
||||||
in
|
|
||||||
Errlog.make_trace_element 0 loc description []
|
|
||||||
in
|
|
||||||
dereference_site :: with_assignment_site
|
|
||||||
in
|
|
||||||
Reporting.log_error summary ~loc ~ltr:trace IssueType.nullsafe_nullable_dereference message
|
|
||||||
|
|
||||||
|
|
||||||
let add_nullable_ap ap call_sites (aps, pnames) = (NullableAP.add ap call_sites aps, pnames)
|
|
||||||
|
|
||||||
let remove_nullable_ap ap (aps, pnames) = (NullableAP.remove ap aps, pnames)
|
|
||||||
|
|
||||||
let find_nullable_ap ap (aps, _) = NullableAP.find ap aps
|
|
||||||
|
|
||||||
let assume_pnames_notnull ap (aps, checked_pnames) : Domain.t =
|
|
||||||
let remove_call_sites ap aps =
|
|
||||||
let add_diff (to_remove : CallSites.t) ap call_sites map =
|
|
||||||
let remaining_call_sites = CallSites.diff call_sites to_remove in
|
|
||||||
if CallSites.is_empty remaining_call_sites then map
|
|
||||||
else NullableAP.add ap remaining_call_sites map
|
|
||||||
in
|
|
||||||
match NullableAP.find_opt ap aps with
|
|
||||||
| None ->
|
|
||||||
aps
|
|
||||||
| Some call_sites ->
|
|
||||||
let updated_aps = NullableAP.fold (add_diff call_sites) aps NullableAP.empty in
|
|
||||||
updated_aps
|
|
||||||
in
|
|
||||||
let updated_pnames =
|
|
||||||
try
|
|
||||||
let call_sites = NullableAP.find ap aps in
|
|
||||||
CallSites.fold
|
|
||||||
(fun call_site s -> NullCheckedPname.add (CallSite.pname call_site) s)
|
|
||||||
call_sites checked_pnames
|
|
||||||
with Caml.Not_found -> checked_pnames
|
|
||||||
in
|
|
||||||
(remove_call_sites ap aps, updated_pnames)
|
|
||||||
|
|
||||||
|
|
||||||
let rec longest_nullable_prefix ap ((nullable_aps, _) as astate) =
|
|
||||||
try Some (ap, NullableAP.find ap nullable_aps)
|
|
||||||
with Caml.Not_found -> (
|
|
||||||
match ap with
|
|
||||||
| _, [] ->
|
|
||||||
None
|
|
||||||
| p ->
|
|
||||||
longest_nullable_prefix (fst (AccessPath.truncate p)) astate )
|
|
||||||
|
|
||||||
|
|
||||||
let check_ap proc_data loc ap astate =
|
|
||||||
match longest_nullable_prefix ap astate with
|
|
||||||
| None ->
|
|
||||||
astate
|
|
||||||
| Some (nullable_ap, call_sites) ->
|
|
||||||
report_nullable_dereference nullable_ap call_sites proc_data loc ;
|
|
||||||
assume_pnames_notnull ap astate
|
|
||||||
|
|
||||||
|
|
||||||
let rec check_nil_in_objc_container proc_data loc args astate =
|
|
||||||
match args with
|
|
||||||
| [] ->
|
|
||||||
astate
|
|
||||||
| [arg] when HilExp.is_null_literal arg ->
|
|
||||||
astate
|
|
||||||
| HilExp.AccessExpression access_expr :: other_args ->
|
|
||||||
let ap = HilExp.AccessExpression.to_access_path access_expr in
|
|
||||||
check_nil_in_objc_container proc_data loc other_args (check_ap proc_data loc ap astate)
|
|
||||||
| _ :: other_args ->
|
|
||||||
check_nil_in_objc_container proc_data loc other_args astate
|
|
||||||
|
|
||||||
|
|
||||||
let exec_instr ((_, checked_pnames) as astate) proc_data _ (instr : HilInstr.t) : Domain.t =
|
|
||||||
let is_pointer_assignment tenv lhs rhs =
|
|
||||||
(* the rhs has type int when assigning the lhs to null *)
|
|
||||||
if HilExp.is_null_literal rhs then true
|
|
||||||
(* the lhs and rhs have the same type in the case of pointer assignment
|
|
||||||
but the types are different when assigning the pointee *)
|
|
||||||
else
|
|
||||||
match (AccessPath.get_typ lhs tenv, HilExp.get_typ tenv rhs) with
|
|
||||||
(* defensive assumption when the types are not known *)
|
|
||||||
| None, _ | _, None ->
|
|
||||||
true
|
|
||||||
(* the rhs can be a subtype of the lhs *)
|
|
||||||
| Some lhs_typ, Some rhs_typ ->
|
|
||||||
is_pointer_subtype tenv rhs_typ lhs_typ
|
|
||||||
in
|
|
||||||
match instr with
|
|
||||||
| Call (ret_var, Direct callee_pname, _, _, _)
|
|
||||||
when NullCheckedPname.mem callee_pname checked_pnames ->
|
|
||||||
(* Do not report nullable when the method has already been checked for null *)
|
|
||||||
remove_nullable_ap (ret_var, []) astate
|
|
||||||
| Call (_, Direct callee_pname, HilExp.AccessExpression receiver :: _, _, _)
|
|
||||||
when Models.is_check_not_null callee_pname ->
|
|
||||||
assume_pnames_notnull (HilExp.AccessExpression.to_access_path receiver) astate
|
|
||||||
| Call (_, Direct callee_pname, _, _, _) when is_blacklisted_method callee_pname ->
|
|
||||||
astate
|
|
||||||
| Call (ret_var, Direct callee_pname, _, _, loc)
|
|
||||||
when Annotations.pname_has_return_annot callee_pname ~attrs_of_pname:lookup_local_attributes
|
|
||||||
Annotations.ia_is_nullable ->
|
|
||||||
let call_site = CallSite.make callee_pname loc in
|
|
||||||
add_nullable_ap (ret_var, []) (CallSites.singleton call_site) astate
|
|
||||||
| Call (_, Direct callee_pname, args, _, loc) when is_objc_container_add_method callee_pname ->
|
|
||||||
check_nil_in_objc_container proc_data loc args astate
|
|
||||||
| Call (_, Direct callee_pname, HilExp.AccessExpression receiver :: _, _, loc)
|
|
||||||
when is_non_objc_instance_method callee_pname ->
|
|
||||||
check_ap proc_data loc (HilExp.AccessExpression.to_access_path receiver) astate
|
|
||||||
| Call
|
|
||||||
(((_, ret_typ) as ret_var), Direct callee_pname, HilExp.AccessExpression receiver :: _, _, _)
|
|
||||||
when Typ.is_pointer ret_typ && is_objc_instance_method callee_pname -> (
|
|
||||||
match longest_nullable_prefix (HilExp.AccessExpression.to_access_path receiver) astate with
|
|
||||||
| None ->
|
|
||||||
astate
|
|
||||||
| Some (_, call_sites) ->
|
|
||||||
(* Objective C method will return nil when called on a nil receiver *)
|
|
||||||
add_nullable_ap (ret_var, []) call_sites astate )
|
|
||||||
| Call (ret_var, _, _, _, _) ->
|
|
||||||
remove_nullable_ap (ret_var, []) astate
|
|
||||||
| Assign (lhs_access_expr, rhs, loc) -> (
|
|
||||||
let lhs = HilExp.AccessExpression.to_access_path lhs_access_expr in
|
|
||||||
Option.iter
|
|
||||||
~f:(fun (nullable_ap, call_sites) ->
|
|
||||||
if not (is_pointer_assignment proc_data.ProcData.tenv nullable_ap rhs) then
|
|
||||||
(* TODO (T22426288): Undertand why the pointer derference and the pointer
|
|
||||||
assignment have the same HIL representation *)
|
|
||||||
report_nullable_dereference nullable_ap call_sites proc_data loc )
|
|
||||||
(longest_nullable_prefix lhs astate) ;
|
|
||||||
match rhs with
|
|
||||||
| HilExp.AccessExpression access_expr -> (
|
|
||||||
try
|
|
||||||
(* Add the lhs to the list of nullable values if the rhs is nullable *)
|
|
||||||
let ap = HilExp.AccessExpression.to_access_path access_expr in
|
|
||||||
add_nullable_ap lhs (find_nullable_ap ap astate) astate
|
|
||||||
with Caml.Not_found ->
|
|
||||||
(* Remove the lhs from the list of nullable values if the rhs is not nullable *)
|
|
||||||
remove_nullable_ap lhs astate )
|
|
||||||
| _ ->
|
|
||||||
(* Remove the lhs from the list of nullable values if the rhs is not an access path *)
|
|
||||||
remove_nullable_ap lhs astate )
|
|
||||||
| Assume (HilExp.AccessExpression access_expr, _, _, _) ->
|
|
||||||
assume_pnames_notnull (HilExp.AccessExpression.to_access_path access_expr) astate
|
|
||||||
| Assume
|
|
||||||
( ( HilExp.BinaryOperator (Binop.Ne, HilExp.AccessExpression access_expr, exp)
|
|
||||||
| HilExp.BinaryOperator (Binop.Ne, exp, HilExp.AccessExpression access_expr) )
|
|
||||||
, _
|
|
||||||
, _
|
|
||||||
, _ )
|
|
||||||
| Assume
|
|
||||||
( HilExp.UnaryOperator
|
|
||||||
( Unop.LNot
|
|
||||||
, ( HilExp.BinaryOperator (Binop.Eq, HilExp.AccessExpression access_expr, exp)
|
|
||||||
| HilExp.BinaryOperator (Binop.Eq, exp, HilExp.AccessExpression access_expr) )
|
|
||||||
, _ )
|
|
||||||
, _
|
|
||||||
, _
|
|
||||||
, _ ) ->
|
|
||||||
if HilExp.is_null_literal exp then
|
|
||||||
assume_pnames_notnull (HilExp.AccessExpression.to_access_path access_expr) astate
|
|
||||||
else astate
|
|
||||||
| _ ->
|
|
||||||
astate
|
|
||||||
|
|
||||||
|
|
||||||
let pp_session_name _node fmt = F.pp_print_string fmt "nullability check"
|
|
||||||
end
|
|
||||||
|
|
||||||
module Analyzer = LowerHil.MakeAbstractInterpreter (TransferFunctions (ProcCfg.Exceptional))
|
|
||||||
|
|
||||||
let checker {Callbacks.summary; exe_env} =
|
|
||||||
let initial = (NullableAP.empty, NullCheckedPname.empty) in
|
|
||||||
let tenv = Exe_env.get_tenv exe_env (Summary.get_proc_name summary) in
|
|
||||||
let proc_data = ProcData.make summary tenv () in
|
|
||||||
ignore (Analyzer.compute_post proc_data ~initial) ;
|
|
||||||
summary
|
|
@ -1,10 +0,0 @@
|
|||||||
(*
|
|
||||||
* 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.
|
|
||||||
*)
|
|
||||||
|
|
||||||
open! IStd
|
|
||||||
|
|
||||||
val checker : Callbacks.proc_callback_t
|
|
@ -1,236 +0,0 @@
|
|||||||
(*
|
|
||||||
* 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.
|
|
||||||
*)
|
|
||||||
open! IStd
|
|
||||||
module F = Format
|
|
||||||
module L = Logging
|
|
||||||
module MF = MarkupFormatter
|
|
||||||
|
|
||||||
module UseDefChain = struct
|
|
||||||
type t =
|
|
||||||
| DependsOn of (Location.t * AccessPath.t)
|
|
||||||
| NullDefCompare of (Location.t * AccessPath.t)
|
|
||||||
| NullDefAssign of (Location.t * AccessPath.t)
|
|
||||||
[@@deriving compare]
|
|
||||||
|
|
||||||
let leq ~lhs ~rhs = compare lhs rhs <= 0
|
|
||||||
|
|
||||||
(* Keep only one chain in join/widen as we are going to report only one
|
|
||||||
* trace to the user eventually. *)
|
|
||||||
let join lhs rhs = if leq ~lhs ~rhs then rhs else lhs
|
|
||||||
|
|
||||||
let widen ~prev ~next ~num_iters:_ = join prev next
|
|
||||||
|
|
||||||
let pp fmt = function
|
|
||||||
| NullDefAssign (loc, ap) ->
|
|
||||||
F.fprintf fmt "NullDefAssign(%a, %a)" Location.pp loc AccessPath.pp ap
|
|
||||||
| NullDefCompare (loc, ap) ->
|
|
||||||
F.fprintf fmt "NullDefCompare(%a, %a)" Location.pp loc AccessPath.pp ap
|
|
||||||
| DependsOn (loc, ap) ->
|
|
||||||
F.fprintf fmt "DependsOn(%a, %a)" Location.pp loc AccessPath.pp ap
|
|
||||||
|
|
||||||
|
|
||||||
module Set = Caml.Set.Make (struct
|
|
||||||
type nonrec t = t
|
|
||||||
|
|
||||||
let compare = compare
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
module Domain = AbstractDomain.Map (AccessPath) (UseDefChain)
|
|
||||||
|
|
||||||
type extras = ProcData.no_extras
|
|
||||||
|
|
||||||
module TransferFunctions (CFG : ProcCfg.S) = struct
|
|
||||||
module CFG = CFG
|
|
||||||
module Domain = Domain
|
|
||||||
|
|
||||||
type nonrec extras = extras
|
|
||||||
|
|
||||||
let is_access_nullable ap proc_data =
|
|
||||||
match AccessPath.get_field_and_annotation ap proc_data.ProcData.tenv with
|
|
||||||
| Some (_, annot_item) ->
|
|
||||||
Annotations.ia_is_nullable annot_item
|
|
||||||
| _ ->
|
|
||||||
false
|
|
||||||
|
|
||||||
|
|
||||||
let rec nullable_usedef_chain_of exp lhs astate loc =
|
|
||||||
match exp with
|
|
||||||
| HilExp.Cast (_, e) ->
|
|
||||||
nullable_usedef_chain_of e lhs astate loc
|
|
||||||
| HilExp.Constant (Cint n) when IntLit.isnull n ->
|
|
||||||
Some (UseDefChain.NullDefAssign (loc, lhs))
|
|
||||||
| HilExp.AccessExpression access_expr -> (
|
|
||||||
try
|
|
||||||
let ap = HilExp.AccessExpression.to_access_path access_expr in
|
|
||||||
match Domain.find ap astate with
|
|
||||||
| UseDefChain.NullDefCompare _ ->
|
|
||||||
(* Stop NullDefCompare from propagating here because we want to prevent
|
|
||||||
* the checker from suggesting @Nullable on y in the following case:
|
|
||||||
* if (x == null) ... else { y = x; } *)
|
|
||||||
None
|
|
||||||
| _ ->
|
|
||||||
Some (UseDefChain.DependsOn (loc, ap))
|
|
||||||
with Caml.Not_found -> None )
|
|
||||||
| _ ->
|
|
||||||
None
|
|
||||||
|
|
||||||
|
|
||||||
let rec extract_null_compare_expr = function
|
|
||||||
| HilExp.Cast (_, e) ->
|
|
||||||
extract_null_compare_expr e
|
|
||||||
| HilExp.BinaryOperator ((Eq | Ne), HilExp.AccessExpression access_expr, exp)
|
|
||||||
| HilExp.BinaryOperator ((Eq | Ne), exp, HilExp.AccessExpression access_expr) ->
|
|
||||||
Option.some_if (HilExp.is_null_literal exp)
|
|
||||||
(HilExp.AccessExpression.to_access_path access_expr)
|
|
||||||
| _ ->
|
|
||||||
None
|
|
||||||
|
|
||||||
|
|
||||||
let exec_instr (astate : Domain.t) proc_data _ (instr : HilInstr.t) =
|
|
||||||
match instr with
|
|
||||||
| Assume (expr, _, _, loc) -> (
|
|
||||||
match extract_null_compare_expr expr with
|
|
||||||
| Some ap when not (is_access_nullable ap proc_data) ->
|
|
||||||
let udchain = UseDefChain.NullDefCompare (loc, ap) in
|
|
||||||
Domain.add ap udchain astate
|
|
||||||
| _ ->
|
|
||||||
astate )
|
|
||||||
| Call _ ->
|
|
||||||
(* For now we just assume the callee always return non-null *)
|
|
||||||
astate
|
|
||||||
| Assign (lhs_access_expr, rhs, loc) ->
|
|
||||||
let lhs = HilExp.AccessExpression.to_access_path lhs_access_expr in
|
|
||||||
if not (is_access_nullable lhs proc_data) then
|
|
||||||
match nullable_usedef_chain_of rhs lhs astate loc with
|
|
||||||
| Some udchain ->
|
|
||||||
Domain.add lhs udchain astate
|
|
||||||
| None ->
|
|
||||||
astate
|
|
||||||
else astate
|
|
||||||
| Metadata _ ->
|
|
||||||
astate
|
|
||||||
|
|
||||||
|
|
||||||
let pp_session_name _node fmt = F.pp_print_string fmt "nullability suggest"
|
|
||||||
end
|
|
||||||
|
|
||||||
module Analyzer = LowerHil.MakeAbstractInterpreter (TransferFunctions (ProcCfg.Exceptional))
|
|
||||||
|
|
||||||
let make_error_trace astate ap ud =
|
|
||||||
let name_of ap =
|
|
||||||
match AccessPath.get_last_access ap with
|
|
||||||
| Some (AccessPath.FieldAccess field_name) ->
|
|
||||||
"Field " ^ Fieldname.get_field_name field_name
|
|
||||||
| Some (AccessPath.ArrayAccess _) ->
|
|
||||||
"Some array element"
|
|
||||||
| None ->
|
|
||||||
"Variable"
|
|
||||||
in
|
|
||||||
let open UseDefChain in
|
|
||||||
let rec error_trace_impl seen depth ap = function
|
|
||||||
| NullDefAssign (loc, src) ->
|
|
||||||
let msg = F.sprintf "%s is assigned null here" (name_of src) in
|
|
||||||
let ltr = [Errlog.make_trace_element depth loc msg []] in
|
|
||||||
Some (loc, ltr)
|
|
||||||
| NullDefCompare (loc, src) ->
|
|
||||||
let msg = F.sprintf "%s is compared to null here" (name_of src) in
|
|
||||||
let ltr = [Errlog.make_trace_element depth loc msg []] in
|
|
||||||
Some (loc, ltr)
|
|
||||||
| DependsOn (loc, dep) -> (
|
|
||||||
match Domain.find dep astate with
|
|
||||||
| exception Caml.Not_found ->
|
|
||||||
None
|
|
||||||
| ud' when Set.mem ud' seen ->
|
|
||||||
None
|
|
||||||
| ud' ->
|
|
||||||
let msg = F.sprintf "%s could be assigned here" (name_of ap) in
|
|
||||||
let trace_elem = Errlog.make_trace_element depth loc msg [] in
|
|
||||||
let seen' = Set.add ud' seen in
|
|
||||||
Option.map
|
|
||||||
(error_trace_impl seen' (depth + 1) dep ud')
|
|
||||||
~f:(fun (_, trace) -> (loc, trace_elem :: trace)) )
|
|
||||||
in
|
|
||||||
error_trace_impl Set.empty 0 ap ud
|
|
||||||
|
|
||||||
|
|
||||||
let pretty_field_name proc_data field_name =
|
|
||||||
match Summary.get_proc_name proc_data.ProcData.summary with
|
|
||||||
| Procname.Java jproc_name ->
|
|
||||||
let proc_class_name = Procname.Java.get_class_name jproc_name in
|
|
||||||
let field_class_name = Fieldname.get_class_name field_name |> Typ.Name.name in
|
|
||||||
if String.equal proc_class_name field_class_name then Fieldname.get_field_name field_name
|
|
||||||
else Fieldname.to_simplified_string field_name
|
|
||||||
| _ ->
|
|
||||||
(* This format is subject to change once this checker gets to run on C/Cpp/ObjC *)
|
|
||||||
Fieldname.to_string field_name
|
|
||||||
|
|
||||||
|
|
||||||
(* Checks if a field name stems from a class outside the domain of what is analyzed by Infer *)
|
|
||||||
let is_outside_codebase proc_name field_name =
|
|
||||||
match proc_name with
|
|
||||||
| Procname.Java _ ->
|
|
||||||
Typ.Name.Java.is_external (Fieldname.get_class_name field_name)
|
|
||||||
| _ ->
|
|
||||||
false
|
|
||||||
|
|
||||||
|
|
||||||
let checker {Callbacks.summary; exe_env} =
|
|
||||||
let proc_desc = Summary.get_proc_desc summary in
|
|
||||||
let proc_name = Procdesc.get_proc_name proc_desc in
|
|
||||||
let tenv = Exe_env.get_tenv exe_env proc_name in
|
|
||||||
let annotation = Localise.nullable_annotation_name proc_name in
|
|
||||||
let report astate (proc_data : extras ProcData.t) =
|
|
||||||
let report_access_path ap udchain =
|
|
||||||
match AccessPath.get_field_and_annotation ap proc_data.tenv with
|
|
||||||
| Some (field_name, _) when is_outside_codebase proc_name field_name ->
|
|
||||||
(* Skip reporting when the field is outside the analyzed codebase.
|
|
||||||
Note that we do similar filtering on high level which is common
|
|
||||||
for all checkers.
|
|
||||||
But this one is different: here we look NOT at the function
|
|
||||||
to be reported (the one that is using the field), but the root
|
|
||||||
cause (the field with the wrong annotation itself).
|
|
||||||
NOTE: Ideally we'd like to support such filtering in the way that
|
|
||||||
is agnostic to particular checker, but it is not trivial to
|
|
||||||
do, so let's do it in ad hoc way.
|
|
||||||
*)
|
|
||||||
()
|
|
||||||
| Some (field_name, _) when Fieldname.is_java_captured_parameter field_name ->
|
|
||||||
(* Skip reporting when field comes from generated code *)
|
|
||||||
()
|
|
||||||
| Some (field_name, _) ->
|
|
||||||
let message =
|
|
||||||
F.asprintf "Field %a should be annotated with %a" MF.pp_monospaced
|
|
||||||
(pretty_field_name proc_data field_name)
|
|
||||||
MF.pp_monospaced annotation
|
|
||||||
in
|
|
||||||
let loc, ltr =
|
|
||||||
match make_error_trace astate ap udchain with
|
|
||||||
| Some (loc, ltr) ->
|
|
||||||
(loc, Some ltr)
|
|
||||||
| None ->
|
|
||||||
(Procdesc.get_loc proc_desc, None)
|
|
||||||
in
|
|
||||||
Reporting.log_warning summary ~loc ?ltr IssueType.nullsafe_field_not_nullable message
|
|
||||||
| _ ->
|
|
||||||
()
|
|
||||||
in
|
|
||||||
Domain.iter report_access_path astate
|
|
||||||
in
|
|
||||||
if AndroidFramework.is_destroy_method proc_name then
|
|
||||||
(* Skip the fields nullified in Fragment onDestroy and onDestroyView *)
|
|
||||||
summary
|
|
||||||
else
|
|
||||||
(* Assume all fields are not null in the beginning *)
|
|
||||||
let initial = Domain.empty in
|
|
||||||
let proc_data = ProcData.make_default summary tenv in
|
|
||||||
( match Analyzer.compute_post proc_data ~initial with
|
|
||||||
| Some post ->
|
|
||||||
report post proc_data
|
|
||||||
| None ->
|
|
||||||
L.internal_error "Analyzer failed to compute post for %a@." Procname.pp proc_name ) ;
|
|
||||||
summary
|
|
@ -1,11 +0,0 @@
|
|||||||
(*
|
|
||||||
* 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.
|
|
||||||
*)
|
|
||||||
(* Module that suggest adding nullability annotations *)
|
|
||||||
|
|
||||||
open! IStd
|
|
||||||
|
|
||||||
val checker : Callbacks.proc_callback_t
|
|
@ -1,3 +1,2 @@
|
|||||||
codetoanalyze/cpp/conflicts/test.cpp, test1_bad, 2, NULL_DEREFERENCE, no_bucket, ERROR, [start of procedure test1_bad(),Skipping nullableMethod(): method has no implementation]
|
codetoanalyze/cpp/conflicts/test.cpp, test1_bad, 2, NULL_DEREFERENCE, no_bucket, ERROR, [start of procedure test1_bad(),Skipping nullableMethod(): method has no implementation]
|
||||||
codetoanalyze/cpp/conflicts/test.cpp, test2_bad, 1, NULLSAFE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [dereferencing the return of nullableMethod(),definition of nullableMethod]
|
|
||||||
codetoanalyze/cpp/conflicts/test.cpp, test3_bad, 5, NULL_DEREFERENCE, no_bucket, ERROR, [start of procedure test3_bad(),Skipping nullableMethod(): method has no implementation,Loop condition is true. Entering loop body,Skipping nullableMethod(): method has no implementation,Loop condition is false. Leaving loop]
|
codetoanalyze/cpp/conflicts/test.cpp, test3_bad, 5, NULL_DEREFERENCE, no_bucket, ERROR, [start of procedure test3_bad(),Skipping nullableMethod(): method has no implementation,Loop condition is true. Entering loop body,Skipping nullableMethod(): method has no implementation,Loop condition is false. Leaving loop]
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
TESTS_DIR = ../../..
|
|
||||||
|
|
||||||
# see explanations in cpp/errors/Makefile for the custom isystem
|
|
||||||
CLANG_OPTIONS = -x c++ -std=c++11 -nostdinc++ -isystem$(ROOT_DIR) -isystem$(CLANG_INCLUDES)/c++/v1/ -c
|
|
||||||
INFER_OPTIONS = --biabduction --nullsafe --debug-exceptions --project-root $(TESTS_DIR)
|
|
||||||
INFERPRINT_OPTIONS = --issues-tests
|
|
||||||
|
|
||||||
SOURCES = $(wildcard *.cpp)
|
|
||||||
|
|
||||||
include $(TESTS_DIR)/clang.make
|
|
||||||
|
|
||||||
infer-out/report.json: $(MAKEFILE_LIST)
|
|
@ -1,62 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
class T {
|
|
||||||
private:
|
|
||||||
int* unnanotated_field;
|
|
||||||
int* _Nullable nullable_field;
|
|
||||||
int* _Nonnull nonnull_field;
|
|
||||||
|
|
||||||
public:
|
|
||||||
void assign_nullable_field_to_null_okay() { nullable_field = nullptr; }
|
|
||||||
|
|
||||||
public:
|
|
||||||
void assign_unnanotated_field_to_null_bad() { unnanotated_field = nullptr; }
|
|
||||||
|
|
||||||
public:
|
|
||||||
void assign_nonnull_field_to_null_bad() { nonnull_field = nullptr; }
|
|
||||||
|
|
||||||
public:
|
|
||||||
void test_nullable_field_for_null_okay() {
|
|
||||||
if (nullable_field == nullptr) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
void test_unnanotated_field_for_null_bad() {
|
|
||||||
if (unnanotated_field == nullptr) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
void test_nonnull_field_for_null_bad() {
|
|
||||||
if (nonnull_field == nullptr) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
void dereference_unnanotated_field_okay() { *unnanotated_field = 42; }
|
|
||||||
|
|
||||||
public:
|
|
||||||
void dereference_nonnull_field_okay() { *nonnull_field = 42; }
|
|
||||||
|
|
||||||
public:
|
|
||||||
void dereference_nullable_field_bad() { *nullable_field = 42; }
|
|
||||||
|
|
||||||
public:
|
|
||||||
void dereference_unnanotated_field_after_test_for_null_bad() {
|
|
||||||
if (unnanotated_field == nullptr) {
|
|
||||||
*unnanotated_field = 42;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
void FP_dereference_nonnull_field_after_test_for_null_okay() {
|
|
||||||
if (nonnull_field == nullptr) {
|
|
||||||
*nonnull_field = 42;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,53 +0,0 @@
|
|||||||
codetoanalyze/cpp/nullable/example.cpp, T::FP_dereference_nonnull_field_after_test_for_null_okay, 1, NULLSAFE_FIELD_NOT_NULLABLE, no_bucket, WARNING, [Field nonnull_field is compared to null here]
|
|
||||||
codetoanalyze/cpp/nullable/example.cpp, T::FP_dereference_nonnull_field_after_test_for_null_okay, 2, NULL_DEREFERENCE, B5, ERROR, [start of procedure FP_dereference_nonnull_field_after_test_for_null_okay,Taking true branch]
|
|
||||||
codetoanalyze/cpp/nullable/example.cpp, T::assign_nonnull_field_to_null_bad, 0, NULLSAFE_FIELD_NOT_NULLABLE, no_bucket, WARNING, [Field nonnull_field is assigned null here]
|
|
||||||
codetoanalyze/cpp/nullable/example.cpp, T::assign_unnanotated_field_to_null_bad, 0, NULLSAFE_FIELD_NOT_NULLABLE, no_bucket, WARNING, [Field unnanotated_field is assigned null here]
|
|
||||||
codetoanalyze/cpp/nullable/example.cpp, T::dereference_nullable_field_bad, 0, NULL_DEREFERENCE, B1, ERROR, [start of procedure dereference_nullable_field_bad]
|
|
||||||
codetoanalyze/cpp/nullable/example.cpp, T::dereference_unnanotated_field_after_test_for_null_bad, 1, NULLSAFE_FIELD_NOT_NULLABLE, no_bucket, WARNING, [Field unnanotated_field is compared to null here]
|
|
||||||
codetoanalyze/cpp/nullable/example.cpp, T::dereference_unnanotated_field_after_test_for_null_bad, 2, NULL_DEREFERENCE, B5, ERROR, [start of procedure dereference_unnanotated_field_after_test_for_null_bad,Taking true branch]
|
|
||||||
codetoanalyze/cpp/nullable/example.cpp, T::test_nonnull_field_for_null_bad, 1, NULLSAFE_FIELD_NOT_NULLABLE, no_bucket, WARNING, [Field nonnull_field is compared to null here]
|
|
||||||
codetoanalyze/cpp/nullable/example.cpp, T::test_unnanotated_field_for_null_bad, 1, NULLSAFE_FIELD_NOT_NULLABLE, no_bucket, WARNING, [Field unnanotated_field is compared to null here]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, assignNullableValueBad, 2, NULLSAFE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [dereference of p,assignment of the nullable value,definition of mayReturnNullPointer]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, assignNullableValueBad, 2, NULL_DEREFERENCE, B2, ERROR, [start of procedure assignNullableValueBad(),start of procedure mayReturnNullPointer,Taking true branch,return from a call to T::mayReturnNullPointer]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, avoidDoubleReportingBad, 2, NULLSAFE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [dereference of p,assignment of the nullable value,definition of mayReturnNullObject]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, avoidDoubleReportingBad, 2, NULL_DEREFERENCE, B2, ERROR, [start of procedure avoidDoubleReportingBad(),start of procedure mayReturnNullObject,Taking true branch,return from a call to T::mayReturnNullObject]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, callMethodOnNullableObjectBad, 1, NULLSAFE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [dereferencing the return of T::mayReturnNullObject,definition of mayReturnNullObject]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, callMethodOnNullableObjectBad, 1, NULL_DEREFERENCE, B5, ERROR, [start of procedure callMethodOnNullableObjectBad(),start of procedure mayReturnNullObject,Taking false branch,return from a call to T::mayReturnNullObject]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, callMethodOnNullableObjectOkay, 2, NULL_TEST_AFTER_DEREFERENCE, no_bucket, WARNING, [start of procedure callMethodOnNullableObjectOkay(),start of procedure mayReturnNullObject,Taking false branch,return from a call to T::mayReturnNullObject,Taking false branch]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, dereferenceFieldOfNullableObjectBad, 2, NULLSAFE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [dereference of p,assignment of the nullable value,definition of mayReturnNullObject]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, dereferenceFieldOfNullableObjectBad, 2, NULL_DEREFERENCE, B2, ERROR, [start of procedure dereferenceFieldOfNullableObjectBad(),start of procedure mayReturnNullObject,Taking true branch,return from a call to T::mayReturnNullObject]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, dereferenceOfAliasesCheckedForNullOkay, 3, NULL_TEST_AFTER_DEREFERENCE, no_bucket, WARNING, [start of procedure dereferenceOfAliasesCheckedForNullOkay(),start of procedure mayReturnNullObject,Taking false branch,return from a call to T::mayReturnNullObject,Taking false branch]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, methodAlwaysCheckedForNullOkay, 1, NULL_TEST_AFTER_DEREFERENCE, no_bucket, WARNING, [start of procedure methodAlwaysCheckedForNullOkay(),Taking true branch,start of procedure mayReturnNullObject,Taking false branch,return from a call to T::mayReturnNullObject,Taking false branch]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, methodAlwaysCheckedForNullOkay, 2, NULL_DEREFERENCE, B5, ERROR, [start of procedure methodAlwaysCheckedForNullOkay(),Taking true branch,start of procedure mayReturnNullObject,Taking false branch,return from a call to T::mayReturnNullObject,Taking true branch,start of procedure mayReturnNullObject,Taking false branch,return from a call to T::mayReturnNullObject]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, methodCallOnFieldOfNullableObjectBad, 2, NULLSAFE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [dereference of p,assignment of the nullable value,definition of mayReturnNullObject]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, methodCallOnFieldOfNullableObjectBad, 2, NULL_DEREFERENCE, B2, ERROR, [start of procedure methodCallOnFieldOfNullableObjectBad(),start of procedure mayReturnNullObject,Taking true branch,return from a call to T::mayReturnNullObject]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, methodCheckedForNullAndReturnOkay, 1, NULL_TEST_AFTER_DEREFERENCE, no_bucket, WARNING, [start of procedure methodCheckedForNullAndReturnOkay(),start of procedure mayReturnNullObject,Taking false branch,return from a call to T::mayReturnNullObject,Taking false branch]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, methodCheckedForNullAndReturnOkay, 4, NULL_DEREFERENCE, B5, ERROR, [start of procedure methodCheckedForNullAndReturnOkay(),start of procedure mayReturnNullObject,Taking false branch,return from a call to T::mayReturnNullObject,Taking false branch,start of procedure mayReturnNullObject,Taking false branch,return from a call to T::mayReturnNullObject]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, methodCheckedForNullOkay, 1, NULL_TEST_AFTER_DEREFERENCE, no_bucket, WARNING, [start of procedure methodCheckedForNullOkay(),start of procedure mayReturnNullObject,Taking false branch,return from a call to T::mayReturnNullObject,Taking false branch]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, methodCheckedForNullOkay, 2, NULL_DEREFERENCE, B5, ERROR, [start of procedure methodCheckedForNullOkay(),start of procedure mayReturnNullObject,Taking false branch,return from a call to T::mayReturnNullObject,Taking true branch,start of procedure mayReturnNullObject,Taking false branch,return from a call to T::mayReturnNullObject]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, methodNotAlwaysCheckedForNullBad, 1, NULL_TEST_AFTER_DEREFERENCE, no_bucket, WARNING, [start of procedure methodNotAlwaysCheckedForNullBad(),Taking false branch,start of procedure mayReturnNullObject,Taking false branch,return from a call to T::mayReturnNullObject,Taking false branch]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, methodNotAlwaysCheckedForNullBad, 2, NULLSAFE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [dereferencing the return of T::mayReturnNullObject,definition of mayReturnNullObject]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, methodNotAlwaysCheckedForNullBad, 2, NULL_DEREFERENCE, B5, ERROR, [start of procedure methodNotAlwaysCheckedForNullBad(),Taking false branch,start of procedure mayReturnNullObject,Taking false branch,return from a call to T::mayReturnNullObject,Taking true branch,start of procedure mayReturnNullObject,Taking false branch,return from a call to T::mayReturnNullObject]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, methodTestedForNullOkay, 1, NULL_TEST_AFTER_DEREFERENCE, no_bucket, WARNING, [start of procedure methodTestedForNullOkay(),start of procedure mayReturnNullObject,Taking false branch,return from a call to T::mayReturnNullObject,Taking false branch]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, methodTestedForNullOkay, 2, NULL_DEREFERENCE, B5, ERROR, [start of procedure methodTestedForNullOkay(),start of procedure mayReturnNullObject,Taking false branch,return from a call to T::mayReturnNullObject,Taking true branch,start of procedure mayReturnNullObject,Taking false branch,return from a call to T::mayReturnNullObject]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, nullableAssignmentInOneBranchBad, 7, NULLSAFE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [dereference of p,assignment of the nullable value,definition of mayReturnNullObject]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, nullableAssignmentInOneBranchBad, 7, NULL_DEREFERENCE, B2, ERROR, [start of procedure nullableAssignmentInOneBranchBad(),Taking true branch,start of procedure mayReturnNullObject,Taking true branch,return from a call to T::mayReturnNullObject]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, onlyReportOnceBad, 1, NULLSAFE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [dereferencing the return of T::mayReturnNullObject,definition of mayReturnNullObject]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, onlyReportOnceBad, 1, NULL_DEREFERENCE, B5, ERROR, [start of procedure onlyReportOnceBad(),start of procedure mayReturnNullObject,Taking false branch,return from a call to T::mayReturnNullObject]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, onlyReportOnceBad, 3, NULL_DEREFERENCE, B5, ERROR, [start of procedure onlyReportOnceBad(),start of procedure mayReturnNullObject,Taking false branch,return from a call to T::mayReturnNullObject,start of procedure doSomething,return from a call to T::doSomething,start of procedure mayReturnNullObject,Taking false branch,return from a call to T::mayReturnNullObject]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, pointerTestBad, 2, NULL_TEST_AFTER_DEREFERENCE, no_bucket, WARNING, [start of procedure pointerTestBad(),start of procedure mayReturnNullObject,Taking false branch,return from a call to T::mayReturnNullObject,Taking false branch]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, pointerTestBad, 5, NULLSAFE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [dereference of p,assignment of the nullable value,definition of mayReturnNullObject]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, pointerTestBad, 5, NULL_DEREFERENCE, B2, ERROR, [start of procedure pointerTestBad(),start of procedure mayReturnNullObject,Taking true branch,return from a call to T::mayReturnNullObject,Taking false branch]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, pointerTestOkay, 2, NULL_TEST_AFTER_DEREFERENCE, no_bucket, WARNING, [start of procedure pointerTestOkay(),start of procedure mayReturnNullObject,Taking false branch,return from a call to T::mayReturnNullObject,Taking false branch]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, reassigningNullablePointerOkay, 1, DEAD_STORE, no_bucket, ERROR, [Write of unused value]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, reassigningNullablePointerToNullOkay, 1, DEAD_STORE, no_bucket, ERROR, [Write of unused value]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, reportsViolationInNotNullElseBranchBad, 1, NULL_TEST_AFTER_DEREFERENCE, no_bucket, WARNING, [start of procedure reportsViolationInNotNullElseBranchBad(),start of procedure mayReturnNullObject,Taking false branch,return from a call to T::mayReturnNullObject,Taking false branch]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, reportsViolationInNotNullElseBranchBad, 3, NULLSAFE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [dereferencing the return of T::mayReturnNullObject,definition of mayReturnNullObject]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, reportsViolationInNotNullElseBranchBad, 3, NULL_DEREFERENCE, B5, ERROR, [start of procedure reportsViolationInNotNullElseBranchBad(),start of procedure mayReturnNullObject,Taking true branch,return from a call to T::mayReturnNullObject,Taking false branch,start of procedure mayReturnNullObject,Taking false branch,return from a call to T::mayReturnNullObject]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, reportsViolationInNullBranchBad, 1, NULL_TEST_AFTER_DEREFERENCE, no_bucket, WARNING, [start of procedure reportsViolationInNullBranchBad(),start of procedure mayReturnNullObject,Taking false branch,return from a call to T::mayReturnNullObject,Taking false branch]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, reportsViolationInNullBranchBad, 2, NULLSAFE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [dereferencing the return of T::mayReturnNullObject,definition of mayReturnNullObject]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, reportsViolationInNullBranchBad, 2, NULL_DEREFERENCE, B5, ERROR, [start of procedure reportsViolationInNullBranchBad(),start of procedure mayReturnNullObject,Taking true branch,return from a call to T::mayReturnNullObject,Taking true branch,start of procedure mayReturnNullObject,Taking false branch,return from a call to T::mayReturnNullObject]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, reportsViolationOutsideOfNullCheckBad, 1, NULL_TEST_AFTER_DEREFERENCE, no_bucket, WARNING, [start of procedure reportsViolationOutsideOfNullCheckBad(),start of procedure mayReturnNullObject,Taking false branch,return from a call to T::mayReturnNullObject,Taking false branch]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, reportsViolationOutsideOfNullCheckBad, 2, NULL_DEREFERENCE, B5, ERROR, [start of procedure reportsViolationOutsideOfNullCheckBad(),start of procedure mayReturnNullObject,Taking false branch,return from a call to T::mayReturnNullObject,Taking true branch,start of procedure mayReturnNullObject,Taking false branch,return from a call to T::mayReturnNullObject]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, reportsViolationOutsideOfNullCheckBad, 4, NULLSAFE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [dereferencing the return of T::mayReturnNullObject,definition of mayReturnNullObject]
|
|
||||||
codetoanalyze/cpp/nullable/method.cpp, reportsViolationOutsideOfNullCheckBad, 4, NULL_DEREFERENCE, B5, ERROR, [start of procedure reportsViolationOutsideOfNullCheckBad(),start of procedure mayReturnNullObject,Taking true branch,return from a call to T::mayReturnNullObject,Taking false branch,start of procedure mayReturnNullObject,Taking false branch,return from a call to T::mayReturnNullObject]
|
|
@ -1,170 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
bool star();
|
|
||||||
|
|
||||||
class T {
|
|
||||||
public:
|
|
||||||
int x;
|
|
||||||
T* field;
|
|
||||||
|
|
||||||
public:
|
|
||||||
int* _Nullable mayReturnNullPointer() {
|
|
||||||
if (star()) {
|
|
||||||
return nullptr;
|
|
||||||
} else {
|
|
||||||
return new int;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
T* _Nullable mayReturnNullObject() {
|
|
||||||
if (star()) {
|
|
||||||
return nullptr;
|
|
||||||
} else {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
T* doesNotReturnNullObject() { return new T(); }
|
|
||||||
|
|
||||||
public:
|
|
||||||
void doSomething() {}
|
|
||||||
};
|
|
||||||
|
|
||||||
void assignNullableValueBad(T* t) {
|
|
||||||
int* p = t->mayReturnNullPointer();
|
|
||||||
*p = 42;
|
|
||||||
}
|
|
||||||
|
|
||||||
void reassigningNullablePointerOkay(T* t) {
|
|
||||||
int* p = t->mayReturnNullPointer();
|
|
||||||
p = new int; // does not report here
|
|
||||||
*p = 42; // does not report here
|
|
||||||
}
|
|
||||||
|
|
||||||
void reassigningNullablePointerToNullOkay(T* t) {
|
|
||||||
int* p = t->mayReturnNullPointer();
|
|
||||||
p = nullptr; // does not report here
|
|
||||||
}
|
|
||||||
|
|
||||||
void callMethodOnNullableObjectBad(T* t) {
|
|
||||||
t->mayReturnNullObject()->doSomething();
|
|
||||||
}
|
|
||||||
|
|
||||||
void callMethodOnNullableObjectOkay(T* t) {
|
|
||||||
T* p = t->mayReturnNullObject();
|
|
||||||
if (p != nullptr) {
|
|
||||||
p->doSomething();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void dereferenceFieldOfNullableObjectBad(T* t) {
|
|
||||||
T* p = t->mayReturnNullObject();
|
|
||||||
p->x = 42;
|
|
||||||
}
|
|
||||||
|
|
||||||
void methodCallOnFieldOfNullableObjectBad(T* t) {
|
|
||||||
T* p = t->mayReturnNullObject();
|
|
||||||
p->field->doSomething();
|
|
||||||
}
|
|
||||||
|
|
||||||
void avoidDoubleReportingBad(T* t) {
|
|
||||||
T* p = t->mayReturnNullObject();
|
|
||||||
p->doSomething(); // reports here
|
|
||||||
p->doSomething(); // does not report here
|
|
||||||
}
|
|
||||||
|
|
||||||
void nullableAssignmentInOneBranchBad(T* t) {
|
|
||||||
T* p;
|
|
||||||
if (star()) {
|
|
||||||
p = t->mayReturnNullObject();
|
|
||||||
} else {
|
|
||||||
p = t->doesNotReturnNullObject();
|
|
||||||
}
|
|
||||||
p->doSomething(); // reports here
|
|
||||||
}
|
|
||||||
|
|
||||||
void methodCheckedForNullOkay(T* t) {
|
|
||||||
if (t->mayReturnNullObject() != nullptr) {
|
|
||||||
t->mayReturnNullObject()->doSomething(); // does not report here
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void methodCheckedForNullAndReturnOkay(T* t) {
|
|
||||||
if (t->mayReturnNullObject() == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
t->mayReturnNullObject()->doSomething(); // does not report here
|
|
||||||
}
|
|
||||||
|
|
||||||
void reportsViolationOutsideOfNullCheckBad(T* t) {
|
|
||||||
if (t->mayReturnNullObject() != nullptr) {
|
|
||||||
t->mayReturnNullObject()->doSomething(); // does not report here
|
|
||||||
}
|
|
||||||
t->mayReturnNullObject()->doSomething(); // reports here
|
|
||||||
}
|
|
||||||
|
|
||||||
void reportsViolationInNullBranchBad(T* t) {
|
|
||||||
if (t->mayReturnNullObject() == nullptr) {
|
|
||||||
t->mayReturnNullObject()->doSomething(); // reports here
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void reportsViolationInNotNullElseBranchBad(T* t) {
|
|
||||||
if (t->mayReturnNullObject() != nullptr) {
|
|
||||||
} else {
|
|
||||||
t->mayReturnNullObject()->doSomething(); // reports here
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void methodAlwaysCheckedForNullOkay(T* t) {
|
|
||||||
if (star() && t->mayReturnNullObject() != nullptr) {
|
|
||||||
t->mayReturnNullObject()->doSomething(); // does not report here
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void methodNotAlwaysCheckedForNullBad(T* t) {
|
|
||||||
if (star() || t->mayReturnNullObject() != nullptr) {
|
|
||||||
t->mayReturnNullObject()->doSomething(); // reports here
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void onlyReportOnceBad(T* t) {
|
|
||||||
t->mayReturnNullObject()->doSomething(); // reports here
|
|
||||||
// ...
|
|
||||||
t->mayReturnNullObject()->doSomething(); // does not report here
|
|
||||||
}
|
|
||||||
|
|
||||||
void dereferenceOfAliasesCheckedForNullOkay(T* t) {
|
|
||||||
T* s = t->mayReturnNullObject();
|
|
||||||
T* r = s;
|
|
||||||
if (r != nullptr) {
|
|
||||||
s->doSomething();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void pointerTestOkay(T* t) {
|
|
||||||
T* p = t->mayReturnNullObject();
|
|
||||||
if (p) {
|
|
||||||
p->doSomething();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void pointerTestBad(T* t) {
|
|
||||||
T* p = t->mayReturnNullObject();
|
|
||||||
if (p) {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
p->doSomething();
|
|
||||||
}
|
|
||||||
|
|
||||||
void methodTestedForNullOkay(T* t) {
|
|
||||||
if (t->mayReturnNullObject()) {
|
|
||||||
t->mayReturnNullObject()->doSomething();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,290 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
#import "Library.h"
|
|
||||||
|
|
||||||
int* __nullable returnsNull();
|
|
||||||
|
|
||||||
typedef struct s_ {
|
|
||||||
int x;
|
|
||||||
} S;
|
|
||||||
|
|
||||||
@interface T : NSObject
|
|
||||||
- (NSObject* _Nullable)nullableMethod;
|
|
||||||
- (T* _Nullable)nullableT;
|
|
||||||
@property(nonatomic) S structProperty;
|
|
||||||
@property(nonatomic) S* pointerProperty;
|
|
||||||
@property(nonatomic, nullable) NSObject* nullableProperty;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation T {
|
|
||||||
int* unnanotatedField;
|
|
||||||
int* __nullable nullableField;
|
|
||||||
int* nonnullField;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)assignNullableFieldToNullOkay {
|
|
||||||
nullableField = nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)assignUnnanotatedFieldToNullBad {
|
|
||||||
unnanotatedField = nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)assignNonnullFieldToNullBad {
|
|
||||||
nonnullField = nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)testNullableFieldForNullOkay {
|
|
||||||
if (nullableField == nil) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)testUnnanotatedFieldForNullBad {
|
|
||||||
if (unnanotatedField == nil) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (int)DeadStoreFP_testUnnanotatedFieldInClosureBad {
|
|
||||||
int (^testField)(int defaultValue);
|
|
||||||
testField = ^(int defaultValue) {
|
|
||||||
if (unnanotatedField != nil) {
|
|
||||||
return *unnanotatedField;
|
|
||||||
} else {
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return testField(42);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)testNonnullFieldForNullBad {
|
|
||||||
if (nonnullField == nil) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)dereferenceUnnanotatedFieldOkay {
|
|
||||||
*unnanotatedField = 42;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)dereferenceNonnullFieldOkay {
|
|
||||||
*nonnullField = 42;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)dereferenceNullableFieldBad {
|
|
||||||
*nullableField = 42;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)dereferenceUnnanotatedFieldAfterTestForNullBad {
|
|
||||||
if (unnanotatedField == nil) {
|
|
||||||
*unnanotatedField = 42;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)FP_dereferenceNonnullFieldAfterTestForNullOkay {
|
|
||||||
if (nonnullField == nil) {
|
|
||||||
*nonnullField = 42;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)dereferenceNullableFunctionBad {
|
|
||||||
int* p = returnsNull();
|
|
||||||
*p = 42;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)dereferenceNullableFunction1Ok {
|
|
||||||
int* p = returnsNull();
|
|
||||||
if (p) {
|
|
||||||
*p = 42;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)dereferenceNullableFunction2Ok {
|
|
||||||
int* p = returnsNull();
|
|
||||||
if (p != nil) {
|
|
||||||
*p = 42;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSObject* _Nullable)nullableMethod {
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString*)dereferenceNullableMethodOkay {
|
|
||||||
NSObject* nullableObject = [self nullableMethod];
|
|
||||||
return [nullableObject description]; // does not report here
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)reassigningNullableObjectOkay {
|
|
||||||
NSObject* nullableObject = [self nullableMethod];
|
|
||||||
nullableObject = nil; // does not report here
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSArray*)nullableObjectInNSArrayBad {
|
|
||||||
NSObject* nullableObject = [self nullableMethod];
|
|
||||||
NSArray* array = @[ nullableObject ]; // reports here
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSArray*)nullablePropertyInNSArrayBad {
|
|
||||||
return @[ self.nullableMethod ]; // reports here
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSArray*)nullableMethodCheckedForNullAndReturnOkay {
|
|
||||||
NSObject* nullableObject = [self nullableMethod];
|
|
||||||
NSArray* array;
|
|
||||||
if (nullableObject == nil) {
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
array = @[ nullableObject ]; // does not report here
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSArray*)secondElementNullableObjectInNSArrayBad {
|
|
||||||
NSObject* allocatedObject = [NSObject alloc];
|
|
||||||
NSObject* nullableObject = [self nullableMethod];
|
|
||||||
NSArray* array = @[ allocatedObject, nullableObject ]; // reports here
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSArray*)nullableObjectInNSArrayOkay {
|
|
||||||
NSObject* nullableObject = [self nullableMethod];
|
|
||||||
NSArray* array;
|
|
||||||
if (nullableObject) {
|
|
||||||
array = @[ nullableObject ]; // reports here
|
|
||||||
} else {
|
|
||||||
array = @[ @"String" ];
|
|
||||||
}
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSArray*)URLWithStringOkay {
|
|
||||||
NSURL* url = [NSURL URLWithString:@"some/url/string"];
|
|
||||||
NSArray* array = @[ url ]; // reports here
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSDictionary*)nullableValueInNSDictionaryBad {
|
|
||||||
NSObject* nullableValue = [self nullableMethod];
|
|
||||||
NSMutableDictionary* dict = [NSMutableDictionary
|
|
||||||
dictionaryWithObjectsAndKeys:@"key", nullableValue, nil]; // reports here
|
|
||||||
return dict;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSDictionary*)nullableKeyInNSDictionaryBad {
|
|
||||||
NSObject* nullableKey = [self nullableMethod];
|
|
||||||
NSMutableDictionary* dict = [NSMutableDictionary
|
|
||||||
dictionaryWithObjectsAndKeys:nullableKey, @"value", nil]; // reports here
|
|
||||||
return dict;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSDictionary*)nullableKeyInNSDictionaryInitBad {
|
|
||||||
NSObject* nullableKey = [self nullableMethod];
|
|
||||||
NSDictionary* dict = [[NSDictionary alloc]
|
|
||||||
initWithObjectsAndKeys:nullableKey, @"value", nil]; // reports here
|
|
||||||
return dict;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSDictionary*)nullableValueInNSDictionaryInitBad {
|
|
||||||
NSObject* nullableValue = [self nullableMethod];
|
|
||||||
NSDictionary* dict = [[NSDictionary alloc]
|
|
||||||
initWithObjectsAndKeys:@"key", nullableValue, nil]; // reports here
|
|
||||||
return dict;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSDictionary*)nullableKeyInNSDictionaryInitLiteralBad {
|
|
||||||
NSObject* nullableKey = [self nullableMethod];
|
|
||||||
NSDictionary* dict = @{nullableKey : @"value"}; // reports here
|
|
||||||
return dict;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSDictionary*)nullableValueInNSDictionaryInitLiteralBad {
|
|
||||||
NSObject* nullableValue = [self nullableMethod];
|
|
||||||
NSDictionary* dict = @{@"key" : nullableValue}; // reports here
|
|
||||||
return dict;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSDictionary*)indirectNullableKeyInNSDictionaryBad {
|
|
||||||
NSObject* nullableKey = [self nullableMethod];
|
|
||||||
NSString* nullableKeyString = [nullableKey description];
|
|
||||||
NSDictionary* dict = [[NSDictionary alloc]
|
|
||||||
initWithObjectsAndKeys:nullableKeyString, @"value", nil]; // reports here
|
|
||||||
return dict;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSArray*)createArrayByAddingNilBad {
|
|
||||||
NSArray* array = @[ [NSObject alloc] ];
|
|
||||||
return [array arrayByAddingObject:[self nullableMethod]];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSDictionary*)setNullableObjectInDictionaryBad {
|
|
||||||
NSMutableDictionary* mutableDict = [NSMutableDictionary dictionary];
|
|
||||||
[mutableDict setObject:[self nullableMethod] forKey:@"key"]; // reports here
|
|
||||||
return mutableDict;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSArray*)addNullableObjectInMutableArrayBad {
|
|
||||||
NSMutableArray* mutableArray = [[NSMutableArray alloc] init];
|
|
||||||
[mutableArray addObject:[self nullableMethod]]; // reports here
|
|
||||||
return mutableArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSArray*)insertNullableObjectInMutableArrayBad {
|
|
||||||
NSMutableArray* mutableArray = [[NSMutableArray alloc] init];
|
|
||||||
[mutableArray insertObject:[self nullableMethod] atIndex:0]; // reports here
|
|
||||||
return mutableArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSArray*)propagateNullabilityOnMethodCallBad {
|
|
||||||
NSObject* nullableObject = [self nullableMethod];
|
|
||||||
NSString* nullableString =
|
|
||||||
[nullableObject description]; // returns nil if nullableObject is nil
|
|
||||||
return @[ nullableString ]; // reports here
|
|
||||||
}
|
|
||||||
|
|
||||||
- (S*)shouldPropagateNullabilityOnPointerTypeBad {
|
|
||||||
T* nullableT = [self nullableT];
|
|
||||||
S* s = nullableT.pointerProperty; // returns nil when nullableT is nil
|
|
||||||
s->x = 42; // reports here
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (S)shouldNotPropagateNullabilityOnNonPointerTypeGood {
|
|
||||||
T* nullableT = [self nullableT];
|
|
||||||
S s =
|
|
||||||
nullableT.structProperty; // returns an empty struct when nullabeT is nil
|
|
||||||
s.x = 42; // does not report here
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSArray*)pointerAssignmentWithSubtypeOkay:(NSString*)string {
|
|
||||||
NSObject* nullableObject = [self nullableMethod];
|
|
||||||
nullableObject = string;
|
|
||||||
NSArray* array = @[ nullableObject ]; // does not report here
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSArray*)dereferenceLibraryMethodOk:(L*)object {
|
|
||||||
NSObject* nullableObject = [object libraryMethod];
|
|
||||||
NSArray* array = @[ nullableObject ]; // does not report here
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSArray*)dereferenceNullableLibraryMethodBad:(L*)object {
|
|
||||||
NSObject* nullableObject = [object nullableLibraryMethod];
|
|
||||||
NSArray* array = @[ nullableObject ]; // reports here
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@protocol P
|
|
||||||
- (NSObject* _Nullable)nullableMethod;
|
|
||||||
@end
|
|
||||||
|
|
||||||
NSDictionary* callNullableMethodFromProtocolBad(id<P> pObject) {
|
|
||||||
NSObject* nullableObject = [pObject nullableMethod];
|
|
||||||
return @{@"key" : nullableObject};
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
|
|
||||||
@interface L : NSObject
|
|
||||||
- (NSObject*)libraryMethod;
|
|
||||||
- (NSObject* _Nullable)nullableLibraryMethod;
|
|
||||||
@end
|
|
@ -1,17 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
TESTS_DIR = ../../..
|
|
||||||
|
|
||||||
CLANG_OPTIONS = -c $(OBJC_CLANG_OPTIONS)
|
|
||||||
INFER_OPTIONS = --debug-exceptions --no-default-checkers --nullsafe --project-root $(TESTS_DIR)
|
|
||||||
INFERPRINT_OPTIONS = --issues-tests
|
|
||||||
|
|
||||||
SOURCES = $(wildcard *.m)
|
|
||||||
|
|
||||||
include $(TESTS_DIR)/clang.make
|
|
||||||
include $(TESTS_DIR)/objc.make
|
|
||||||
|
|
||||||
infer-out/report.json: $(MAKEFILE_LIST)
|
|
@ -1,26 +0,0 @@
|
|||||||
codetoanalyze/objc/nullable/Examples.m, T::FP_dereferenceNonnullFieldAfterTestForNullOkay, 1, NULLSAFE_FIELD_NOT_NULLABLE, no_bucket, WARNING, [Field nonnullField is compared to null here]
|
|
||||||
codetoanalyze/objc/nullable/Examples.m, T::addNullableObjectInMutableArrayBad, 2, NULLSAFE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [dereferencing the return of T::nullableMethod,assignment of the nullable value,definition of nullableMethod]
|
|
||||||
codetoanalyze/objc/nullable/Examples.m, T::assignNonnullFieldToNullBad, 1, NULLSAFE_FIELD_NOT_NULLABLE, no_bucket, WARNING, [Field nonnullField is assigned null here]
|
|
||||||
codetoanalyze/objc/nullable/Examples.m, T::assignUnnanotatedFieldToNullBad, 1, NULLSAFE_FIELD_NOT_NULLABLE, no_bucket, WARNING, [Field unnanotatedField is assigned null here]
|
|
||||||
codetoanalyze/objc/nullable/Examples.m, T::createArrayByAddingNilBad, 2, NULLSAFE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [dereferencing the return of T::nullableMethod,assignment of the nullable value,definition of nullableMethod]
|
|
||||||
codetoanalyze/objc/nullable/Examples.m, T::dereferenceNullableFunctionBad, 2, NULLSAFE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [dereference of p,assignment of the nullable value,definition of returnsNull]
|
|
||||||
codetoanalyze/objc/nullable/Examples.m, T::dereferenceNullableLibraryMethodBad:, 2, NULLSAFE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [dereference of nullableObject,assignment of the nullable value,definition of nullableLibraryMethod]
|
|
||||||
codetoanalyze/objc/nullable/Examples.m, T::dereferenceUnnanotatedFieldAfterTestForNullBad, 1, NULLSAFE_FIELD_NOT_NULLABLE, no_bucket, WARNING, [Field unnanotatedField is compared to null here]
|
|
||||||
codetoanalyze/objc/nullable/Examples.m, T::indirectNullableKeyInNSDictionaryBad, 3, NULLSAFE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [dereference of nullableKeyString,assignment of the nullable value,definition of nullableMethod]
|
|
||||||
codetoanalyze/objc/nullable/Examples.m, T::insertNullableObjectInMutableArrayBad, 2, NULLSAFE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [dereferencing the return of T::nullableMethod,assignment of the nullable value,definition of nullableMethod]
|
|
||||||
codetoanalyze/objc/nullable/Examples.m, T::nullableKeyInNSDictionaryBad, 2, NULLSAFE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [dereference of nullableKey,assignment of the nullable value,definition of nullableMethod]
|
|
||||||
codetoanalyze/objc/nullable/Examples.m, T::nullableKeyInNSDictionaryInitBad, 2, NULLSAFE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [dereference of nullableKey,assignment of the nullable value,definition of nullableMethod]
|
|
||||||
codetoanalyze/objc/nullable/Examples.m, T::nullableKeyInNSDictionaryInitLiteralBad, 2, NULLSAFE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [dereference of nullableKey,assignment of the nullable value,definition of nullableMethod]
|
|
||||||
codetoanalyze/objc/nullable/Examples.m, T::nullableObjectInNSArrayBad, 2, NULLSAFE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [dereference of nullableObject,assignment of the nullable value,definition of nullableMethod]
|
|
||||||
codetoanalyze/objc/nullable/Examples.m, T::nullablePropertyInNSArrayBad, 1, NULLSAFE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [dereferencing the return of T::nullableMethod,assignment of the nullable value,definition of nullableMethod]
|
|
||||||
codetoanalyze/objc/nullable/Examples.m, T::nullableValueInNSDictionaryBad, 2, NULLSAFE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [dereference of nullableValue,assignment of the nullable value,definition of nullableMethod]
|
|
||||||
codetoanalyze/objc/nullable/Examples.m, T::nullableValueInNSDictionaryInitBad, 2, NULLSAFE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [dereference of nullableValue,assignment of the nullable value,definition of nullableMethod]
|
|
||||||
codetoanalyze/objc/nullable/Examples.m, T::nullableValueInNSDictionaryInitLiteralBad, 2, NULLSAFE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [dereference of nullableValue,assignment of the nullable value,definition of nullableMethod]
|
|
||||||
codetoanalyze/objc/nullable/Examples.m, T::propagateNullabilityOnMethodCallBad, 4, NULLSAFE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [dereference of nullableString,assignment of the nullable value,definition of nullableMethod]
|
|
||||||
codetoanalyze/objc/nullable/Examples.m, T::secondElementNullableObjectInNSArrayBad, 3, NULLSAFE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [dereference of nullableObject,assignment of the nullable value,definition of nullableMethod]
|
|
||||||
codetoanalyze/objc/nullable/Examples.m, T::setNullableObjectInDictionaryBad, 2, NULLSAFE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [dereferencing the return of T::nullableMethod,assignment of the nullable value,definition of nullableMethod]
|
|
||||||
codetoanalyze/objc/nullable/Examples.m, T::shouldPropagateNullabilityOnPointerTypeBad, 3, NULLSAFE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [dereference of s,assignment of the nullable value,definition of nullableT]
|
|
||||||
codetoanalyze/objc/nullable/Examples.m, T::testNonnullFieldForNullBad, 1, NULLSAFE_FIELD_NOT_NULLABLE, no_bucket, WARNING, [Field nonnullField is compared to null here]
|
|
||||||
codetoanalyze/objc/nullable/Examples.m, T::testUnnanotatedFieldForNullBad, 1, NULLSAFE_FIELD_NOT_NULLABLE, no_bucket, WARNING, [Field unnanotatedField is compared to null here]
|
|
||||||
codetoanalyze/objc/nullable/Examples.m, callNullableMethodFromProtocolBad, 2, NULLSAFE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [dereference of nullableObject,assignment of the nullable value,definition of nullableMethod]
|
|
||||||
codetoanalyze/objc/nullable/Examples.m, objc_blockT::DeadStoreFP_testUnnanotatedFieldInClosureBad_1, 1, NULLSAFE_FIELD_NOT_NULLABLE, no_bucket, WARNING, [Field unnanotatedField is compared to null here]
|
|
@ -1,27 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
#import <Foundation/NSObject.h>
|
|
||||||
#import <string>
|
|
||||||
|
|
||||||
@interface A : NSObject {
|
|
||||||
}
|
|
||||||
- (char _Nullable*)toString;
|
|
||||||
@end
|
|
||||||
|
|
||||||
std::string stdStringOK(A* a) {
|
|
||||||
const char* s1 = [a toString];
|
|
||||||
if (s1) {
|
|
||||||
std::string s2 = std::string(s1);
|
|
||||||
return s2;
|
|
||||||
} else
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string stdStringBad(A* a) {
|
|
||||||
std::string s2 = std::string([a toString]);
|
|
||||||
return s2;
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
TESTS_DIR = ../../..
|
|
||||||
|
|
||||||
CLANG_OPTIONS = -c $(OBJCPP_CLANG_OPTIONS)
|
|
||||||
INFER_OPTIONS = --debug-exceptions --no-default-checkers --nullsafe --project-root $(TESTS_DIR)
|
|
||||||
INFERPRINT_OPTIONS = --issues-tests
|
|
||||||
|
|
||||||
SOURCES = $(wildcard *.mm)
|
|
||||||
|
|
||||||
include $(TESTS_DIR)/clang.make
|
|
||||||
include $(TESTS_DIR)/objc.make
|
|
||||||
|
|
||||||
infer-out/report.json: $(MAKEFILE_LIST)
|
|
@ -1 +0,0 @@
|
|||||||
codetoanalyze/objcpp/nullable/Examples.mm, stdStringBad, 1, NULLSAFE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [dereferencing the return of A::toString,assignment of the nullable value,definition of toString]
|
|
Loading…
Reference in new issue