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.
254 lines
9.4 KiB
254 lines
9.4 KiB
(*
|
|
* Copyright (c) 2017-present, Facebook, Inc.
|
|
*
|
|
* 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 MF = MarkupFormatter
|
|
|
|
let desc_retain_cycle tenv (cycle: RetainCyclesType.t) =
|
|
let open RetainCyclesType in
|
|
Logging.d_strln "Proposition with retain cycle:" ;
|
|
let do_edge index_ edge =
|
|
let index = index_ + 1 in
|
|
let node = State.get_node () in
|
|
let from_exp_str edge_obj =
|
|
let type_str =
|
|
let typ_str = Typ.to_string edge_obj.rc_from.rc_node_typ in
|
|
if String.equal typ_str "objc_object" then MF.monospaced_to_string "id"
|
|
else MF.monospaced_to_string (Format.sprintf "%s*" typ_str)
|
|
in
|
|
match Errdesc.find_outermost_dereference tenv node edge_obj.rc_from.rc_node_exp with
|
|
| Some de
|
|
-> (
|
|
let decomp = DecompiledExp.to_string de in
|
|
match de with
|
|
| DecompiledExp.Dretcall _ ->
|
|
Format.sprintf "object %s of type %s" decomp type_str
|
|
| _ ->
|
|
Format.sprintf "%s of type %s" (MF.monospaced_to_string decomp) type_str )
|
|
| None ->
|
|
Format.sprintf "object of type %s" type_str
|
|
in
|
|
let location_str =
|
|
match edge with
|
|
| Object obj ->
|
|
let update_str_list =
|
|
Localise.access_desc (Errdesc.access_opt obj.rc_field.rc_field_inst)
|
|
in
|
|
if List.is_empty update_str_list then " "
|
|
else ", " ^ String.concat ~sep:"," update_str_list
|
|
| Block _ ->
|
|
""
|
|
in
|
|
let cycle_item_str =
|
|
match edge with
|
|
| Object obj ->
|
|
Format.sprintf "%s --> %s" (from_exp_str obj)
|
|
(MF.monospaced_to_string (Typ.Fieldname.to_string obj.rc_field.rc_field_name))
|
|
| Block (_, var) ->
|
|
Format.sprintf "a block that captures %s" (MF.monospaced_to_string (Pvar.to_string var))
|
|
in
|
|
Format.sprintf "(%d) %s%s" index cycle_item_str location_str
|
|
in
|
|
let cycle_str = List.mapi ~f:do_edge cycle.rc_elements in
|
|
List.fold_left cycle_str ~f:(fun acc s -> Format.sprintf "%s\n %s" acc s) ~init:""
|
|
|
|
|
|
let edge_is_strong tenv obj_edge =
|
|
let open RetainCyclesType in
|
|
(* returns items annotation for field fn in struct t *)
|
|
let get_item_annotation (t: Typ.t) fn =
|
|
match t.desc with
|
|
| Tstruct name -> (
|
|
match Tenv.lookup tenv name with
|
|
| Some {fields} -> (
|
|
match List.find ~f:(fun (fn', _, _) -> Typ.Fieldname.equal fn fn') fields with
|
|
| None ->
|
|
None
|
|
| Some (_, _, ann) ->
|
|
Some ann (* Only returns annotations when the type and field are found in the tenv *) )
|
|
| None ->
|
|
None )
|
|
| _ ->
|
|
None
|
|
in
|
|
let has_weak_or_unretained_or_assign params =
|
|
List.exists
|
|
~f:(fun att ->
|
|
String.equal Config.unsafe_unret att || String.equal Config.weak att
|
|
|| String.equal Config.assign att )
|
|
params
|
|
in
|
|
let weak_edge_by_type =
|
|
match obj_edge.rc_from.rc_node_typ.Typ.desc with
|
|
| Typ.Tptr (_, Typ.Pk_objc_weak) | Typ.Tptr (_, Typ.Pk_objc_unsafe_unretained) ->
|
|
true
|
|
| _ ->
|
|
false
|
|
in
|
|
let weak_edge_by_field =
|
|
match get_item_annotation obj_edge.rc_from.rc_node_typ obj_edge.rc_field.rc_field_name with
|
|
| Some ia ->
|
|
List.exists
|
|
~f:(fun ((ann: Annot.t), _) ->
|
|
( String.equal ann.class_name Config.property_attributes
|
|
|| String.equal ann.class_name Config.ivar_attributes )
|
|
&& has_weak_or_unretained_or_assign ann.parameters )
|
|
ia
|
|
| None ->
|
|
true
|
|
(* Assume the edge is weak if the type or field cannot be found in the tenv, to avoid FPs *)
|
|
in
|
|
not (weak_edge_by_type || weak_edge_by_field)
|
|
|
|
|
|
exception Max_retain_cycles of RetainCyclesType.Set.t
|
|
|
|
let add_cycle found_cycles rev_path =
|
|
match RetainCyclesType.create_cycle (List.rev rev_path) with
|
|
| Some cycle ->
|
|
if RetainCyclesType.Set.cardinal found_cycles < 10 then
|
|
RetainCyclesType.Set.add cycle found_cycles
|
|
else raise (Max_retain_cycles found_cycles)
|
|
| None ->
|
|
found_cycles
|
|
|
|
|
|
let get_cycle_blocks root_node exp =
|
|
match exp with
|
|
| Exp.Closure {name; captured_vars} ->
|
|
List.find_map
|
|
~f:(fun (e, var, typ) ->
|
|
match typ.Typ.desc with
|
|
| Typ.Tptr (_, Typ.Pk_objc_weak) | Typ.Tptr (_, Typ.Pk_objc_unsafe_unretained) ->
|
|
None
|
|
| _ ->
|
|
if Exp.equal e root_node.RetainCyclesType.rc_node_exp then Some (name, var) else None
|
|
)
|
|
captured_vars
|
|
| _ ->
|
|
None
|
|
|
|
|
|
let get_weak_alias_type prop e =
|
|
let sigma = prop.Prop.sigma in
|
|
let check_weak_alias hpred =
|
|
match hpred with
|
|
| Sil.Hpointsto (_, Sil.Eexp (e', _), Sizeof {typ}) -> (
|
|
match typ.Typ.desc with
|
|
| (Typ.Tptr (_, Typ.Pk_objc_weak) | Typ.Tptr (_, Typ.Pk_objc_unsafe_unretained))
|
|
when Exp.equal e' e ->
|
|
Some typ
|
|
| _ ->
|
|
None )
|
|
| _ ->
|
|
None
|
|
in
|
|
List.find_map ~f:check_weak_alias sigma
|
|
|
|
|
|
let get_cycles found_cycles root tenv prop =
|
|
let open RetainCyclesType in
|
|
let sigma = prop.Prop.sigma in
|
|
let get_points_to e =
|
|
List.find
|
|
~f:(fun hpred -> match hpred with Sil.Hpointsto (e', _, _) -> Exp.equal e' e | _ -> false)
|
|
sigma
|
|
in
|
|
(* Perform a dfs of a graph stopping when e_root is reached. Returns the set of cycles reached. *)
|
|
let rec dfs ~found_cycles ~root_node ~from_node ~rev_path ~fields ~visited =
|
|
let from_node =
|
|
match get_weak_alias_type prop from_node.rc_node_exp with
|
|
| Some typ ->
|
|
{from_node with rc_node_typ= typ}
|
|
| None ->
|
|
from_node
|
|
in
|
|
match fields with
|
|
| [] ->
|
|
found_cycles
|
|
| (field, Sil.Eexp (f_exp, f_inst)) :: el' ->
|
|
let rc_field = {rc_field_name= field; rc_field_inst= f_inst} in
|
|
let obj_edge = {rc_from= from_node; rc_field} in
|
|
let edge = Object obj_edge in
|
|
let visited' = from_node.rc_node_exp :: visited in
|
|
let found_cycles' =
|
|
(* found root, finish the cycle *)
|
|
if edge_is_strong tenv obj_edge && Exp.equal f_exp root_node.rc_node_exp then
|
|
add_cycle found_cycles (edge :: rev_path) (* we already visited f_exp, stop *)
|
|
else if List.mem ~equal:Exp.equal visited f_exp then found_cycles
|
|
else
|
|
(* cycle with a block *)
|
|
let cycle_opt = get_cycle_blocks root_node f_exp in
|
|
if edge_is_strong tenv obj_edge && Option.is_some cycle_opt then
|
|
let procname, captured_var = Option.value_exn cycle_opt in
|
|
let edge2 = Block (procname, captured_var) in
|
|
let rev_path' = edge2 :: edge :: rev_path in
|
|
add_cycle found_cycles rev_path'
|
|
else
|
|
match get_points_to f_exp with
|
|
| None ->
|
|
found_cycles
|
|
| Some (Sil.Hpointsto (_, Sil.Estruct (new_fields, _), Exp.Sizeof {typ= te}))
|
|
when edge_is_strong tenv obj_edge ->
|
|
let rc_to = {rc_node_exp= f_exp; rc_node_typ= te} in
|
|
dfs ~found_cycles ~root_node ~from_node:rc_to ~rev_path:(edge :: rev_path)
|
|
~fields:new_fields ~visited:visited'
|
|
| _ ->
|
|
found_cycles
|
|
in
|
|
dfs ~found_cycles:found_cycles' ~root_node ~from_node ~rev_path ~fields:el'
|
|
~visited:visited'
|
|
| _ ->
|
|
found_cycles
|
|
in
|
|
match root with
|
|
| Sil.Hpointsto (e_root, Sil.Estruct (fl, _), Exp.Sizeof {typ= te}) when Sil.is_objc_object root ->
|
|
let se_root = {rc_node_exp= e_root; rc_node_typ= te} in
|
|
(* start dfs with empty path and expr pointing to root *)
|
|
dfs ~found_cycles ~root_node:se_root ~from_node:se_root ~rev_path:[] ~fields:fl ~visited:[]
|
|
| _ ->
|
|
found_cycles
|
|
|
|
|
|
(* Find all the cycles available in prop, up to a limit of 10 *)
|
|
let get_retain_cycles tenv prop =
|
|
let get_retain_cycles_with_root acc_set root = get_cycles acc_set root tenv prop in
|
|
let sigma = prop.Prop.sigma in
|
|
try List.fold ~f:get_retain_cycles_with_root sigma ~init:RetainCyclesType.Set.empty
|
|
with Max_retain_cycles cycles -> cycles
|
|
|
|
|
|
let exn_retain_cycle tenv cycle =
|
|
let retain_cycle = desc_retain_cycle tenv cycle in
|
|
let cycle_dotty = Format.asprintf "%a" RetainCyclesType.pp_dotty cycle in
|
|
if Config.debug_mode then (
|
|
let rc_dotty_dir = Filename.concat Config.results_dir Config.retain_cycle_dotty_dir in
|
|
Utils.create_dir rc_dotty_dir ;
|
|
let rc_dotty_file = Filename.temp_file ~in_dir:rc_dotty_dir "rc" ".dot" in
|
|
RetainCyclesType.write_dotty_to_file rc_dotty_file cycle ) ;
|
|
let desc = Localise.desc_retain_cycle retain_cycle (State.get_loc ()) (Some cycle_dotty) in
|
|
Exceptions.Retain_cycle (desc, __POS__)
|
|
|
|
|
|
let report_cycle tenv pname prop =
|
|
(* When there is a cycle in objc we ignore it
|
|
only if it's empty or it has weak or unsafe_unretained fields.
|
|
Otherwise we report a retain cycle. *)
|
|
let cycles = get_retain_cycles tenv prop in
|
|
RetainCyclesType.Set.iter RetainCyclesType.print_cycle cycles ;
|
|
match Summary.get pname with
|
|
| Some summary ->
|
|
if RetainCyclesType.Set.cardinal cycles > 0 then (
|
|
RetainCyclesType.Set.iter
|
|
(fun cycle ->
|
|
let exn = exn_retain_cycle tenv cycle in
|
|
Reporting.log_error summary exn )
|
|
cycles ;
|
|
(* we report the retain cycles above but need to raise an exception as well to stop the analysis *)
|
|
raise (Exceptions.Dummy_exception (Localise.verbatim_desc "retain cycle found")) )
|
|
| None ->
|
|
()
|