[cost] Extend the polynomial domain for closure field

Summary:
This diff extended the polynomial domain to include symbols for closure calls.

When the closure symbol is added to the polynomial? Unknown closure is called inside a function
like,

```
foo() {
  self->closure_field();
}
```

Thus, the cost of `foo` becomes `|self->flosure_field|`, rather than unknown. (Note that this
semantics is added only for autoreleasepool size at the moment.)

When the symbol is instantiated? `foo` is called with correct closure contexts.

```
goo() {
  self->closure_field = ^(){ ... };
  foo();
}
```

The summary of `goo` will have instantiated summary of the closure.

Reviewed By: ezgicicek

Differential Revision: D23992590

fbshipit-source-id: d1d228403
master
Sungkeun Cho 4 years ago committed by Facebook GitHub Bot
parent bf1f4c393a
commit 075581ec8d

@ -502,14 +502,9 @@ let mk_eval_sym_trace ?(is_params_ref = false) integer_type_widths
; trace_of_sym }
let mk_eval_sym_mode ~mode integer_type_widths callee_formals actual_exps caller_mem =
let eval_sym_trace =
mk_eval_sym_trace integer_type_widths callee_formals actual_exps caller_mem ~mode
in
eval_sym_trace.eval_sym
let mk_eval_sym_cost integer_type_widths callee_formals actual_exps caller_mem =
mk_eval_sym_trace integer_type_widths callee_formals actual_exps caller_mem ~mode:EvalCost
let mk_eval_sym_cost = mk_eval_sym_mode ~mode:EvalCost
(* This function evaluates the array length conservatively, which is useful when there are multiple
array locations and their lengths are joined to top. For example, if the [arr_locs] points to

@ -79,7 +79,7 @@ val mk_eval_sym_cost :
-> (Pvar.t * Typ.t) list
-> (Exp.t * Typ.t) list
-> BufferOverrunDomain.Mem.t
-> Bounds.Bound.eval_sym
-> BufferOverrunDomain.eval_sym_trace
(** Make [eval_sym] function of [EvalCost] mode for on-demand symbol evaluation *)
module Prune : sig

@ -105,9 +105,16 @@ module NonNegativeBoundWithDegreeKind = struct
let make_err_trace_symbol symbol = NonNegativeBound.make_err_trace symbol
end
let pp_magic_parentheses pp fmt x =
let s = F.asprintf "%a" pp x in
if String.contains s ' ' then F.fprintf fmt "(%s)" s else F.pp_print_string fmt s
module NonNegativeNonTopPolynomial = struct
module Key = struct
type t = NonNegativeBoundWithDegreeKind of NonNegativeBoundWithDegreeKind.t
type t =
| NonNegativeBoundWithDegreeKind of NonNegativeBoundWithDegreeKind.t
| FuncPtr of Symb.SymbolPath.partial
[@@deriving compare]
let lift_valclass = function
@ -115,10 +122,20 @@ module NonNegativeNonTopPolynomial = struct
Symbolic (NonNegativeBoundWithDegreeKind s)
| (Constant _ | ValTop _) as x ->
x
let pp_hum ~hum f = function
| NonNegativeBoundWithDegreeKind bound ->
pp_magic_parentheses (NonNegativeBoundWithDegreeKind.pp ~hum) f bound
| FuncPtr partial ->
F.fprintf f "|%a|" Symb.SymbolPath.pp_partial partial
let pp = pp_hum ~hum:true
end
module M = struct
include Caml.Map.Make (Key)
include PrettyPrintable.MakePPMap (Key)
let increasing_union ~f m1 m2 = union (fun _ v1 v2 -> Some (f v1 v2)) m1 m2
@ -195,10 +212,13 @@ module NonNegativeNonTopPolynomial = struct
let rec degree_poly {terms} =
M.fold
(fun t p cur_max ->
let p = degree_poly p in
let degree_term =
match t with
| NonNegativeBoundWithDegreeKind t ->
let degree_term =
Degree.succ (NonNegativeBoundWithDegreeKind.degree_kind t) (degree_poly p)
Degree.succ (NonNegativeBoundWithDegreeKind.degree_kind t) p
| FuncPtr _ ->
p
in
if Degree.compare degree_term cur_max > 0 then degree_term else cur_max )
terms Degree.zero
@ -293,8 +313,6 @@ module NonNegativeNonTopPolynomial = struct
{const= NonNegativeInt.zero; terms}
let mult_symb : t -> Key.t -> t = fun x s -> {x with poly= mult_symb_poly x.poly s}
let rec mult_poly : poly -> poly -> poly =
fun p1 p2 ->
if is_zero_poly p1 || is_zero_poly p2 then zero_poly
@ -324,6 +342,13 @@ module NonNegativeNonTopPolynomial = struct
body )
let singleton key =
{ poly= {const= NonNegativeInt.zero; terms= M.singleton key one_poly}
; autoreleasepool_trace= None }
let of_func_ptr path = singleton (FuncPtr path)
let rec of_valclass : (NonNegativeInt.t, Key.t, 't) valclass -> ('t, t, 't) below_above = function
| ValTop trace ->
Above trace
@ -332,9 +357,7 @@ module NonNegativeNonTopPolynomial = struct
| Symbolic (NonNegativeBoundWithDegreeKind s as key) -> (
match NonNegativeBoundWithDegreeKind.split_mult s with
| None ->
Val
{ poly= {const= NonNegativeInt.zero; terms= M.singleton key one_poly}
; autoreleasepool_trace= None }
Val (singleton key)
| Some (s1, s2) -> (
match
( of_valclass (Key.lift_valclass (NonNegativeBoundWithDegreeKind.classify s1))
@ -346,6 +369,8 @@ module NonNegativeNonTopPolynomial = struct
assert false
| (Above _ as t), _ | _, (Above _ as t) ->
t ) )
| Symbolic (FuncPtr _ as key) ->
Val (singleton key)
let rec int_lb {const; terms} =
@ -355,7 +380,9 @@ module NonNegativeNonTopPolynomial = struct
| NonNegativeBoundWithDegreeKind symbol ->
let s_lb = NonNegativeBoundWithDegreeKind.int_lb symbol in
let p_lb = int_lb polynomial in
NonNegativeInt.((s_lb * p_lb) + acc) )
NonNegativeInt.((s_lb * p_lb) + acc)
| FuncPtr _ ->
acc )
terms const
@ -367,7 +394,9 @@ module NonNegativeNonTopPolynomial = struct
Option.bind acc ~f:(fun acc ->
Option.bind (NonNegativeBoundWithDegreeKind.int_ub symbol) ~f:(fun s_ub ->
Option.map (int_ub polynomial) ~f:(fun p_ub ->
NonNegativeInt.((s_ub * p_ub) + acc) ) ) ) )
NonNegativeInt.((s_ub * p_ub) + acc) ) ) )
| FuncPtr _ ->
acc )
terms (Some const)
@ -398,9 +427,9 @@ module NonNegativeNonTopPolynomial = struct
; terms=
M.fold
(fun s p acc ->
let p' = mask_min_max_constant_poly p in
match s with
| NonNegativeBoundWithDegreeKind s ->
let p' = mask_min_max_constant_poly p in
M.update
(NonNegativeBoundWithDegreeKind
(NonNegativeBoundWithDegreeKind.mask_min_max_constant s))
@ -409,7 +438,9 @@ module NonNegativeNonTopPolynomial = struct
Some p'
| Some p ->
if leq_poly ~lhs:p ~rhs:p' then Some p' else Some p )
acc )
acc
| FuncPtr _ as key ->
M.add key p' acc )
terms M.empty }
@ -428,10 +459,11 @@ module NonNegativeNonTopPolynomial = struct
if is_constant p1 then p1 else if is_constant p2 then p2 else p1
let subst callee_pname location =
let subst callee_pname location {poly; autoreleasepool_trace} eval_sym eval_func_ptrs
get_closure_callee_cost ~default_closure_cost =
let exception ReturnTop of (NonNegativeBoundWithDegreeKind.t * BoundTrace.t) in
(* avoids top-lifting everything *)
let rec subst_poly {const; terms} eval_sym =
let rec subst_poly {const; terms} =
M.fold
(fun s p acc ->
match s with
@ -442,18 +474,36 @@ module NonNegativeNonTopPolynomial = struct
| None ->
acc
| Some c ->
let p = subst_poly p eval_sym in
let p = subst_poly p in
mult_const_positive p c |> plus_poly acc )
| ValTop trace ->
let p = subst_poly p eval_sym in
let p = subst_poly p in
if is_zero_poly p then acc else raise (ReturnTop (s, trace))
| Symbolic s ->
let p = subst_poly p eval_sym in
mult_symb_poly p (NonNegativeBoundWithDegreeKind s) |> plus_poly acc ) )
let p = subst_poly p in
mult_symb_poly p (NonNegativeBoundWithDegreeKind s) |> plus_poly acc )
| FuncPtr s ->
let funcptr_p =
let p = subst_poly p in
match FuncPtr.Set.is_singleton_or_more (eval_func_ptrs s) with
| Singleton (Closure {name}) ->
let closure_p =
match get_closure_callee_cost name with
| Some {poly= closure_p} ->
closure_p
| None ->
poly_of_non_negative_int default_closure_cost
in
mult_poly closure_p p
| Singleton (Path path) ->
mult_symb_poly p (FuncPtr path)
| Empty | More ->
mult_poly (poly_of_non_negative_int default_closure_cost) p
in
plus_poly acc funcptr_p )
terms (poly_of_non_negative_int const)
in
fun {poly; autoreleasepool_trace} eval_sym ->
match subst_poly poly eval_sym with
match subst_poly poly with
| poly ->
let autoreleasepool_trace =
Option.map autoreleasepool_trace ~f:(BoundTrace.call ~callee_pname ~location)
@ -464,24 +514,44 @@ module NonNegativeNonTopPolynomial = struct
(** Emit a pair (d,t) where d is the degree of the polynomial and t is the first term with such
degree *)
degree. When calculating the degree, it ignores symbols of function pointer, so they are
addressed as if zero cost. *)
let degree_with_term {poly; autoreleasepool_trace} =
let rec degree_with_term_poly {terms} =
let rec is_func_ptr_poly {const; terms} =
NonNegativeInt.is_zero const
&& M.for_all
(fun key v ->
match key with
| FuncPtr _ ->
true
| NonNegativeBoundWithDegreeKind _ ->
is_func_ptr_poly v )
terms
in
let rec degree_with_term_poly ({terms} as poly) =
if is_func_ptr_poly poly then (Degree.zero, true, poly)
else
M.fold
(fun t p cur_max ->
match t with
| NonNegativeBoundWithDegreeKind t ->
let d, p' = degree_with_term_poly p in
let degree_term =
( Degree.succ (NonNegativeBoundWithDegreeKind.degree_kind t) d
, mult_symb p' (NonNegativeBoundWithDegreeKind t) )
match (t, degree_with_term_poly p) with
| NonNegativeBoundWithDegreeKind b, (d, false, p') ->
( Degree.succ (NonNegativeBoundWithDegreeKind.degree_kind b) d
, false
, mult_symb_poly p' t )
| FuncPtr _, (_, _, p') | _, (_, true, p') ->
(* It ignores function pointers when calculating degree of polynomial, since their
semantics is different to the other symbolic values. For example, when a
function has a complexity of |fptr| where fptr is a function pointer, it does
not make sense to say the function has a linear complexity. *)
(Degree.zero, true, mult_symb_poly p' t)
in
if [%compare: Degree.t * t] degree_term cur_max > 0 then degree_term else cur_max )
terms
(Degree.zero, one ())
if [%compare: Degree.t * bool * poly] degree_term cur_max > 0 then degree_term
else cur_max )
terms (Degree.zero, false, one_poly)
in
let d, p = degree_with_term_poly poly in
(d, {p with autoreleasepool_trace})
let d, _, poly = degree_with_term_poly poly in
(d, {poly; autoreleasepool_trace})
let degree p = fst (degree_with_term p)
@ -500,16 +570,7 @@ module NonNegativeNonTopPolynomial = struct
let pp_exp fmt (e : PositiveInt.t) =
if Z.(gt (e :> Z.t) one) then PositiveInt.pp_exponent fmt e
in
let pp_magic_parentheses pp fmt x =
let s = F.asprintf "%a" pp x in
if String.contains s ' ' then F.fprintf fmt "(%s)" s else F.pp_print_string fmt s
in
let pp_symb ~hum fmt symb =
match (symb : Key.t) with
| NonNegativeBoundWithDegreeKind symb ->
pp_magic_parentheses (NonNegativeBoundWithDegreeKind.pp ~hum) fmt symb
in
let pp_symb_exp ~hum fmt (symb, exp) = F.fprintf fmt "%a%a" (pp_symb ~hum) symb pp_exp exp in
let pp_symb_exp ~hum fmt (symb, exp) = F.fprintf fmt "%a%a" (Key.pp_hum ~hum) symb pp_exp exp in
let pp_symbs ~hum fmt (last, others) =
List.rev_append others [last] |> Pp.seq ~sep:multiplication_sep (pp_symb_exp ~hum) fmt
in
@ -547,9 +608,14 @@ module NonNegativeNonTopPolynomial = struct
let rec get_symbols_sub {terms} acc =
M.fold
(fun s p acc ->
let acc =
match s with
| NonNegativeBoundWithDegreeKind s ->
get_symbols_sub p (NonNegativeBoundWithDegreeKind.symbol s :: acc) )
NonNegativeBoundWithDegreeKind.symbol s :: acc
| FuncPtr _ ->
acc
in
get_symbols_sub p acc )
terms acc
in
get_symbols_sub p.poly []
@ -738,6 +804,8 @@ module NonNegativePolynomial = struct
|> make_trace_set ~map_above:TopTrace.unbounded_loop
let of_func_ptr path = Val (NonNegativeNonTopPolynomial.of_func_ptr path)
let is_symbolic = function
| Below _ | Above _ ->
false
@ -783,7 +851,8 @@ module NonNegativePolynomial = struct
~f_below:UnreachableTraces.join
let subst callee_pname location p eval_sym =
let subst callee_pname location p eval_sym eval_func_ptrs get_closure_callee_cost
~default_closure_cost =
match p with
| Above callee_traces ->
Above
@ -796,7 +865,16 @@ module NonNegativePolynomial = struct
(fun callee_trace -> UnreachableTrace.call ~callee_pname ~location callee_trace)
callee_traces)
| Val p ->
NonNegativeNonTopPolynomial.subst callee_pname location p eval_sym
let get_closure_callee_cost pname =
match get_closure_callee_cost pname with
| Some (Val p) ->
Some p
| None | Some (Below _ | Above _) ->
(* It doesn't propagate Top/Bottoms if the closure has these costs. *)
None
in
NonNegativeNonTopPolynomial.subst callee_pname location p eval_sym eval_func_ptrs
get_closure_callee_cost ~default_closure_cost
|> make_trace_set ~map_above:(fun (symbol, bound_trace) ->
TopTrace.unbounded_symbol ~location ~symbol bound_trace )

@ -71,6 +71,8 @@ module NonNegativePolynomial : sig
val of_non_negative_bound : ?degree_kind:DegreeKind.t -> Bounds.NonNegativeBound.t -> t
val of_func_ptr : Symb.SymbolPath.partial -> t
val plus : t -> t -> t
val mult_unreachable : t -> t -> t
@ -82,7 +84,15 @@ module NonNegativePolynomial : sig
val min_default_left : t -> t -> t
val subst : Procname.t -> Location.t -> t -> Bounds.Bound.eval_sym -> t
val subst :
Procname.t
-> Location.t
-> t
-> Bounds.Bound.eval_sym
-> FuncPtr.Set.eval_func_ptrs
-> (Procname.t -> t option)
-> default_closure_cost:Ints.NonNegativeInt.t
-> t
val degree : t -> Degree.t option

@ -25,13 +25,18 @@ type extras_WorstCaseCost =
; get_formals: Procname.t -> (Pvar.t * Typ.t) list option
; proc_resolve_attributes: Procname.t -> ProcAttributes.t option }
let instantiate_cost integer_type_widths ~inferbo_caller_mem ~callee_pname ~callee_formals ~params
~callee_cost ~loc =
let eval_sym =
let instantiate_cost ?get_closure_callee_cost ~default_closure_cost integer_type_widths
~inferbo_caller_mem ~callee_pname ~callee_formals ~params ~callee_cost ~loc =
let {BufferOverrunDomain.eval_sym; eval_func_ptrs} =
BufferOverrunSemantics.mk_eval_sym_cost integer_type_widths callee_formals params
inferbo_caller_mem
in
BasicCostWithReason.subst callee_pname loc callee_cost eval_sym
let get_closure_callee_cost pname =
Option.bind get_closure_callee_cost ~f:(fun get_closure_callee_cost ->
get_closure_callee_cost pname )
in
BasicCostWithReason.subst callee_pname loc callee_cost eval_sym eval_func_ptrs
get_closure_callee_cost ~default_closure_cost
module InstrBasicCostWithReason = struct
@ -101,6 +106,21 @@ module InstrBasicCostWithReason = struct
|> Option.value ~default:(Option.value callee_cost_opt ~default:BasicCostWithReason.zero)
let dispatch_autoreleasepool_func_ptr_call {inferbo_invariant_map; integer_type_widths} instr_node
fun_exp =
BufferOverrunAnalysis.extract_pre (InstrCFG.Node.id instr_node) inferbo_invariant_map
|> Option.value_map ~default:BasicCostWithReason.zero ~f:(fun inferbo_mem ->
let func_ptrs =
BufferOverrunSemantics.eval integer_type_widths fun_exp inferbo_mem
|> BufferOverrunDomain.Val.get_func_ptrs
in
match FuncPtr.Set.is_singleton_or_more func_ptrs with
| Singleton (Path path) ->
BasicCost.of_func_ptr path |> BasicCostWithReason.of_basic_cost
| _ ->
BasicCostWithReason.zero )
let get_instr_cost_record tenv extras cfg instr_node instr =
match instr with
| Sil.Call (ret, Exp.Const (Const.Cfun callee_pname), params, location, _)
@ -126,12 +146,25 @@ module InstrBasicCostWithReason = struct
integer_type_widths inferbo_get_summary )
in
let get_callee_cost_opt kind inferbo_mem =
let default_closure_cost =
match (kind : CostKind.t) with
| OperationCost ->
Ints.NonNegativeInt.one
| AllocationCost | AutoreleasepoolSize ->
Ints.NonNegativeInt.zero
in
match (get_summary callee_pname, get_formals callee_pname) with
| Some {CostDomain.post= callee_cost_record}, Some callee_formals ->
CostDomain.find_opt kind callee_cost_record
|> Option.map ~f:(fun callee_cost ->
instantiate_cost integer_type_widths ~inferbo_caller_mem:inferbo_mem
~callee_pname ~callee_formals ~params ~callee_cost ~loc:location )
let get_closure_callee_cost pname =
get_summary pname
|> Option.map ~f:(fun {CostDomain.post} ->
CostDomain.get_cost_kind kind post )
in
instantiate_cost ~get_closure_callee_cost ~default_closure_cost
integer_type_widths ~inferbo_caller_mem:inferbo_mem ~callee_pname
~callee_formals ~params ~callee_cost ~loc:location )
| _ ->
None
in
@ -152,12 +185,21 @@ module InstrBasicCostWithReason = struct
model_env ret cfg location inferbo_mem ) )
| Sil.Call (_, Exp.Const (Const.Cfun _), _, _, _) ->
CostDomain.zero_record
| Sil.Call (_, fun_exp, _, _location, _) ->
CostDomain.construct ~f:(fun kind ->
match kind with
| OperationCost ->
BasicCostWithReason.one ()
| AllocationCost ->
BasicCostWithReason.zero
| AutoreleasepoolSize ->
dispatch_autoreleasepool_func_ptr_call extras instr_node fun_exp )
| Sil.Load {id= lhs_id} when Ident.is_none lhs_id ->
(* dummy deref inserted by frontend--don't count as a step. In
JDK 11, dummy deref disappears and causes cost differences
otherwise. *)
CostDomain.zero_record
| Sil.Load _ | Sil.Store _ | Sil.Call _ | Sil.Prune _ ->
| Sil.Load _ | Sil.Store _ | Sil.Prune _ ->
CostDomain.unit_cost_atomic_operation
| Sil.Metadata Skip -> (
match InstrCFG.Node.kind instr_node with

@ -15,7 +15,9 @@ val checker :
-> CostDomain.summary option
val instantiate_cost :
Typ.IntegerWidths.t
?get_closure_callee_cost:(Procname.t -> CostDomain.BasicCostWithReason.t option)
-> default_closure_cost:Ints.NonNegativeInt.t
-> Typ.IntegerWidths.t
-> inferbo_caller_mem:BufferOverrunDomain.Mem.t
-> callee_pname:Procname.t
-> callee_formals:(Pvar.t * Typ.t) list

@ -33,8 +33,15 @@ module BasicCostWithReason = struct
let of_basic_cost cost = {cost; top_pname_opt= None}
let subst callee_pname location {cost; top_pname_opt} eval_sym =
let cost = BasicCost.subst callee_pname location cost eval_sym in
let subst callee_pname location {cost; top_pname_opt} eval_sym eval_func_ptrs
get_closure_callee_cost ~default_closure_cost =
let get_closure_callee_cost pname =
get_closure_callee_cost pname |> Option.map ~f:(fun {cost} -> cost)
in
let cost =
BasicCost.subst callee_pname location cost eval_sym eval_func_ptrs get_closure_callee_cost
~default_closure_cost
in
if BasicCost.is_top cost then {cost; top_pname_opt= Some callee_pname} else {cost; top_pname_opt}

@ -43,7 +43,15 @@ module BasicCostWithReason : sig
val plus : t -> t -> t
val subst : Procname.t -> Location.t -> t -> Bounds.Bound.eval_sym -> t
val subst :
Procname.t
-> Location.t
-> t
-> Bounds.Bound.eval_sym
-> FuncPtr.Set.eval_func_ptrs
-> (Procname.t -> t option)
-> default_closure_cost:Ints.NonNegativeInt.t
-> t
val degree : t -> Polynomials.Degree.t option

@ -113,8 +113,9 @@ let get_cost_if_expensive tenv integer_type_widths get_callee_cost_summary_and_f
let callee_cost = CostDomain.get_operation_cost cost_record in
if CostDomain.BasicCost.is_symbolic callee_cost.cost then
Some
(Cost.instantiate_cost integer_type_widths ~inferbo_caller_mem:inferbo_mem
~callee_pname:pname ~callee_formals ~params ~callee_cost ~loc)
(Cost.instantiate_cost ~default_closure_cost:Ints.NonNegativeInt.one integer_type_widths
~inferbo_caller_mem:inferbo_mem ~callee_pname:pname ~callee_formals ~params
~callee_cost ~loc)
.cost
else None
| None ->

@ -24,12 +24,12 @@ codetoanalyze/objc/autoreleasepool/arc_caller.m, ArcCaller.callGiveMeObject_line
codetoanalyze/objc/autoreleasepool/arc_caller.m, ArcCaller.callMutableCopyObject_zero:x:, 0, OnUIThread:false, []
codetoanalyze/objc/autoreleasepool/arc_caller.m, ArcCaller.callNewObject_zero:, 0, OnUIThread:false, []
codetoanalyze/objc/autoreleasepool/arc_caller.m, ArcCaller.dealloc, 0, OnUIThread:false, []
codetoanalyze/objc/autoreleasepool/arc_enumerator.m, ArcEnumerator.callMyEnumerator_linear_FN:, 0, OnUIThread:false, []
codetoanalyze/objc/autoreleasepool/arc_enumerator.m, ArcEnumerator.callMyEnumerator_linear_FN:, 1, OnUIThread:false, []
codetoanalyze/objc/autoreleasepool/arc_enumerator.m, ArcEnumerator.dealloc, 0, OnUIThread:false, []
codetoanalyze/objc/autoreleasepool/arc_enumerator.m, ArcEnumerator.makeMyEnumerator_zero:, 0, OnUIThread:false, []
codetoanalyze/objc/autoreleasepool/arc_enumerator.m, MyEnumerator.dealloc, 0, OnUIThread:false, []
codetoanalyze/objc/autoreleasepool/arc_enumerator.m, MyEnumerator.initWithArray:filter:, 0, OnUIThread:false, []
codetoanalyze/objc/autoreleasepool/arc_enumerator.m, MyEnumerator.nextObject, 0, OnUIThread:false, []
codetoanalyze/objc/autoreleasepool/arc_enumerator.m, MyEnumerator.nextObject, |self->_filter|, OnUIThread:false, []
codetoanalyze/objc/autoreleasepool/arc_enumerator.m, objc_blockArcEnumerator.makeMyEnumerator_zero:_1, 1, OnUIThread:false, [autorelease,Call to NoArcCallee.giveMeObject,Modeled call to NSObject.autorelease]
codetoanalyze/objc/autoreleasepool/arc_keyed_unarchiver.m, ArcKeyedUnarchiver.callInitForReadingFromData_constant:data:, 1, OnUIThread:false, [autorelease,Modeled call to NSKeyedUnarchiver.initForReadingFromData:error:]
codetoanalyze/objc/autoreleasepool/arc_keyed_unarchiver.m, ArcKeyedUnarchiver.callInitForReadingWithData_constant:data:, 1, OnUIThread:false, [autorelease,Modeled call to NSKeyedUnarchiver.initForReadingWithData:]

Loading…
Cancel
Save