You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
569 lines
21 KiB
569 lines
21 KiB
(*
|
|
* 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 DomainData = struct
|
|
type self_pointer_kind =
|
|
| CAPTURED_STRONG_SELF
|
|
| CHECKED_STRONG_SELF
|
|
| SELF
|
|
| UNCHECKED_STRONG_SELF
|
|
| WEAK_SELF
|
|
[@@deriving compare]
|
|
|
|
let is_unchecked_strong_self kind = match kind with UNCHECKED_STRONG_SELF -> true | _ -> false
|
|
|
|
let pp_self_pointer_kind fmt kind =
|
|
let s =
|
|
match kind with
|
|
| CAPTURED_STRONG_SELF ->
|
|
"CAPTURED_STRONG_SELF"
|
|
| CHECKED_STRONG_SELF ->
|
|
"CHECKED_STRONG_SELF"
|
|
| SELF ->
|
|
"SELF"
|
|
| UNCHECKED_STRONG_SELF ->
|
|
"UNCHECKED_STRONG_SELF"
|
|
| WEAK_SELF ->
|
|
"WEAK_SELF"
|
|
in
|
|
F.fprintf fmt "%s" s
|
|
|
|
|
|
let kind_join kind1 kind2 =
|
|
if phys_equal kind1 kind2 then kind1
|
|
else
|
|
match (kind1, kind2) with
|
|
| CHECKED_STRONG_SELF, _ | _, CHECKED_STRONG_SELF ->
|
|
CHECKED_STRONG_SELF
|
|
| _ ->
|
|
Logging.die InternalError
|
|
"The only unequal kinds that can be joined in this domain are CHECKED_STRONG_SELF and \
|
|
UNCHECKED_STRONG_SELF, but found %a, %a"
|
|
pp_self_pointer_kind kind1 pp_self_pointer_kind kind2
|
|
|
|
|
|
let kind_leq kind1 kind2 =
|
|
if phys_equal kind1 kind2 then true
|
|
else
|
|
match (kind1, kind2) with
|
|
| CHECKED_STRONG_SELF, UNCHECKED_STRONG_SELF ->
|
|
false
|
|
| UNCHECKED_STRONG_SELF, CHECKED_STRONG_SELF ->
|
|
true
|
|
| _ ->
|
|
Logging.die InternalError
|
|
"The only unequal kinds that can be compared in this domain are CHECKED_STRONG_SELF \
|
|
and UNCHECKED_STRONG_SELF, but found %a, %a"
|
|
pp_self_pointer_kind kind1 pp_self_pointer_kind kind2
|
|
|
|
|
|
type t = {pvar: Pvar.t; typ: Typ.t [@compare.ignore]; loc: Location.t; kind: self_pointer_kind}
|
|
[@@deriving compare]
|
|
|
|
let pp fmt {pvar; typ; loc; kind} =
|
|
F.fprintf fmt "%a:%a, at %a (%a)" (Pvar.pp Pp.text) pvar (Typ.pp Pp.text) typ Location.pp loc
|
|
pp_self_pointer_kind kind
|
|
|
|
|
|
let join elem1 elem2 =
|
|
assert (Pvar.equal elem1.pvar elem2.pvar) ;
|
|
{elem1 with kind= kind_join elem1.kind elem2.kind}
|
|
|
|
|
|
let widen ~prev ~next ~num_iters:_ = join prev next
|
|
|
|
let leq ~lhs ~rhs =
|
|
assert (Pvar.equal lhs.pvar rhs.pvar) ;
|
|
kind_leq lhs.kind rhs.kind
|
|
end
|
|
|
|
module PPPVar = struct
|
|
type t = Pvar.t [@@deriving compare]
|
|
|
|
let pp = Pvar.pp Pp.text
|
|
end
|
|
|
|
module CheckedForNull = struct
|
|
type t = {checked: bool; loc: Location.t; reported: bool}
|
|
|
|
let pp fmt {checked; reported} =
|
|
let s = match checked with true -> "Checked" | false -> "NotChecked" in
|
|
let s' = match reported with true -> "Reported" | false -> "NotReported" in
|
|
F.fprintf fmt "%s, %s" s s'
|
|
|
|
|
|
let join {checked= elem1; loc= loc1; reported= reported1}
|
|
{checked= elem2; loc= loc2; reported= reported2} =
|
|
let loc, reported =
|
|
match (elem1, elem2) with true, false -> (loc2, reported2) | _ -> (loc1, reported1)
|
|
in
|
|
{checked= elem1 && elem2; loc; reported}
|
|
|
|
|
|
let widen ~prev ~next ~num_iters:_ = join prev next
|
|
|
|
let leq ~lhs:{checked= lhs} ~rhs:{checked= rhs} = AbstractDomain.BooleanAnd.leq ~lhs ~rhs
|
|
end
|
|
|
|
module StrongEqualToWeakCapturedVars = AbstractDomain.Map (PPPVar) (CheckedForNull)
|
|
module Vars = AbstractDomain.Map (Ident) (DomainData)
|
|
|
|
module Domain = struct
|
|
type t = {vars: Vars.t; strongVars: StrongEqualToWeakCapturedVars.t}
|
|
|
|
let pp fmt {vars; strongVars} =
|
|
F.fprintf fmt "%a@.%a" Vars.pp vars StrongEqualToWeakCapturedVars.pp strongVars
|
|
|
|
|
|
let join lhs rhs =
|
|
{ vars= Vars.join lhs.vars rhs.vars
|
|
; strongVars= StrongEqualToWeakCapturedVars.join lhs.strongVars rhs.strongVars }
|
|
|
|
|
|
let widen ~prev ~next ~num_iters:_ = join prev next
|
|
|
|
let leq ~lhs ~rhs =
|
|
Vars.leq ~lhs:lhs.vars ~rhs:rhs.vars
|
|
&& StrongEqualToWeakCapturedVars.leq ~lhs:lhs.strongVars ~rhs:rhs.strongVars
|
|
end
|
|
|
|
type report_issues_result =
|
|
{ reported_captured_strong_self: Pvar.Set.t
|
|
; reported_weak_self_in_noescape_block: Pvar.Set.t
|
|
; selfList: DomainData.t list
|
|
; weakSelfList: DomainData.t list }
|
|
|
|
module TransferFunctions = struct
|
|
module Domain = Domain
|
|
module CFG = ProcCfg.Normal
|
|
|
|
type analysis_data = IntraproceduralAnalysis.t
|
|
|
|
let pp_session_name _node fmt = F.pp_print_string fmt "SelfCapturedInBlock"
|
|
|
|
let is_captured_self attributes pvar =
|
|
let pvar_name = Pvar.get_name pvar in
|
|
Pvar.is_self pvar
|
|
&& List.exists
|
|
~f:(fun {CapturedVar.name= captured; typ} ->
|
|
Mangled.equal captured pvar_name && Typ.is_strong_pointer typ )
|
|
attributes.ProcAttributes.captured
|
|
|
|
|
|
(* The variable is captured in the block, contains self in the name, is not self, and it's strong. *)
|
|
let is_captured_strong_self attributes pvar =
|
|
(not (Pvar.is_self pvar))
|
|
&& List.exists
|
|
~f:(fun {CapturedVar.name= captured; typ} ->
|
|
Typ.is_strong_pointer typ
|
|
&& Mangled.equal captured (Pvar.get_name pvar)
|
|
&& String.is_suffix ~suffix:"self" (String.lowercase (Mangled.to_string captured)) )
|
|
attributes.ProcAttributes.captured
|
|
|
|
|
|
let is_captured_weak_self attributes pvar =
|
|
List.exists
|
|
~f:(fun {CapturedVar.name= captured; typ} ->
|
|
Mangled.equal captured (Pvar.get_name pvar)
|
|
&& String.is_substring ~substring:"self" (String.lowercase (Mangled.to_string captured))
|
|
&& Typ.is_weak_pointer typ )
|
|
attributes.ProcAttributes.captured
|
|
|
|
|
|
let find_strong_var (domain : Domain.t) id =
|
|
match Vars.find_opt id domain.vars with
|
|
| Some elem when StrongEqualToWeakCapturedVars.mem elem.pvar domain.strongVars ->
|
|
Some (elem, elem.pvar, StrongEqualToWeakCapturedVars.find elem.pvar domain.strongVars)
|
|
| _ ->
|
|
None
|
|
|
|
|
|
let exec_null_check_id (astate : Domain.t) id =
|
|
match find_strong_var astate id with
|
|
| Some (elem, pvar, strongVarElem) ->
|
|
let strongVars =
|
|
StrongEqualToWeakCapturedVars.add pvar
|
|
{strongVarElem with checked= true}
|
|
astate.strongVars
|
|
in
|
|
let vars =
|
|
(* We may have added UNCHECKED_STRONG_SELF in the previous Load instr,
|
|
but this occurrence is not a bug, since we are checking right here! *)
|
|
Vars.add id {elem with kind= CHECKED_STRONG_SELF} astate.vars
|
|
in
|
|
{Domain.vars; strongVars}
|
|
| None ->
|
|
astate
|
|
|
|
|
|
let make_trace_unchecked_strongself (domain : Domain.t) =
|
|
let trace_elems_strongVars =
|
|
StrongEqualToWeakCapturedVars.fold
|
|
(fun pvar {loc} trace_elems ->
|
|
let trace_elem_desc = F.asprintf "%a assigned here" (Pvar.pp Pp.text) pvar in
|
|
let trace_elem = Errlog.make_trace_element 0 loc trace_elem_desc [] in
|
|
trace_elem :: trace_elems )
|
|
domain.strongVars []
|
|
in
|
|
let trace_elems_vars =
|
|
Vars.fold
|
|
(fun _ {pvar; loc; kind} trace_elems ->
|
|
match kind with
|
|
| UNCHECKED_STRONG_SELF ->
|
|
let trace_elem_desc =
|
|
F.asprintf "Using %a not checked for null" (Pvar.pp Pp.text) pvar
|
|
in
|
|
let trace_elem = Errlog.make_trace_element 0 loc trace_elem_desc [] in
|
|
trace_elem :: trace_elems
|
|
| _ ->
|
|
trace_elems )
|
|
domain.vars []
|
|
in
|
|
let trace_elems = List.append trace_elems_strongVars trace_elems_vars in
|
|
List.sort trace_elems ~compare:(fun {Errlog.lt_loc= loc1} {Errlog.lt_loc= loc2} ->
|
|
Location.compare loc1 loc2 )
|
|
|
|
|
|
let report_unchecked_strongself_issues proc_desc err_log (domain : Domain.t) var_use var =
|
|
match find_strong_var domain var with
|
|
| Some ({DomainData.pvar; loc; kind}, _, strongVarElem)
|
|
when DomainData.is_unchecked_strong_self kind && not strongVarElem.reported ->
|
|
let message =
|
|
F.asprintf
|
|
"The variable `%a`, equal to a weak pointer to `self`, is %s without a check for null \
|
|
at %a. This could cause a crash or unexpected behavior."
|
|
(Pvar.pp Pp.text) pvar var_use Location.pp loc
|
|
in
|
|
let ltr = make_trace_unchecked_strongself domain in
|
|
Reporting.log_issue proc_desc err_log ~ltr ~loc SelfInBlock
|
|
IssueType.strong_self_not_checked message ;
|
|
let strongVars =
|
|
StrongEqualToWeakCapturedVars.add pvar
|
|
{strongVarElem with reported= true}
|
|
domain.strongVars
|
|
in
|
|
{domain with strongVars}
|
|
| _ ->
|
|
domain
|
|
|
|
|
|
let report_unchecked_strongself_issues_on_exps proc_desc err_log (domain : Domain.t)
|
|
(instr : Sil.instr) =
|
|
let report_unchecked_strongself_issues_on_exp strongVars (exp : Exp.t) =
|
|
match exp with
|
|
| Lfield (Var var, _, _) ->
|
|
report_unchecked_strongself_issues proc_desc err_log domain "dereferenced" var
|
|
| _ ->
|
|
strongVars
|
|
in
|
|
let exps = Sil.exps_of_instr instr in
|
|
List.fold ~f:report_unchecked_strongself_issues_on_exp exps ~init:domain
|
|
|
|
|
|
(* The translation of closures includes a load instruction for the captured variable,
|
|
then we add that corresponding id to the closure. This doesn't correspond to an
|
|
actual "use" of the captured variable in the source program though, and causes false
|
|
positives. Here we remove the ids from the domain when that id is being added to a closure. *)
|
|
let remove_ids_in_closures_from_domain (domain : Domain.t) (instr : Sil.instr) =
|
|
let remove_id_in_closures_from_domain vars ((exp : Exp.t), _, _, _) =
|
|
match exp with Var id -> Vars.remove id vars | _ -> vars
|
|
in
|
|
let do_exp vars (exp : Exp.t) =
|
|
match exp with
|
|
| Closure {captured_vars} ->
|
|
List.fold ~init:vars ~f:remove_id_in_closures_from_domain captured_vars
|
|
| _ ->
|
|
vars
|
|
in
|
|
let exps = Sil.exps_of_instr instr in
|
|
let vars = List.fold ~init:domain.vars ~f:do_exp exps in
|
|
{domain with vars}
|
|
|
|
|
|
let is_objc_instance attributes_opt =
|
|
match attributes_opt with
|
|
| Some proc_attrs -> (
|
|
match proc_attrs.ProcAttributes.clang_method_kind with
|
|
| ClangMethodKind.OBJC_INSTANCE ->
|
|
true
|
|
| _ ->
|
|
false )
|
|
| None ->
|
|
false
|
|
|
|
|
|
let get_annotations attributes_opt =
|
|
match attributes_opt with
|
|
| Some proc_attrs ->
|
|
Some proc_attrs.ProcAttributes.method_annotation.params
|
|
| None ->
|
|
None
|
|
|
|
|
|
let report_unchecked_strongself_issues_on_args proc_desc err_log (domain : Domain.t) pname args =
|
|
let report_issue var =
|
|
report_unchecked_strongself_issues proc_desc err_log domain
|
|
(F.sprintf "passed to `%s`" (Procname.to_simplified_string pname))
|
|
var
|
|
in
|
|
let rec report_on_non_nullable_arg ?annotations domain args =
|
|
match (annotations, args) with
|
|
| Some (annot :: annot_rest), (arg, _) :: rest ->
|
|
let domain =
|
|
match arg with
|
|
| Exp.Var var when not (Annotations.ia_is_nullable annot) ->
|
|
report_issue var
|
|
| _ ->
|
|
domain
|
|
in
|
|
report_on_non_nullable_arg ~annotations:annot_rest domain rest
|
|
| None, (arg, _) :: rest ->
|
|
let domain = match arg with Exp.Var var -> report_issue var | _ -> domain in
|
|
report_on_non_nullable_arg domain rest
|
|
| _ ->
|
|
domain
|
|
in
|
|
let attributes_opt = AnalysisCallbacks.proc_resolve_attributes pname in
|
|
let annotations = get_annotations attributes_opt in
|
|
let args =
|
|
if is_objc_instance attributes_opt then match args with _ :: rest -> rest | [] -> []
|
|
else args
|
|
in
|
|
let annotations =
|
|
if is_objc_instance attributes_opt then
|
|
match annotations with Some (_ :: rest) -> Some rest | _ -> annotations
|
|
else annotations
|
|
in
|
|
report_on_non_nullable_arg ?annotations domain args
|
|
|
|
|
|
let exec_instr (astate : Domain.t) {IntraproceduralAnalysis.proc_desc; err_log} _cfg_node _
|
|
(instr : Sil.instr) =
|
|
let attributes = Procdesc.get_attributes proc_desc in
|
|
let astate = report_unchecked_strongself_issues_on_exps proc_desc err_log astate instr in
|
|
let astate = remove_ids_in_closures_from_domain astate instr in
|
|
match instr with
|
|
| Load {id; e= Lvar pvar; loc; typ} ->
|
|
let vars =
|
|
if is_captured_self attributes pvar then
|
|
Vars.add id {pvar; typ; loc; kind= SELF} astate.vars
|
|
else if is_captured_strong_self attributes pvar then
|
|
Vars.add id {pvar; typ; loc; kind= CAPTURED_STRONG_SELF} astate.vars
|
|
else if is_captured_weak_self attributes pvar then
|
|
Vars.add id {pvar; typ; loc; kind= WEAK_SELF} astate.vars
|
|
else
|
|
try
|
|
let isChecked = StrongEqualToWeakCapturedVars.find pvar astate.strongVars in
|
|
if not isChecked.checked then
|
|
Vars.add id {pvar; typ; loc; kind= UNCHECKED_STRONG_SELF} astate.vars
|
|
else astate.vars
|
|
with Caml.Not_found -> astate.vars
|
|
in
|
|
{astate with vars}
|
|
| Store {e1= Lvar pvar; e2= Var id; typ= pvar_typ; loc} ->
|
|
let strongVars =
|
|
try
|
|
let {DomainData.pvar= binding_for_id} = Vars.find id astate.vars in
|
|
if is_captured_weak_self attributes binding_for_id && Typ.is_strong_pointer pvar_typ
|
|
then
|
|
StrongEqualToWeakCapturedVars.add pvar
|
|
{checked= false; loc; reported= false}
|
|
astate.strongVars
|
|
else astate.strongVars
|
|
with Caml.Not_found -> astate.strongVars
|
|
in
|
|
{astate with strongVars}
|
|
| Prune (Var id, _, _, _) ->
|
|
exec_null_check_id astate id
|
|
(* If (strongSelf != nil) or equivalent else branch *)
|
|
| Prune (BinOp (Binop.Ne, Var id, e), _, _, _)
|
|
(* If (!(strongSelf == nil)) or equivalent else branch *)
|
|
| Prune (UnOp (LNot, BinOp (Binop.Eq, Var id, e), _), _, _, _) ->
|
|
if Exp.is_null_literal e then exec_null_check_id astate id else astate
|
|
| Call (_, Exp.Const (Const.Cfun callee_pn), args, _, _) ->
|
|
report_unchecked_strongself_issues_on_args proc_desc err_log astate callee_pn args
|
|
| _ ->
|
|
astate
|
|
end
|
|
|
|
let make_trace_use_self_weakself domain =
|
|
let trace_elems =
|
|
Vars.fold
|
|
(fun _ {pvar; loc; kind} trace_elems ->
|
|
match kind with
|
|
| SELF | WEAK_SELF ->
|
|
let trace_elem_desc = F.asprintf "Using %a" (Pvar.pp Pp.text) pvar in
|
|
let trace_elem = Errlog.make_trace_element 0 loc trace_elem_desc [] in
|
|
trace_elem :: trace_elems
|
|
| _ ->
|
|
trace_elems )
|
|
domain []
|
|
in
|
|
List.sort trace_elems ~compare:(fun {Errlog.lt_loc= loc1} {Errlog.lt_loc= loc2} ->
|
|
Location.compare loc1 loc2 )
|
|
|
|
|
|
let make_trace_captured_strong_self domain =
|
|
let trace_elems =
|
|
Vars.fold
|
|
(fun _ {pvar; loc; kind} trace_elems ->
|
|
match kind with
|
|
| CAPTURED_STRONG_SELF ->
|
|
let trace_elem_desc = F.asprintf "Using captured %a" (Pvar.pp Pp.text) pvar in
|
|
let trace_elem = Errlog.make_trace_element 0 loc trace_elem_desc [] in
|
|
trace_elem :: trace_elems
|
|
| _ ->
|
|
trace_elems )
|
|
domain []
|
|
in
|
|
List.sort trace_elems ~compare:(fun {Errlog.lt_loc= loc1} {Errlog.lt_loc= loc2} ->
|
|
Location.compare loc1 loc2 )
|
|
|
|
|
|
let report_mix_self_weakself_issues proc_desc err_log domain (weakSelf : DomainData.t)
|
|
(self : DomainData.t) =
|
|
let message =
|
|
F.asprintf
|
|
"This block uses both `%a` (%a) and `%a` (%a). This could lead to retain cycles or \
|
|
unexpected behavior."
|
|
(Pvar.pp Pp.text) weakSelf.pvar Location.pp weakSelf.loc (Pvar.pp Pp.text) self.pvar
|
|
Location.pp self.loc
|
|
in
|
|
let ltr = make_trace_use_self_weakself domain in
|
|
Reporting.log_issue proc_desc err_log ~ltr ~loc:self.loc SelfInBlock IssueType.mixed_self_weakself
|
|
message
|
|
|
|
|
|
let report_weakself_in_no_escape_block_issues proc_desc err_log domain (weakSelf : DomainData.t)
|
|
procname reported_weak_self_in_noescape_block =
|
|
if not (Pvar.Set.mem weakSelf.pvar reported_weak_self_in_noescape_block) then (
|
|
let reported_weak_self_in_noescape_block =
|
|
Pvar.Set.add weakSelf.pvar reported_weak_self_in_noescape_block
|
|
in
|
|
let message =
|
|
F.asprintf
|
|
"This block uses `%a` at %a. This is probably not needed since the block is passed to the \
|
|
method `%s` in a position annotated with NS_NOESCAPE. Use `self` instead."
|
|
(Pvar.pp Pp.text) weakSelf.pvar Location.pp weakSelf.loc
|
|
(Procname.to_simplified_string procname)
|
|
in
|
|
let ltr = make_trace_use_self_weakself domain in
|
|
Reporting.log_issue proc_desc err_log ~ltr ~loc:weakSelf.loc SelfInBlock
|
|
IssueType.weak_self_in_noescape_block message ;
|
|
reported_weak_self_in_noescape_block )
|
|
else reported_weak_self_in_noescape_block
|
|
|
|
|
|
let report_weakself_multiple_issue proc_desc err_log domain (weakSelf1 : DomainData.t)
|
|
(weakSelf2 : DomainData.t) =
|
|
let message =
|
|
F.asprintf
|
|
"This block uses the weak pointer `%a` more than once (%a) and (%a). This could lead to \
|
|
unexpected behavior. Even if `%a` is not nil in the first use, it could be nil in the \
|
|
following uses since the object that `%a` points to could be freed anytime; assign it to a \
|
|
strong variable first."
|
|
(Pvar.pp Pp.text) weakSelf1.pvar Location.pp weakSelf1.loc Location.pp weakSelf2.loc
|
|
(Pvar.pp Pp.text) weakSelf1.pvar (Pvar.pp Pp.text) weakSelf1.pvar
|
|
in
|
|
let ltr = make_trace_use_self_weakself domain in
|
|
Reporting.log_issue proc_desc err_log ~ltr ~loc:weakSelf1.loc SelfInBlock
|
|
IssueType.multiple_weakself message
|
|
|
|
|
|
let report_captured_strongself_issue proc_desc err_log domain (capturedStrongSelf : DomainData.t)
|
|
report_captured_strongself =
|
|
let attributes = Procdesc.get_attributes proc_desc in
|
|
if
|
|
Option.is_none attributes.ProcAttributes.passed_as_noescape_block_to
|
|
&& not (Pvar.Set.mem capturedStrongSelf.pvar report_captured_strongself)
|
|
then (
|
|
let report_captured_strongself =
|
|
Pvar.Set.add capturedStrongSelf.pvar report_captured_strongself
|
|
in
|
|
let message =
|
|
F.asprintf
|
|
"The variable `%a`, used at `%a`, is a strong pointer to `self` captured in this block. \
|
|
This could lead to retain cycles or unexpected behavior since to avoid retain cycles one \
|
|
usually uses a local strong pointer or a captured weak pointer instead."
|
|
(Pvar.pp Pp.text) capturedStrongSelf.pvar Location.pp capturedStrongSelf.loc
|
|
in
|
|
let ltr = make_trace_captured_strong_self domain in
|
|
Reporting.log_issue proc_desc err_log ~ltr ~loc:capturedStrongSelf.loc SelfInBlock
|
|
IssueType.captured_strong_self message ;
|
|
report_captured_strongself )
|
|
else report_captured_strongself
|
|
|
|
|
|
let report_issues proc_desc err_log domain =
|
|
let process_domain_item (result : report_issues_result) (_, (domain_data : DomainData.t)) =
|
|
match domain_data.kind with
|
|
| DomainData.CAPTURED_STRONG_SELF ->
|
|
let reported_captured_strong_self =
|
|
report_captured_strongself_issue proc_desc err_log domain domain_data
|
|
result.reported_captured_strong_self
|
|
in
|
|
{result with reported_captured_strong_self}
|
|
| DomainData.WEAK_SELF ->
|
|
let reported_weak_self_in_noescape_block =
|
|
let attributes = Procdesc.get_attributes proc_desc in
|
|
match attributes.ProcAttributes.passed_as_noescape_block_to with
|
|
| Some procname ->
|
|
report_weakself_in_no_escape_block_issues proc_desc err_log domain domain_data
|
|
procname result.reported_weak_self_in_noescape_block
|
|
| None ->
|
|
result.reported_weak_self_in_noescape_block
|
|
in
|
|
{ result with
|
|
reported_weak_self_in_noescape_block
|
|
; weakSelfList= domain_data :: result.weakSelfList }
|
|
| DomainData.SELF ->
|
|
{result with selfList= domain_data :: result.selfList}
|
|
| _ ->
|
|
result
|
|
in
|
|
let report_issues_result_empty =
|
|
{ reported_captured_strong_self= Pvar.Set.empty
|
|
; reported_weak_self_in_noescape_block= Pvar.Set.empty
|
|
; selfList= []
|
|
; weakSelfList= [] }
|
|
in
|
|
let domain_bindings =
|
|
Vars.bindings domain
|
|
|> List.sort ~compare:(fun (_, {DomainData.loc= loc1}) (_, {DomainData.loc= loc2}) ->
|
|
Location.compare loc1 loc2 )
|
|
in
|
|
let {weakSelfList; selfList} =
|
|
List.fold_left ~f:process_domain_item ~init:report_issues_result_empty domain_bindings
|
|
in
|
|
let weakSelfList = List.rev weakSelfList in
|
|
let selfList = List.rev selfList in
|
|
( match (weakSelfList, selfList) with
|
|
| weakSelf :: _, self :: _ ->
|
|
report_mix_self_weakself_issues proc_desc err_log domain weakSelf self
|
|
| _ ->
|
|
() ) ;
|
|
match weakSelfList with
|
|
| weakSelf1 :: weakSelf2 :: _ ->
|
|
report_weakself_multiple_issue proc_desc err_log domain weakSelf1 weakSelf2
|
|
| _ ->
|
|
()
|
|
|
|
|
|
module Analyzer = AbstractInterpreter.MakeWTO (TransferFunctions)
|
|
|
|
let checker ({IntraproceduralAnalysis.proc_desc; err_log} as analysis_data) =
|
|
let initial = {Domain.vars= Vars.empty; strongVars= StrongEqualToWeakCapturedVars.empty} in
|
|
let procname = Procdesc.get_proc_name proc_desc in
|
|
if Procname.is_objc_block procname then
|
|
match Analyzer.compute_post analysis_data ~initial proc_desc with
|
|
| Some domain ->
|
|
report_issues proc_desc err_log domain.vars
|
|
| None ->
|
|
()
|