Skip to content

Commit

Permalink
[clang] Adding implicit implementation of dealloc methods to ObjC cla…
Browse files Browse the repository at this point in the history
…sses

Summary:
This models ARC implementation of dealloc, see https://clang.llvm.org/docs/AutomaticReferenceCounting.html#dealloc. Dealloc methods can be added to ObjC classes to free C memory for example, but the deallocation of the ObjC instance variables of the object is done automatically. So here we add this explicitly to Infer:
1. First, we add an empty dealloc method when it is not written explicitly.
2. For each dealloc method (including the implicitly added ones) we add calls to dealloc of the ObjC instance variables.

Reviewed By: jvillard

Differential Revision: D21883546

fbshipit-source-id: f5d4930f2
  • Loading branch information
dulmarod authored and facebook-github-bot committed Jun 12, 2020
1 parent 2707ee8 commit 61d5fde
Show file tree
Hide file tree
Showing 66 changed files with 811 additions and 103 deletions.
6 changes: 6 additions & 0 deletions infer/src/IR/Procname.ml
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,12 @@ let is_objc_method procname =
match procname with ObjC_Cpp name -> ObjC_Cpp.is_objc_method name | _ -> false


let is_objc_dealloc procname =
is_objc_method procname
&&
match procname with ObjC_Cpp {method_name} -> ObjC_Cpp.is_objc_dealloc method_name | _ -> false


let block_name_of_procname procname =
match procname with
| Block block ->
Expand Down
3 changes: 3 additions & 0 deletions infer/src/IR/Procname.mli
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,9 @@ val get_method : t -> string
val is_objc_block : t -> bool
(** Return whether the procname is a block procname. *)

val is_objc_dealloc : t -> bool
(** Return whether the dealloc method of an Objective-C class. *)

val is_c_method : t -> bool
(** Return true this is an Objective-C/C++ method name. *)

Expand Down
81 changes: 81 additions & 0 deletions infer/src/clang/CAddImplicitDeallocImpl.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
(*
* 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

let get_dealloc_call_field (self_var, self_typ) location instrs (fieldname, field_typ, _) =
match field_typ.Typ.desc with
| Typ.Tptr (({desc= Tstruct name} as cls), Pk_pointer) when Typ.is_objc_class cls ->
let field_class_dealloc_name = Procname.make_objc_dealloc name in
let id_pvar = Ident.create_fresh Ident.knormal in
let load_pvar_instr =
Sil.Load {id= id_pvar; e= Lvar self_var; root_typ= self_typ; typ= self_typ; loc= location}
in
let id_field = Ident.create_fresh Ident.knormal in
let class_typ = match self_typ.Typ.desc with Typ.Tptr (t, _) -> t | _ -> self_typ in
let e = Exp.Lfield (Var id_pvar, fieldname, class_typ) in
let load_field_instr =
Sil.Load {id= id_field; e; root_typ= field_typ; typ= field_typ; loc= location}
in
let ret_id = Ident.create_fresh Ident.knormal in
let call_instr =
Sil.Call
( (ret_id, Typ.void)
, Const (Cfun field_class_dealloc_name)
, [(Var id_field, field_typ)]
, location
, CallFlags.default )
in
instrs @ [load_pvar_instr; load_field_instr; call_instr]
| _ ->
instrs


let process_dealloc proc_desc fields self =
let exit_node = Procdesc.get_exit_node proc_desc in
let location = Procdesc.Node.get_last_loc exit_node in
let fields_dealloc_call_instrs =
List.fold ~f:(get_dealloc_call_field self location) ~init:[] fields
in
let exit_pred_nodes = Procdesc.Node.get_preds exit_node in
let node_name = Procdesc.Node.Call CFrontend_config.dealloc in
let node_kind = Procdesc.Node.Stmt_node node_name in
let dealloc_calls_node =
Procdesc.create_node proc_desc location node_kind fields_dealloc_call_instrs
in
Procdesc.node_set_succs proc_desc dealloc_calls_node ~normal:[exit_node] ~exn:[] ;
List.iter
~f:(fun node -> Procdesc.node_set_succs proc_desc node ~normal:[dealloc_calls_node] ~exn:[])
exit_pred_nodes


let process_procdesc tenv proc_name proc_desc =
let get_struct_procname tenv proc_name =
match Procname.get_class_type_name proc_name with
| Some name ->
Tenv.lookup tenv name
| None ->
None
in
if Procdesc.is_defined proc_desc && Procname.is_objc_dealloc proc_name then
let struct_opt = get_struct_procname tenv proc_name in
match struct_opt with
| Some {fields} -> (
let formals = Procdesc.get_formals proc_desc in
let self = List.find ~f:(fun (var, _) -> Mangled.equal var Mangled.self) formals in
match self with
| Some (self, typ) ->
let self_var = Pvar.mk self proc_name in
process_dealloc proc_desc fields (self_var, typ)
| None ->
() )
| _ ->
()
else ()


let process cfg tenv = Procname.Hash.iter (process_procdesc tenv) cfg
18 changes: 18 additions & 0 deletions infer/src/clang/CAddImplicitDeallocImpl.mli
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
(*
* 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

val process : Cfg.t -> Tenv.t -> unit
(** This models ARC implementation of dealloc, see
https://clang.llvm.org/docs/AutomaticReferenceCounting.html#dealloc. Dealloc methods can be
added to ObjC classes to free C memory for example, but the deallocation of the ObjC instance
variables of the object is done automatically. So here we add this explicitely to Infer: we add
calls to dealloc of the ObjC instance variables. Here we assume that every ObjC class has
already a dealloc method, because if it doesn't exist we add an empty method in
CFrontend_decl.create_and_process_dealloc_objc_impl TODO(T68411500): add calls to dealloc of the
superclass. *)
2 changes: 2 additions & 0 deletions infer/src/clang/ast_expressions.mli
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ val create_pointer_qual_type : ?quals:Typ.type_quals -> qual_type -> qual_type

val create_reference_qual_type : ?quals:Typ.type_quals -> qual_type -> qual_type

val create_void_type : qual_type

val create_char_star_type : ?quals:Typ.type_quals -> unit -> qual_type

val make_next_object_exp :
Expand Down
1 change: 1 addition & 0 deletions infer/src/clang/cFrontend.ml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ let do_source_file (translation_unit_context : CFrontend_config.translation_unit
L.(debug Capture Verbose)
"@\n Start building call/cfg graph for '%a'....@\n" SourceFile.pp source_file ;
let cfg = compute_icfg translation_unit_context tenv ast in
CAddImplicitDeallocImpl.process cfg tenv ;
L.(debug Capture Verbose) "@\n End building call/cfg graph for '%a'.@\n" SourceFile.pp source_file ;
NullabilityPreanalysis.analysis cfg tenv ;
SourceFiles.add source_file cfg (Tenv.FileLocal tenv) (Some integer_type_widths) ;
Expand Down
2 changes: 2 additions & 0 deletions infer/src/clang/cFrontend_config.ml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ type decl_trans_context = [`DeclTraversal | `Translation]

let alloc = "alloc"

let dealloc = "dealloc"

let assert_fail = "__assert_fail"

let assert_rtn = "__assert_rtn"
Expand Down
2 changes: 2 additions & 0 deletions infer/src/clang/cFrontend_config.mli
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ type decl_trans_context = [`DeclTraversal | `Translation]

val alloc : string

val dealloc : string

val assert_fail : string

val assert_rtn : string
Expand Down
56 changes: 54 additions & 2 deletions infer/src/clang/cFrontend_decl.ml
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,56 @@ module CFrontend_decl_funct (T : CModule_type.CTranslation) : CModule_type.CFron
List.iter ~f:(process_one_method_decl trans_unit_ctx tenv cfg curr_class) decl_list


(* Here we add an empty dealloc method to every ObjC class if it doesn't have one. Then the implicit
implementation of such method will be added in CAddImplicitDeallocImpl.process *)
let create_and_process_dealloc_objc_impl trans_unit_ctx tenv cfg curr_class objc_class_decl_info
decl_list =
let open Clang_ast_t in
let found_dealloc =
List.exists
~f:(fun decl ->
match decl with
| ObjCMethodDecl (_, name_info, mdi) ->
String.equal name_info.ni_name "dealloc" && mdi.Clang_ast_t.omdi_is_instance_method
| _ ->
false )
decl_list
in
if not found_dealloc then
let name_info =
{ni_name= CFrontend_config.dealloc; ni_qual_name= [CFrontend_config.dealloc]}
in
let decl_info =
{ di_pointer= CAst_utils.get_fresh_pointer ()
; di_parent_pointer= Some objc_class_decl_info.Clang_ast_t.di_pointer
; di_source_range= objc_class_decl_info.Clang_ast_t.di_source_range
; di_owning_module= objc_class_decl_info.Clang_ast_t.di_owning_module
; di_is_hidden= true
; di_is_implicit= true
; di_is_used= true
; di_is_this_declaration_referenced= false
; di_is_invalid_decl= false
; di_attributes= []
; di_full_comment= None
; di_access= `None }
in
let obj_c_method_decl_info =
{ omdi_is_instance_method= true
; omdi_result_type= Ast_expressions.create_void_type
; omdi_is_property_accessor= false
; omdi_property_decl= None
; omdi_parameters= []
; omdi_implicit_parameters= []
; omdi_is_variadic= false
; omdi_is_overriding= true
; omdi_is_optional= false
; omdi_body= Some (Clang_ast_t.CompoundStmt (CAst_utils.dummy_stmt_info (), []))
; omdi_mangled_name= CFrontend_config.dealloc }
in
let method_decl = ObjCMethodDecl (decl_info, name_info, obj_c_method_decl_info) in
process_method_decl trans_unit_ctx tenv cfg curr_class method_decl


(** Given REVERSED list of method qualifiers (method_name::class_name::rest_quals), return whether
method should be translated based on method and class whitelists *)
let is_whitelisted_cpp_method =
Expand Down Expand Up @@ -332,13 +382,15 @@ module CFrontend_decl_funct (T : CModule_type.CTranslation) : CModule_type.CFron
(ObjcCategory_decl.category_impl_decl CType_decl.qual_type_to_sil_type
CType_decl.CProcname.from_decl tenv dec) ;
process_methods trans_unit_ctx tenv cfg curr_class decl_list
| ObjCImplementationDecl (_, _, decl_list, _, _) ->
| ObjCImplementationDecl (objc_class_decl_info, _, decl_list, _, _) ->
let curr_class = CContext.ContextClsDeclPtr dec_ptr in
let qual_type_to_sil_type = CType_decl.qual_type_to_sil_type in
ignore
(ObjcInterface_decl.interface_impl_declaration qual_type_to_sil_type
CType_decl.CProcname.from_decl tenv dec) ;
process_methods trans_unit_ctx tenv cfg curr_class decl_list
process_methods trans_unit_ctx tenv cfg curr_class decl_list ;
create_and_process_dealloc_objc_impl trans_unit_ctx tenv cfg curr_class
objc_class_decl_info decl_list
| CXXMethodDecl (decl_info, _, _, _, _)
| CXXConstructorDecl (decl_info, _, _, _, _)
| CXXConversionDecl (decl_info, _, _, _, _)
Expand Down
6 changes: 3 additions & 3 deletions infer/tests/codetoanalyze/objc/biabduction/issues.exp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ codetoanalyze/objc/shared/block/BlockVar.m, BlockVar.blockPostBad, 5, NULL_DEREF
codetoanalyze/objc/shared/block/BlockVar.m, BlockVar.capturedNullDeref, 5, NULL_DEREFERENCE, no_bucket, ERROR, [start of procedure capturedNullDeref,start of procedure block]
codetoanalyze/objc/shared/block/BlockVar.m, BlockVar.navigateToURLInBackground, 8, NULL_DEREFERENCE, B1, ERROR, [start of procedure navigateToURLInBackground,start of procedure block,start of procedure test,return from a call to BlockVar.test,return from a call to objc_blockBlockVar.navigateToURLInBackground_1,Taking true branch]
codetoanalyze/objc/shared/block/block.m, main1, 30, DIVIDE_BY_ZERO, no_bucket, ERROR, [start of procedure main1(),start of procedure block,start of procedure block,return from a call to objc_blockobjc_blockmain1_2_3,return from a call to objc_blockmain1_2,start of procedure block,return from a call to objc_blockmain1_1]
codetoanalyze/objc/shared/block/block_no_args.m, My_manager.m, 10, NULL_DEREFERENCE, B1, ERROR, [start of procedure m,start of procedure block,return from a call to objc_blockMy_manager.m_1,Taking true branch]
codetoanalyze/objc/shared/block/block_no_args.m, Block_no_args.m, 10, NULL_DEREFERENCE, B1, ERROR, [start of procedure m,start of procedure block,return from a call to objc_blockBlock_no_args.m_1,Taking true branch]
codetoanalyze/objc/shared/category_procdesc/main.c, CategoryProcdescMain, 3, MEMORY_LEAK, no_bucket, ERROR, [start of procedure CategoryProcdescMain(),Skipping performDaysWork: method has no implementation]
codetoanalyze/objc/shared/field_superclass/SuperExample.m, ASuper.init, 2, NULL_DEREFERENCE, B2, ERROR, [start of procedure init]
codetoanalyze/objc/biabduction/blocks_in_heap/BlockInHeap.m, block_in_heap_executed_after_bi_abduction_ok_test, 3, NULL_DEREFERENCE, B1, ERROR, [start of procedure block_in_heap_executed_after_bi_abduction_ok_test(),start of procedure block_in_heap_executed_after_bi_abduction_ok_no_retain_cycle(),start of procedure assign_block_to_ivar,Executing synthesized setter setHandler:,return from a call to BlockInHeap.assign_block_to_ivar,Executing synthesized getter handler,start of procedure block,return from a call to objc_blockBlockInHeap.assign_block_to_ivar_1,return from a call to block_in_heap_executed_after_bi_abduction_ok_no_retain_cycle,Taking true branch]
Expand Down Expand Up @@ -107,8 +107,8 @@ codetoanalyze/objc/biabduction/property/main.c, property_main, 3, MEMORY_LEAK, n
codetoanalyze/objc/biabduction/resource_leaks/Dispatch_sources.m, ProcessContentsOfFile, 35, RESOURCE_LEAK, no_bucket, ERROR, [start of procedure ProcessContentsOfFile(),Taking false branch,Skipping dispatch_get_global_queue(): method has no implementation,Skipping dispatch_source_create(): method has no implementation,Taking false branch,Skipping dispatch_source_set_event_handler(): method has no implementation,Skipping dispatch_source_set_cancel_handler(): method has no implementation]
codetoanalyze/objc/biabduction/resource_leaks/Dispatch_sources.m, objc_blockProcessContentsOfFile_2, 6, MEMORY_LEAK, no_bucket, ERROR, [start of procedure block,Skipping dispatch_source_get_data(): method has no implementation,Taking true branch,Skipping MyProcessFileData(): method has no implementation]
codetoanalyze/objc/biabduction/resource_leaks/ResourceLeakExample.m, NSFileHandle.fileHandleForLoggingAtPath:mode:, 9, RESOURCE_LEAK, no_bucket, ERROR, [start of procedure fileHandleForLoggingAtPath:mode:,Taking true branch,Skipping fileSystemRepresentation: method has no implementation,Taking false branch,Taking true branch,Skipping autorelease: no implementation found for method declared in Objective-C protocol]
codetoanalyze/objc/shared/annotations/nonnull_annotations.m, A.test1:, 2, PARAMETER_NOT_NULL_CHECKED, B2, WARNING, [start of procedure test1:,Message child with receiver nil returns nil.]
codetoanalyze/objc/shared/annotations/nonnull_annotations.m, A.test3:, 1, PARAMETER_NOT_NULL_CHECKED, B1, WARNING, [start of procedure test3:]
codetoanalyze/objc/shared/annotations/nonnull_annotations.m, NonnullAnnot.test1:, 2, PARAMETER_NOT_NULL_CHECKED, B2, WARNING, [start of procedure test1:,Message child with receiver nil returns nil.]
codetoanalyze/objc/shared/annotations/nonnull_annotations.m, NonnullAnnot.test3:, 1, PARAMETER_NOT_NULL_CHECKED, B1, WARNING, [start of procedure test3:]
codetoanalyze/objc/shared/annotations/nullable_annotations.m, User.otherUserName, 2, NULL_DEREFERENCE, B2, ERROR, [start of procedure otherUserName,Skipping otherUser: method has no implementation]
codetoanalyze/objc/shared/annotations/nullable_annotations.m, npe_property_nullable, 3, NULL_DEREFERENCE, B1, ERROR, [start of procedure npe_property_nullable(),Skipping child: method has no implementation]
codetoanalyze/objc/shared/annotations/nullable_annotations_fields.m, A.nullable_field, 3, NULL_DEREFERENCE, B1, ERROR, [start of procedure nullable_field,Skipping getA(): method has no implementation]
Expand Down
22 changes: 22 additions & 0 deletions infer/tests/codetoanalyze/objc/frontend/block/retain_cycle.m.dot
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,28 @@ digraph cfg {


"capture#A#instance.d411336575e4bf632a1828f5f5979726_4" -> "capture#A#instance.d411336575e4bf632a1828f5f5979726_3" ;
"dealloc#A#instance.55ac864e91dcd5d484e8ab7d8eb94fcb_1" [label="1: Start A.dealloc\nFormals: self:A*\nLocals: \n " color=yellow style=filled]


"dealloc#A#instance.55ac864e91dcd5d484e8ab7d8eb94fcb_1" -> "dealloc#A#instance.55ac864e91dcd5d484e8ab7d8eb94fcb_3" ;
"dealloc#A#instance.55ac864e91dcd5d484e8ab7d8eb94fcb_2" [label="2: Exit A.dealloc \n " color=yellow style=filled]


"dealloc#A#instance.55ac864e91dcd5d484e8ab7d8eb94fcb_3" [label="3: Call dealloc \n n$0=*&self:A* [line 50, column 1]\n n$1=*n$0._data:D* [line 50, column 1]\n n$2=_fun_D.dealloc(n$1:D*) [line 50, column 1]\n n$3=*&self:A* [line 50, column 1]\n n$4=*n$3._b:B* [line 50, column 1]\n n$5=_fun_B.dealloc(n$4:B*) [line 50, column 1]\n " shape="box"]


"dealloc#A#instance.55ac864e91dcd5d484e8ab7d8eb94fcb_3" -> "dealloc#A#instance.55ac864e91dcd5d484e8ab7d8eb94fcb_2" ;
"dealloc#B#instance.8757740e0d47129962d40fbccbdf4d3f_1" [label="1: Start B.dealloc\nFormals: self:B*\nLocals: \n " color=yellow style=filled]


"dealloc#B#instance.8757740e0d47129962d40fbccbdf4d3f_1" -> "dealloc#B#instance.8757740e0d47129962d40fbccbdf4d3f_3" ;
"dealloc#B#instance.8757740e0d47129962d40fbccbdf4d3f_2" [label="2: Exit B.dealloc \n " color=yellow style=filled]


"dealloc#B#instance.8757740e0d47129962d40fbccbdf4d3f_3" [label="3: Call dealloc \n n$6=*&self:B* [line 31, column 1]\n n$7=*n$6._d:D* [line 31, column 1]\n n$8=_fun_D.dealloc(n$7:D*) [line 31, column 1]\n " shape="box"]


"dealloc#B#instance.8757740e0d47129962d40fbccbdf4d3f_3" -> "dealloc#B#instance.8757740e0d47129962d40fbccbdf4d3f_2" ;
"sHandler:#B#instance.590685250eb38eaab242405cd45c572b_1" [label="1: Start B.sHandler:\nFormals: self:B* h:_fn_(*)\nLocals: \n " color=yellow style=filled]


Expand Down
11 changes: 11 additions & 0 deletions infer/tests/codetoanalyze/objc/frontend/block/static.m.dot
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,15 @@ digraph cfg {


"test_leak#A#class.8240788aa53244827857be0e92d27671_3" -> "test_leak#A#class.8240788aa53244827857be0e92d27671_2" ;
"dealloc#A#instance.55ac864e91dcd5d484e8ab7d8eb94fcb_1" [label="1: Start A.dealloc\nFormals: self:A*\nLocals: \n " color=yellow style=filled]


"dealloc#A#instance.55ac864e91dcd5d484e8ab7d8eb94fcb_1" -> "dealloc#A#instance.55ac864e91dcd5d484e8ab7d8eb94fcb_3" ;
"dealloc#A#instance.55ac864e91dcd5d484e8ab7d8eb94fcb_2" [label="2: Exit A.dealloc \n " color=yellow style=filled]


"dealloc#A#instance.55ac864e91dcd5d484e8ab7d8eb94fcb_3" [label="3: Call dealloc \n " shape="box"]


"dealloc#A#instance.55ac864e91dcd5d484e8ab7d8eb94fcb_3" -> "dealloc#A#instance.55ac864e91dcd5d484e8ab7d8eb94fcb_2" ;
}
11 changes: 11 additions & 0 deletions infer/tests/codetoanalyze/objc/frontend/boxing/Boxing.m.dot
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
/* @generated */
digraph cfg {
"dealloc#Boxing#instance.569d9054e725a069b00d4e2aa3ade22c_1" [label="1: Start Boxing.dealloc\nFormals: self:Boxing*\nLocals: \n " color=yellow style=filled]


"dealloc#Boxing#instance.569d9054e725a069b00d4e2aa3ade22c_1" -> "dealloc#Boxing#instance.569d9054e725a069b00d4e2aa3ade22c_3" ;
"dealloc#Boxing#instance.569d9054e725a069b00d4e2aa3ade22c_2" [label="2: Exit Boxing.dealloc \n " color=yellow style=filled]


"dealloc#Boxing#instance.569d9054e725a069b00d4e2aa3ade22c_3" [label="3: Call dealloc \n " shape="box"]


"dealloc#Boxing#instance.569d9054e725a069b00d4e2aa3ade22c_3" -> "dealloc#Boxing#instance.569d9054e725a069b00d4e2aa3ade22c_2" ;
"getBool#Boxing#instance.3315ec58788820860ec4adc889dd7197_1" [label="1: Start Boxing.getBool\nFormals: self:Boxing*\nLocals: n:NSNumber* \n " color=yellow style=filled]


Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
/* @generated */
digraph cfg {
"dealloc#A#instance.55ac864e91dcd5d484e8ab7d8eb94fcb_1" [label="1: Start A.dealloc\nFormals: self:A*\nLocals: \n " color=yellow style=filled]


"dealloc#A#instance.55ac864e91dcd5d484e8ab7d8eb94fcb_1" -> "dealloc#A#instance.55ac864e91dcd5d484e8ab7d8eb94fcb_3" ;
"dealloc#A#instance.55ac864e91dcd5d484e8ab7d8eb94fcb_2" [label="2: Exit A.dealloc \n " color=yellow style=filled]


"dealloc#A#instance.55ac864e91dcd5d484e8ab7d8eb94fcb_3" [label="3: Call dealloc \n " shape="box"]


"dealloc#A#instance.55ac864e91dcd5d484e8ab7d8eb94fcb_3" -> "dealloc#A#instance.55ac864e91dcd5d484e8ab7d8eb94fcb_2" ;
"test4:#A#instance.718a300d6fa63609a70f22221a548ee5_1" [label="1: Start A.test4:\nFormals: self:A* x:int\nLocals: \n " color=yellow style=filled]


Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
/* @generated */
digraph cfg {
"dealloc#ExceptionExample#instance.d3f98dfd383bac562b4173d739efcd78_1" [label="1: Start ExceptionExample.dealloc\nFormals: self:ExceptionExample*\nLocals: \n " color=yellow style=filled]


"dealloc#ExceptionExample#instance.d3f98dfd383bac562b4173d739efcd78_1" -> "dealloc#ExceptionExample#instance.d3f98dfd383bac562b4173d739efcd78_3" ;
"dealloc#ExceptionExample#instance.d3f98dfd383bac562b4173d739efcd78_2" [label="2: Exit ExceptionExample.dealloc \n " color=yellow style=filled]


"dealloc#ExceptionExample#instance.d3f98dfd383bac562b4173d739efcd78_3" [label="3: Call dealloc \n " shape="box"]


"dealloc#ExceptionExample#instance.d3f98dfd383bac562b4173d739efcd78_3" -> "dealloc#ExceptionExample#instance.d3f98dfd383bac562b4173d739efcd78_2" ;
"test#ExceptionExample#instance.513cde8d794322493646dbd1821516dd_1" [label="1: Start ExceptionExample.test\nFormals: self:ExceptionExample*\nLocals: s:NSString* \n " color=yellow style=filled]


Expand Down
Loading

0 comments on commit 61d5fde

Please sign in to comment.