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.
161 lines
5.6 KiB
161 lines
5.6 KiB
(*
|
|
* Copyright (c) 2017 - present Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*)
|
|
module L = Logging
|
|
module F = Format
|
|
|
|
let get_cycle root 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 a pair (path, bool) where path is a list of edges
|
|
describing the path to e_root and bool is true if e_root is reached. *)
|
|
let rec dfs root_node from_node path fields visited =
|
|
match fields with
|
|
| [] ->
|
|
(path, false)
|
|
| (field, Sil.Eexp (f_exp, f_inst)) :: el' ->
|
|
if Exp.equal f_exp root_node.rc_node_exp then
|
|
let rc_field = {rc_field_name= field; rc_field_inst= f_inst} in
|
|
let edge = {rc_from= from_node; rc_field} in
|
|
(edge :: path, true)
|
|
else if List.mem ~equal:Exp.equal visited f_exp then (path, false)
|
|
else
|
|
let visited' = from_node.rc_node_exp :: visited in
|
|
let res =
|
|
match get_points_to f_exp with
|
|
| None ->
|
|
(path, false)
|
|
| Some Sil.Hpointsto (_, Sil.Estruct (new_fields, _), Exp.Sizeof {typ= te}) ->
|
|
let rc_field = {rc_field_name= field; rc_field_inst= f_inst} in
|
|
let edge = {rc_from= from_node; rc_field} in
|
|
let rc_to = {rc_node_exp= f_exp; rc_node_typ= te} in
|
|
dfs root_node rc_to (edge :: path) new_fields visited'
|
|
| _ ->
|
|
(path, false)
|
|
(* check for lists *)
|
|
in
|
|
if snd res then res else dfs root_node from_node path el' visited'
|
|
| _ ->
|
|
(path, false)
|
|
in
|
|
L.d_strln "Looking for cycle with root expression: " ;
|
|
Sil.d_hpred root ;
|
|
L.d_strln "" ;
|
|
match root with
|
|
| Sil.Hpointsto (e_root, Sil.Estruct (fl, _), Exp.Sizeof {typ= te}) ->
|
|
let se_root = {rc_node_exp= e_root; rc_node_typ= te} in
|
|
(* start dfs with empty path and expr pointing to root *)
|
|
let pot_cycle, res = dfs se_root se_root [] fl [] in
|
|
if res then pot_cycle
|
|
else (
|
|
L.d_strln "NO cycle found from root" ;
|
|
[] )
|
|
| _ ->
|
|
L.d_strln "Root exp is not an allocated object. No cycle found" ;
|
|
[]
|
|
|
|
|
|
let get_retain_cycle_dotty prop_ cycle =
|
|
match prop_ with
|
|
| None ->
|
|
None
|
|
| Some Some prop_ ->
|
|
Dotty.dotty_retain_cycle_to_str prop_ cycle
|
|
| _ ->
|
|
None
|
|
|
|
|
|
let get_var_retain_cycle prop_ =
|
|
let sigma = prop_.Prop.sigma in
|
|
(* returns the pvars of the first cycle we find in sigma.
|
|
This is an heuristic that works if there is one cycle.
|
|
In case there are more than one cycle we may return not necessarily
|
|
the one we are looking for. *)
|
|
let rec do_sigma sigma_todo =
|
|
match sigma_todo with
|
|
| [] ->
|
|
[]
|
|
| hp :: sigma' ->
|
|
let cycle = get_cycle hp prop_ in
|
|
if List.is_empty cycle then do_sigma sigma' else cycle
|
|
in
|
|
let cycle_elements = do_sigma sigma in
|
|
RetainCyclesType.create_cycle cycle_elements
|
|
|
|
|
|
(** Checks if cycle has fields (derived from a property or directly defined as ivar) with attributes
|
|
weak/unsafe_unretained/assing *)
|
|
let cycle_has_weak_or_unretained_or_assign_field tenv cycle =
|
|
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
|
|
-> (
|
|
let equal_fn (fn', _, _) = Typ.Fieldname.equal fn fn' in
|
|
match Tenv.lookup tenv name with
|
|
| Some {fields; statics} ->
|
|
List.find ~f:equal_fn (fields @ statics) |> Option.value_map ~f:trd3 ~default:[]
|
|
| None ->
|
|
[] )
|
|
| _ ->
|
|
[]
|
|
in
|
|
let rec has_weak_or_unretained_or_assign params =
|
|
match params with
|
|
| [] ->
|
|
false
|
|
| att :: _
|
|
when String.equal Config.unsafe_unret att || String.equal Config.weak att
|
|
|| String.equal Config.assign att ->
|
|
true
|
|
| _ :: params' ->
|
|
has_weak_or_unretained_or_assign params'
|
|
in
|
|
let do_annotation ((a: Annot.t), _) =
|
|
( String.equal a.class_name Config.property_attributes
|
|
|| String.equal a.class_name Config.ivar_attributes )
|
|
&& has_weak_or_unretained_or_assign a.parameters
|
|
in
|
|
let rec do_cycle c =
|
|
let open RetainCyclesType in
|
|
match c with
|
|
| [] ->
|
|
false
|
|
| edge :: c' ->
|
|
let ia = get_item_annotation edge.rc_from.rc_node_typ edge.rc_field.rc_field_name in
|
|
if List.exists ~f:do_annotation ia then true else do_cycle c'
|
|
in
|
|
do_cycle cycle.rc_elements
|
|
|
|
|
|
let exn_retain_cycle original_prop hpred cycle =
|
|
let cycle_dotty = get_retain_cycle_dotty original_prop cycle in
|
|
let desc = Errdesc.explain_retain_cycle cycle (State.get_loc ()) cycle_dotty in
|
|
Exceptions.Retain_cycle (hpred, desc, __POS__)
|
|
|
|
|
|
let report_cycle tenv hpred original_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 remove_opt prop_ = match prop_ with Some Some p -> p | _ -> Prop.prop_emp in
|
|
match get_var_retain_cycle (remove_opt original_prop) with
|
|
| Some cycle when not (cycle_has_weak_or_unretained_or_assign_field tenv cycle) ->
|
|
RetainCyclesType.print_cycle cycle ;
|
|
Some (exn_retain_cycle original_prop hpred cycle)
|
|
| _ ->
|
|
None
|
|
|