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.
338 lines
12 KiB
338 lines
12 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 = SELF | UNCHECKED_STRONG_SELF | CHECKED_STRONG_SELF | WEAK_SELF
|
|
[@@deriving compare]
|
|
|
|
let is_self kind = match kind with SELF -> true | _ -> false
|
|
|
|
let is_weak_self kind = match kind with WEAK_SELF -> true | _ -> false
|
|
|
|
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
|
|
| 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}
|
|
|
|
let pp fmt {checked} =
|
|
let s = match checked with true -> "Checked" | false -> "NotChecked" in
|
|
F.fprintf fmt "%s" s
|
|
|
|
|
|
let join {checked= elem1; loc= loc1} {checked= elem2; loc= loc2} =
|
|
let loc = match (elem1, elem2) with true, false -> loc2 | false, _ | true, true -> loc1 in
|
|
{checked= AbstractDomain.BooleanAnd.join elem1 elem2; loc}
|
|
|
|
|
|
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
|
|
|
|
module TransferFunctions = struct
|
|
module Domain = Domain
|
|
module CFG = ProcCfg.Normal
|
|
|
|
type extras = unit
|
|
|
|
let pp_session_name _node fmt = F.pp_print_string fmt "SelfCapturedInBlock"
|
|
|
|
let is_captured_strong_self attributes pvar =
|
|
let pvar_name = Pvar.get_name pvar in
|
|
Pvar.is_self pvar
|
|
&& List.exists
|
|
~f:(fun (captured, typ) -> Mangled.equal captured pvar_name && Typ.is_strong_pointer typ)
|
|
attributes.ProcAttributes.captured
|
|
|
|
|
|
let is_captured_weak_self attributes pvar =
|
|
List.exists
|
|
~f:(fun (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 exec_null_check_id (astate : Domain.t) id =
|
|
try
|
|
let elem = Vars.find id astate.vars in
|
|
if StrongEqualToWeakCapturedVars.mem elem.pvar astate.strongVars then
|
|
let strongVarElem = StrongEqualToWeakCapturedVars.find elem.pvar astate.strongVars in
|
|
let strongVars =
|
|
StrongEqualToWeakCapturedVars.add elem.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}
|
|
else astate
|
|
with Not_found_s _ | Caml.Not_found -> astate
|
|
|
|
|
|
let exec_instr (astate : Domain.t) {ProcData.summary} _cfg_node (instr : Sil.instr) =
|
|
let attributes = Summary.get_attributes summary in
|
|
match instr with
|
|
| Load {id; e= Lvar pvar; loc; typ} ->
|
|
let vars =
|
|
if is_captured_strong_self attributes pvar then
|
|
Vars.add id {pvar; typ; loc; kind= 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 Not_found_s _ | 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} astate.strongVars
|
|
else astate.strongVars
|
|
with Not_found_s _ | Caml.Not_found -> astate.strongVars
|
|
in
|
|
{astate with strongVars}
|
|
| Prune (Var id, _, _, Sil.Ik_if) ->
|
|
exec_null_check_id astate id
|
|
(* If (strongSelf != nil) or equivalent else branch *)
|
|
| Prune (BinOp (Binop.Ne, Var id, e), _, _, Sil.Ik_if)
|
|
(* If (!(strongSelf == nil)) or equivalent else branch *)
|
|
| Prune (UnOp (LNot, BinOp (Binop.Eq, Var id, e), _), _, _, Sil.Ik_if) ->
|
|
if Exp.is_null_literal e then exec_null_check_id astate id else astate
|
|
| _ ->
|
|
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 report_mix_self_weakself_issues summary domain =
|
|
let weakSelf_opt =
|
|
Vars.filter (fun _ {kind} -> DomainData.is_weak_self kind) domain |> Vars.choose_opt
|
|
in
|
|
let self_opt = Vars.filter (fun _ {kind} -> DomainData.is_self kind) domain |> Vars.choose_opt in
|
|
match (weakSelf_opt, self_opt) with
|
|
| Some (_, {pvar= weakSelf; loc= weakLoc}), Some (_, {pvar= self; loc= selfLoc}) ->
|
|
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 Location.pp weakLoc (Pvar.pp Pp.text) self Location.pp selfLoc
|
|
in
|
|
let ltr = make_trace_use_self_weakself domain in
|
|
Reporting.log_error summary ~ltr ~loc:selfLoc IssueType.mixed_self_weakself message
|
|
| _ ->
|
|
()
|
|
|
|
|
|
let report_weakself_multiple_issues summary domain =
|
|
let weakSelfs =
|
|
Vars.filter (fun _ {kind} -> DomainData.is_weak_self kind) domain
|
|
|> Vars.bindings |> List.map ~f:snd
|
|
in
|
|
let sorted_WeakSelfs =
|
|
List.sort weakSelfs ~compare:(fun {DomainData.loc= loc1} {DomainData.loc= loc2} ->
|
|
Location.compare loc1 loc2 )
|
|
in
|
|
match sorted_WeakSelfs with
|
|
| {pvar= weakSelf; loc= weakLoc1} :: {loc= weakLoc2} :: _ ->
|
|
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) weakSelf Location.pp weakLoc1 Location.pp weakLoc2 (Pvar.pp Pp.text)
|
|
weakSelf (Pvar.pp Pp.text) weakSelf
|
|
in
|
|
let ltr = make_trace_use_self_weakself domain in
|
|
Reporting.log_error summary ~ltr ~loc:weakLoc1 IssueType.multiple_weakself message
|
|
| _ ->
|
|
()
|
|
|
|
|
|
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 summary (domain : Domain.t) =
|
|
let unchecked_strongSelf_opt =
|
|
Vars.filter (fun _ {kind} -> DomainData.is_unchecked_strong_self kind) domain.vars
|
|
|> Vars.choose_opt
|
|
in
|
|
match unchecked_strongSelf_opt with
|
|
| Some (_, {pvar; loc}) ->
|
|
let message =
|
|
F.asprintf
|
|
"The variable `%a`, equal to a weak pointer to `self`, is used without a check for null \
|
|
at %a. This could cause a crash or unexpected behavior."
|
|
(Pvar.pp Pp.text) pvar Location.pp loc
|
|
in
|
|
let ltr = make_trace_unchecked_strongself domain in
|
|
Reporting.log_error summary ~ltr ~loc IssueType.strong_self_not_checked message
|
|
| _ ->
|
|
()
|
|
|
|
|
|
module Analyzer = AbstractInterpreter.MakeWTO (TransferFunctions)
|
|
|
|
let checker {Callbacks.exe_env; summary} =
|
|
let initial = {Domain.vars= Vars.empty; strongVars= StrongEqualToWeakCapturedVars.empty} in
|
|
let procname = Summary.get_proc_name summary in
|
|
let tenv = Exe_env.get_tenv exe_env procname in
|
|
let proc_data = ProcData.make summary tenv () in
|
|
( if Procname.is_objc_block procname then
|
|
match Analyzer.compute_post proc_data ~initial with
|
|
| Some domain ->
|
|
report_mix_self_weakself_issues summary domain.vars ;
|
|
report_unchecked_strongself_issues summary domain ;
|
|
report_weakself_multiple_issues summary domain.vars
|
|
| None ->
|
|
() ) ;
|
|
summary
|