From 91ae1baebc14fa71ad139fad904d4b1c45e118aa Mon Sep 17 00:00:00 2001 From: Sam Blackshear Date: Fri, 18 Mar 2016 16:41:03 -0700 Subject: [PATCH] massive refactoring of harness generation Reviewed By: jeremydubreil Differential Revision: D3059627 fb-gh-sync-id: d3c3f18 shipit-source-id: d3c3f18 --- infer/src/backend/interproc.ml | 5 +- infer/src/harness/harness.ml | 165 +----------------- infer/src/harness/harness.mli | 5 +- infer/src/harness/inhabit.ml | 152 +++++----------- infer/src/harness/inhabit.mli | 16 +- infer/src/java/jMain.ml | 51 +++--- .../java/harness/CallbackActivity.java | 41 ----- .../java/harness/FindViewByIdActivity.java | 44 ----- .../java/harness/SubclassActivity.java | 19 -- .../java/harness/SuperclassActivity.java | 27 --- .../endtoend/java/harness/CallbackTest.java | 61 ------- .../java/harness/FindViewByIdTest.java | 67 ------- .../endtoend/java/harness/SuperclassTest.java | 67 ------- 13 files changed, 81 insertions(+), 639 deletions(-) delete mode 100644 infer/tests/codetoanalyze/java/harness/CallbackActivity.java delete mode 100644 infer/tests/codetoanalyze/java/harness/FindViewByIdActivity.java delete mode 100644 infer/tests/codetoanalyze/java/harness/SubclassActivity.java delete mode 100644 infer/tests/codetoanalyze/java/harness/SuperclassActivity.java delete mode 100644 infer/tests/endtoend/java/harness/CallbackTest.java delete mode 100644 infer/tests/endtoend/java/harness/FindViewByIdTest.java delete mode 100644 infer/tests/endtoend/java/harness/SuperclassTest.java diff --git a/infer/src/backend/interproc.ml b/infer/src/backend/interproc.ml index 10dccb974..2505c2ff7 100644 --- a/infer/src/backend/interproc.ml +++ b/infer/src/backend/interproc.ml @@ -647,10 +647,7 @@ let report_context_leaks pname sigma tenv = when Sil.pvar_is_global pv -> IList.iter (fun (f_name, f_strexp) -> - if not (Harness.is_generated_field f_name) - then - check_reachable_context_from_fld - (f_name, f_strexp) context_exps) + check_reachable_context_from_fld (f_name, f_strexp) context_exps) static_flds | _ -> ()) sigma diff --git a/infer/src/harness/harness.ml b/infer/src/harness/harness.ml index 10ee694cc..560889e99 100644 --- a/infer/src/harness/harness.ml +++ b/infer/src/harness/harness.ml @@ -12,112 +12,6 @@ module F = Format (** Automatically create a harness method to exercise code under test *) -(** given a list [lst] = fst @ (e :: rest), a test predicate [test], and a list [to_insert], returns - the list fst @ (e :: to_insert) @ rest, where e is the first element such that test(e) evaluates - to true. if test(e) does not evaluate to true for any element of the list, returns [lst]. *) -let insert_after lst test to_insert = - let rec insert_rec to_process processed = match to_process with - | instr :: to_process -> - let processed' = instr :: processed in - if test instr then - IList.append (IList.rev processed') (IList.append to_insert to_process) - else - insert_rec to_process processed' - | [] -> lst in - insert_rec lst [] - -(* clear indicator of artificiality, as any real field must have a non-negative offset *) -let generated_field_offset = -1 - -(** Return true if [fieldname] was created by the harness generation *) -let is_generated_field fieldname = - Ident.fieldname_offset fieldname = generated_field_offset - -(** find callees that register callbacks and add instrumentation to extract the callback. - return the set of new global static fields created to extract callbacks and their types *) -let extract_callbacks procdesc cfg_file cfg tenv harness_lvar callback_fields = - (* try to turn a nasty callback name like MyActivity$1 into a nice callback name like - * Button.OnClickListener[line 7]*) - let create_descriptive_callback_name callback_typ loc = - let typ_str = match PatternMatch.type_get_class_name callback_typ with - | Some mangled -> Mangled.get_mangled mangled - | None -> Sil.typ_to_string callback_typ in - let pretty_typ_str = - if Procname.is_anonymous_inner_class_name typ_str then - match PatternMatch.type_get_direct_supertypes callback_typ with - | [] -> - (* this should never happen since an inner class always has a supertype *) - assert false - | l -> - (* choose to describe this anonymous inner class with one of the interfaces that it - * implements. translation always places interfaces at the end of the supertypes list *) - Typename.name (IList.hd (IList.rev l)) - else typ_str in - Mangled.from_string (pretty_typ_str ^ "[line " ^ Location.to_string loc ^ "]") in - let create_instrumentation_fields created_flds node instr = match instr with - | Sil.Call ([], Sil.Const (Sil.Cfun callee), args, loc, _) - when Procname.is_java callee -> - let callee_java = match callee with - | Procname.Java callee_java -> callee_java - | _ -> assert false in - begin - match AndroidFramework.get_callback_registered_by callee_java args tenv with - | Some (cb_obj, (Sil.Tptr (cb_typ, Sil.Pk_pointer) as ptr_to_cb_typ)) -> - let callback_fld_name = create_descriptive_callback_name ptr_to_cb_typ loc in - let created_fld = Ident.create_fieldname callback_fld_name generated_field_offset in - (* create a function that takes the type of the harness class as an argument and modifies - * the instruction set with callback extraction code. we do this because we need to know - * the typ of the harness class before we can write to any of its fields, but we cannot - * actually create this typ until we know how many fields we are going to create in order - * to extract callbacks *) - let mk_field_write harness_class_typ = - (* create an instruction that writes the registered callback object to a global static - * field in the harness class *) - let fld_write_lhs = Sil.Lfield (harness_lvar, created_fld, harness_class_typ) in - let extract_cb_instr = Sil.Set (fld_write_lhs, cb_typ, cb_obj, loc) in - let instrumented_instrs = - let node_instrs = Cfg.Node.get_instrs node in - insert_after node_instrs (fun e -> e == instr) [extract_cb_instr] in - Cfg.Node.replace_instrs node instrumented_instrs; - (cfg_file, cfg) in - (created_fld, ptr_to_cb_typ, mk_field_write) :: created_flds - | _ -> created_flds - end - | _ -> - created_flds in - Cfg.Procdesc.fold_instrs create_instrumentation_fields callback_fields procdesc - -(** find all of the callbacks registered by methods in [lifecycle_trace *) -let find_registered_callbacks lifecycle_trace harness_name proc_file_map tenv = - (* what would be ideal to do here is to go through every method (transitively) called by a - * lifecycle method and look for registered callbacks, however, this would need to be a complex - * iterative process, as detecting callbacks can lead to more methods being called, which in - * turn can lead to more callbacks being registered. so what we do here is iterate through each - * file that a lifecycle proc is defined in and collect all callbacks possibly registered in - * methods in that file. this can err on the side of including too many callbacks (for example, if - * a callback is registered in a superclass method that is overridden, this scheme would - * wrongly include it). on the other hand, this will miss callbacks registered in - * callees of lifecycle methods that aren't in our list of "lifecycle methods files" *) - (* TODO (t4793988): do something more principled here *) - let harness_lvar = Sil.Lvar (Sil.mk_pvar_global harness_name) in - let lifecycle_cfg_files = - IList.fold_left (fun lifecycle_files (lifecycle_proc, _) -> - try - let cfg_fname = - let source_dir = Inhabit.source_dir_from_name lifecycle_proc proc_file_map in - DB.source_dir_get_internal_file source_dir ".cfg" in - DB.FilenameSet.add cfg_fname lifecycle_files - with Not_found -> lifecycle_files - ) DB.FilenameSet.empty lifecycle_trace in - DB.FilenameSet.fold (fun cfg_file registered_callbacks -> - match Cfg.load_cfg_from_file cfg_file with - | Some cfg -> - IList.fold_left (fun registered_callbacks procdesc -> - extract_callbacks procdesc cfg_file cfg tenv harness_lvar registered_callbacks - ) registered_callbacks (Cfg.get_all_procs cfg) - | None -> registered_callbacks - ) lifecycle_cfg_files [] - (** if [typ] is a lifecycle type, generate a list of (method call, receiver) pairs constituting a lifecycle trace *) let try_create_lifecycle_trace typ lifecycle_typ lifecycle_procs tenv = match typ with @@ -136,60 +30,21 @@ let try_create_lifecycle_trace typ lifecycle_typ lifecycle_procs tenv = match ty else [] | _ -> [] -(** get all the callbacks registered in [lifecycle_trace], transform the SIL to "extract" them into - global static fields belong to the harness so that they are easily callable, and return a list - of the (field, typ) pairs that we have created for this purpose *) -let extract_callbacks lifecycle_trace harness_procname proc_file_map tenv = - let harness_name = - Mangled.from_string (Procname.to_string (Procname.Java harness_procname)) in - let registered_cbs = - find_registered_callbacks lifecycle_trace harness_name proc_file_map tenv in - let fields = IList.map (fun (fld, typ, _) -> (fld, typ, [])) registered_cbs in - (* create a new typ for the harness containing all of the cb extraction vars as static fields *) - let harness_struct_typ = - { - Sil.instance_fields = fields; - static_fields = []; - csu = Csu.Class Csu.Java; - struct_name = Some harness_name; - superclasses = []; - def_methods = [Procname.Java harness_procname]; - struct_annotations = []; - } in - let harness_typ = Sil.Tstruct harness_struct_typ in - (* update the tenv with our created harness typ. we don't have to save the tenv to disk here - * because this is done immediately after harness generation runs in jMain.ml *) - let harness_class = Typename.TN_csu (Csu.Class Csu.Java, harness_name) in - Sil.tenv_add tenv harness_class harness_struct_typ; - let cfgs_to_save = - IList.fold_left (fun cfgs_to_save (_, _, instrument_sil_f) -> - (* instrument the cfg's with callback extraction code *) - let (cfg_file, cfg) = instrument_sil_f harness_typ in - DB.FilenameMap.add cfg_file cfg cfgs_to_save - ) DB.FilenameMap.empty registered_cbs in - (* re-save the cfgs that we've modified by extracting callbacks *) - DB.FilenameMap.iter - (fun cfg_file cfg -> Cfg.store_cfg_to_file cfg_file false cfg) cfgs_to_save; - (* these are all the static fields holding callbacks that should be invoked by the harness *) - let harness_global = Sil.Lvar (Sil.mk_pvar_global harness_name) in - IList.map (fun (fld, typ, _) -> (Sil.Lfield (harness_global, fld, harness_typ), typ)) fields - -(** generate a harness for each lifecycle type in an Android application *) -let create_android_harness proc_file_map tenv = +(** generate a harness for a lifecycle type in an Android application *) +let create_harness cfg cg tenv = IList.iter (fun (pkg, clazz, lifecycle_methods) -> let typ_name = Mangled.from_package_class pkg clazz in match AndroidFramework.get_lifecycle_for_framework_typ_opt typ_name lifecycle_methods tenv with | Some (framework_typ, framework_procs) -> - (* iterate through the type environment and generate a lifecycle harness for each subclass of - * [lifecycle_typ] *) + (* iterate through the type environment and generate a lifecycle harness for each + subclass of [lifecycle_typ] *) + (* TODO: instead of iterating through the type environment, interate through the types + declared in [cfg] *) Sil.tenv_iter (fun _ struct_typ -> let typ = Sil.Tstruct struct_typ in match try_create_lifecycle_trace typ framework_typ framework_procs tenv with | [] -> () | lifecycle_trace -> - (* we have identified an application lifecycle type and created a trace for it. now, - * identify the callbacks registered by methods belonging to this type and get the - * inhabitation module to create a harness for us *) let harness_procname = let harness_cls_name = PatternMatch.get_type_name typ in let pname = @@ -200,13 +55,7 @@ let create_android_harness proc_file_map tenv = match pname with | Procname.Java harness_procname -> harness_procname | _ -> assert false in - let callback_fields = - extract_callbacks lifecycle_trace harness_procname proc_file_map tenv in - Inhabit.inhabit_trace - lifecycle_trace callback_fields harness_procname proc_file_map + Inhabit.inhabit_trace lifecycle_trace harness_procname cg cfg ) tenv | None -> () ) AndroidFramework.get_lifecycles - -(** Generate a harness method for exe_env and add it to the execution environment *) -let create_harness proc_file_map tenv = create_android_harness proc_file_map tenv diff --git a/infer/src/harness/harness.mli b/infer/src/harness/harness.mli index cecd5c82e..b67d175cd 100644 --- a/infer/src/harness/harness.mli +++ b/infer/src/harness/harness.mli @@ -9,8 +9,5 @@ (** Automatically create a harness method to exercise code under test *) -(** Return true if [fieldname] was created by the harness generation *) -val is_generated_field : Ident.fieldname -> bool - (** Generate a harness method for exe_env and add it to the execution environment *) -val create_harness : DB.source_file Procname.Map.t -> Sil.tenv -> unit +val create_harness : Cfg.cfg -> Cg.t -> Sil.tenv -> unit diff --git a/infer/src/harness/inhabit.ml b/infer/src/harness/inhabit.ml index 66e3aad3c..1605c9f8d 100644 --- a/infer/src/harness/inhabit.ml +++ b/infer/src/harness/inhabit.ml @@ -18,7 +18,6 @@ module TypSet = Sil.TypSet module TypMap = Sil.TypMap type lifecycle_trace = (Procname.t * Sil.typ option) list -type callback_trace = (Sil.exp * Sil.typ) list (** list of instrs and temporary variables created during inhabitation and a cache of types that * have already been inhabited *) @@ -30,6 +29,20 @@ type env = { instrs : Sil.instr list; pc : Location.t; harness_name : Procname.java } +let procdesc_from_name cfg pname = + let pdesc_ref = ref None in + Cfg.iter_proc_desc cfg + (fun cfg_pname pdesc -> + if Procname.equal cfg_pname pname then + pdesc_ref := Some pdesc + ); + !pdesc_ref + +let formals_from_name cfg pname = + match procdesc_from_name cfg pname with + | Some pdesc -> Cfg.Procdesc.get_formals pdesc + | None -> [] + (** add an instruction to the env, update tmp_vars, and bump the pc *) let env_add_instr instr tmp_vars env = let incr_pc pc = { pc with Location.line = pc.Location.line + 1 } in @@ -40,33 +53,6 @@ let cf_alloc = Sil.cf_default let fun_exp_from_name proc_name = Sil.Const (Sil.Cfun (proc_name)) -let source_dir_from_name proc_name proc_file_map = - let source_file = Procname.Map.find proc_name proc_file_map in - DB.source_dir_from_source_file source_file - -let cfg_from_name proc_name proc_file_map = - let source_dir = source_dir_from_name proc_name proc_file_map in - let cfg_fname = DB.source_dir_get_internal_file source_dir ".cfg" in - match Cfg.load_cfg_from_file cfg_fname with - | Some cfg -> cfg - | None -> assert false - -let cg_from_name proc_name proc_file_map = - let source_dir = source_dir_from_name proc_name proc_file_map in - let cg_fname = DB.source_dir_get_internal_file source_dir ".cg" in - match Cg.load_from_file cg_fname with - | Some cfg -> cfg - | None -> assert false - -let procdesc_from_name proc_name proc_file_map = - match Cfg.Procdesc.find_from_name (cfg_from_name proc_name proc_file_map) proc_name with - | Some proc_desc -> proc_desc - | None -> assert false - -let formals_from_name proc_name proc_file_map = - let procdesc = procdesc_from_name proc_name proc_file_map in - Cfg.Procdesc.get_formals procdesc - let local_name_cntr = ref 0 let create_fresh_local_name () = @@ -92,7 +78,8 @@ let inhabit_alloc sizeof_typ ret_typ alloc_kind env = (inhabited_exp, env_add_instr call_instr [retval] env) (** find or create a Sil expression with type typ *) -let rec inhabit_typ typ proc_file_map env = +(* TODO: this should be done in a differnt way: just make typ a param of the harness procedure *) +let rec inhabit_typ typ cfg env = try (TypMap.find typ env.cache, env) with Not_found -> let inhabit_internal typ env = match typ with @@ -110,8 +97,7 @@ let rec inhabit_typ typ proc_file_map env = | Sil.Tstruct { Sil.csu = Csu.Class _; def_methods } -> let is_suitable_constructor p = let try_get_non_receiver_formals p = - try get_non_receiver_formals (formals_from_name p proc_file_map) - with Not_found -> [] in + get_non_receiver_formals (formals_from_name cfg p) in Procname.is_constructor p && IList.for_all (fun (_, typ) -> not (TypSet.mem typ env.cur_inhabiting)) (try_get_non_receiver_formals p) in IList.filter (fun p -> is_suitable_constructor p) def_methods @@ -121,7 +107,7 @@ let rec inhabit_typ typ proc_file_map env = (* arbitrarily choose a constructor for typ and invoke it. eventually, we may want to * nondeterministically call all possible constructors instead *) let env = - inhabit_constructor constructor (allocated_obj_exp, ptr_to_typ) proc_file_map env in + inhabit_constructor constructor (allocated_obj_exp, ptr_to_typ) cfg env in (* try to get the unqualified name as a class (e.g., Object for java.lang.Object so we * we can use it as a descriptive local variable name in the harness *) let typ_class_name = @@ -154,21 +140,21 @@ let rec inhabit_typ typ proc_file_map env = cur_inhabiting = env.cur_inhabiting }) (** inhabit each of the types in the formals list *) -and inhabit_args formals proc_file_map env = +and inhabit_args formals cfg env = let inhabit_arg (_, formal_typ) (args, env) = - let (exp, env) = inhabit_typ formal_typ proc_file_map env in + let (exp, env) = inhabit_typ formal_typ cfg env in ((exp, formal_typ) :: args, env) in IList.fold_right inhabit_arg formals ([], env) (** create Sil that calls the constructor in constr_name on allocated_obj and inhabits the * remaining arguments *) -and inhabit_constructor constr_name (allocated_obj, obj_type) proc_file_map env = +and inhabit_constructor constr_name (allocated_obj, obj_type) cfg env = try (* this lookup can fail when we try to get the procdesc of a procedure from a different * module. this could be solved with a whole - program class hierarchy analysis *) let (args, env) = - let non_receiver_formals = tl_or_empty (formals_from_name constr_name proc_file_map) in - inhabit_args non_receiver_formals proc_file_map env in + let non_receiver_formals = tl_or_empty (formals_from_name cfg constr_name) in + inhabit_args non_receiver_formals cfg env in let constr_instr = let fun_exp = fun_exp_from_name constr_name in Sil.Call ([], fun_exp, (allocated_obj, obj_type) :: args, env.pc, Sil.cf_default) in @@ -186,47 +172,24 @@ let inhabit_call_with_args procname procdesc args env = env_add_instr call_instr retval env (** create Sil that inhabits args to and calls proc_name *) -let inhabit_call (procname, receiver) proc_file_map env = +let inhabit_call (procname, receiver) cfg env = try - let procdesc = procdesc_from_name procname proc_file_map in - (* swap the type of the 'this' formal with the receiver type, if there is one *) - let formals = match (Cfg.Procdesc.get_formals procdesc, receiver) with - | ((name, _) :: formals, Some receiver) -> (name, receiver) :: formals - | (formals, None) -> formals - | ([], Some _) -> - L.err - "Expected at least one formal to bind receiver to in method %a@." Procname.pp procname; - assert false in - let (args, env) = inhabit_args formals proc_file_map env in - inhabit_call_with_args procname procdesc args env + match procdesc_from_name cfg procname with + | Some procdesc -> + (* swap the type of the 'this' formal with the receiver type, if there is one *) + let formals = match (Cfg.Procdesc.get_formals procdesc, receiver) with + | ((name, _) :: formals, Some receiver) -> (name, receiver) :: formals + | (formals, None) -> formals + | ([], Some _) -> + L.err + "Expected at least one formal to bind receiver to in method %a@." + Procname.pp procname; + assert false in + let (args, env) = inhabit_args formals cfg env in + inhabit_call_with_args procname procdesc args env + | None -> env with Not_found -> env -let inhabit_fld_trace flds proc_file_map env = - let invoke_cb (fld_exp, fld_typ) env = - let lhs = Ident.create_fresh Ident.knormal in - let fld_read_instr = - Sil.Letderef (lhs, fld_exp, fld_typ, env.pc) in - let env = env_add_instr fld_read_instr [lhs] env in - match fld_typ with - | Sil.Tptr (Sil.Tstruct { Sil.csu = Csu.Class _; def_methods }, _) -> - let inhabit_cb_call procname env = - try - let procdesc = procdesc_from_name procname proc_file_map in - (* replace receiver arg with the value read from the field, inhabit other args *) - let (args, env) = - let formals = Cfg.Procdesc.get_formals procdesc in - inhabit_args (tl_or_empty formals) proc_file_map env in - inhabit_call_with_args procname procdesc ((Sil.Var lhs, fld_typ) :: args) env - with Not_found -> - (* TODO (t4645631): investigate why this failure occurs *) - env in - IList.fold_left (fun env procname -> - if not (Procname.is_constructor procname) && - not (Procname.java_is_access_method procname) then inhabit_cb_call procname env - else env) env def_methods - | _ -> assert false in - IList.fold_left (fun env fld -> invoke_cb fld env) env flds - (** create a dummy file for the harness and associate them in the exe_env *) let create_dummy_harness_file harness_name = let dummy_file_name = @@ -260,29 +223,23 @@ let add_harness_to_cg harness_name harness_node cg = (** create and fill the appropriate nodes and add them to the harness cfg. also add the harness * proc to the cg *) -let setup_harness_cfg harness_name env source_dir cg = +let setup_harness_cfg harness_name env cg cfg = (* each procedure has different scope: start names from id 0 *) Ident.NameGenerator.reset (); - - let cfg_file = DB.source_dir_get_internal_file source_dir ".cfg" in - let harness_cfg = match Cfg.load_cfg_from_file cfg_file with - | Some cfg -> cfg - | None -> assert false in - let cg_file = DB.source_dir_get_internal_file source_dir ".cg" in let procdesc = let proc_attributes = { (ProcAttributes.default (Procname.Java harness_name) Config.Java) with ProcAttributes.is_defined = true; loc = env.pc; } in - Cfg.Procdesc.create harness_cfg proc_attributes in + Cfg.Procdesc.create cfg proc_attributes in let harness_node = (* important to reverse the list or there will be scoping issues! *) let instrs = (IList.rev env.instrs) in let nodekind = Cfg.Node.Stmt_node "method_body" in - Cfg.Node.create harness_cfg env.pc nodekind instrs procdesc env.tmp_vars in + Cfg.Node.create cfg env.pc nodekind instrs procdesc env.tmp_vars in let (start_node, exit_node) = - let create_node kind = Cfg.Node.create harness_cfg env.pc kind [] procdesc [] in + let create_node kind = Cfg.Node.create cfg env.pc kind [] procdesc [] in let start_kind = Cfg.Node.Start_node procdesc in let exit_kind = Cfg.Node.Exit_node procdesc in (create_node start_kind, create_node exit_kind) in @@ -291,21 +248,13 @@ let setup_harness_cfg harness_name env source_dir cg = Cfg.Node.add_locals_ret_declaration start_node []; Cfg.Node.set_succs_exn start_node [harness_node] [exit_node]; Cfg.Node.set_succs_exn harness_node [exit_node] [exit_node]; - add_harness_to_cg harness_name harness_node cg; - (* save out the cg and cfg so that they will be accessible in the next phase of the analysis *) - Cg.store_to_file cg_file cg; - Cfg.store_cfg_to_file cfg_file false harness_cfg + add_harness_to_cg harness_name harness_node cg (** create a procedure named harness_name that calls each of the methods in trace in the specified * order with the specified receiver and add it to the execution environment *) -let inhabit_trace trace cb_flds harness_name proc_file_map = +let inhabit_trace trace harness_name cg cfg = if IList.length trace > 0 then - (* pick an arbitrary cg and cfg to piggyback the harness code onto *) - let (source_dir, source_file, cg) = - let (proc_name, source_file) = Procname.Map.choose proc_file_map in - let cg = cg_from_name proc_name proc_file_map in - (source_dir_from_name proc_name proc_file_map, source_file, cg) in - + let source_file = Cg.get_source cg in let harness_file = create_dummy_harness_file harness_name in let start_line = (Cg.get_nLOC cg) + 1 in let empty_env = @@ -316,14 +265,9 @@ let inhabit_trace trace cb_flds harness_name proc_file_map = pc = pc; cur_inhabiting = TypSet.empty; harness_name = harness_name; } in - (* synthesize the harness body *) - let env'' = - (* invoke lifecycle methods *) - let env' = - IList.fold_left (fun env to_call -> inhabit_call to_call proc_file_map env) empty_env trace in - (* invoke callbacks *) - inhabit_fld_trace cb_flds proc_file_map env' in + (* invoke lifecycle methods *) + let env'' = IList.fold_left (fun env to_call -> inhabit_call to_call cfg env) empty_env trace in try - setup_harness_cfg harness_name env'' source_dir cg; + setup_harness_cfg harness_name env'' cg cfg; write_harness_to_file (IList.rev env''.instrs) harness_file with Not_found -> () diff --git a/infer/src/harness/inhabit.mli b/infer/src/harness/inhabit.mli index fc8ab3d97..167c7f3d5 100644 --- a/infer/src/harness/inhabit.mli +++ b/infer/src/harness/inhabit.mli @@ -11,17 +11,7 @@ type lifecycle_trace = (Procname.t * Sil.typ option) list -type callback_trace = (Sil.exp * Sil.typ) list +(** create a procedure named harness_name that calls each of the methods in trace add it to the + cg/cfg *) +val inhabit_trace : lifecycle_trace -> Procname.java -> Cg.t -> Cfg.cfg -> unit -(** create a procedure named harness_name that calls each of the methods in trace in the specified - order with the specified receiver and add it to the execution environment *) -val inhabit_trace : lifecycle_trace -> callback_trace -> Procname.java -> - DB.source_file Procname.Map.t -> unit - -val source_dir_from_name : Procname.t -> DB.source_file Procname.Map.t -> DB.source_dir - -val cfg_from_name : Procname.t -> DB.source_file Procname.Map.t -> Cfg.cfg - -val cg_from_name : Procname.t -> DB.source_file Procname.Map.t -> Cg.t - -val procdesc_from_name : Procname.t -> DB.source_file Procname.Map.t -> Cfg.Procdesc.t diff --git a/infer/src/java/jMain.ml b/infer/src/java/jMain.ml index 9ee820dad..9e032ff61 100644 --- a/infer/src/java/jMain.ml +++ b/infer/src/java/jMain.ml @@ -103,6 +103,7 @@ let store_icfg tenv cg cfg program = begin let cfg_file = DB.source_dir_get_internal_file source_dir ".cfg" in let cg_file = DB.source_dir_get_internal_file source_dir ".cg" in + if !JConfig.create_harness then Harness.create_harness cfg cg tenv; Preanal.doit ~f_translate_typ:(Some f_translate_typ) cfg cg tenv; Cg.store_to_file cg_file cg; Cfg.store_cfg_to_file cfg_file true cfg; @@ -120,20 +121,14 @@ let store_icfg tenv cg cfg program = (* environment are obtained and saved. *) let do_source_file never_null_matcher linereader classes program tenv - source_basename (package_opt, source_file) proc_file_map = + source_basename (package_opt, source_file) = JUtils.log "\nfilename: %s (%s)@." (DB.source_file_to_string source_file) source_basename; let call_graph, cfg = JFrontend.compute_source_icfg never_null_matcher linereader classes program tenv source_basename package_opt in - store_icfg tenv call_graph cfg program; - if !JConfig.create_harness then - IList.fold_left - (fun proc_file_map pdesc -> - Procname.Map.add (Cfg.Procdesc.get_proc_name pdesc) source_file proc_file_map) - proc_file_map (Cfg.get_all_procs cfg) - else proc_file_map + store_icfg tenv call_graph cfg program let capture_libs never_null_matcher linereader program tenv = @@ -196,30 +191,26 @@ let do_all_files classpath sources classes = Inferconfig.SkipTranslationMatcher.load_matcher (Inferconfig.inferconfig ()) in let never_null_matcher = Inferconfig.NeverReturnNull.load_matcher (Inferconfig.inferconfig ()) in - let proc_file_map = - let skip source_file = - skip_translation_matcher source_file Procname.empty in - let translate_source_file basename (package_opt, _) source_file map = - init_global_state source_file; - if skip source_file then map - else do_source_file - never_null_matcher linereader classes program tenv - basename (package_opt, source_file) map in - StringMap.fold - (fun basename file_entry map -> - match file_entry with - | JClasspath.Singleton source_file -> - translate_source_file basename (None, source_file) source_file map - | JClasspath.Duplicate source_files -> - IList.fold_left - (fun accu (package, source_file) -> - translate_source_file basename (Some package, source_file) source_file accu) - map source_files) - sources - Procname.Map.empty in + let skip source_file = + skip_translation_matcher source_file Procname.empty in + let translate_source_file basename (package_opt, _) source_file = + init_global_state source_file; + if not (skip source_file) then + do_source_file + never_null_matcher linereader classes program tenv basename (package_opt, source_file) in + StringMap.iter + (fun basename file_entry -> + match file_entry with + | JClasspath.Singleton source_file -> + translate_source_file basename (None, source_file) source_file + | JClasspath.Duplicate source_files -> + IList.iter + (fun (package, source_file) -> + translate_source_file basename (Some package, source_file) source_file) + source_files) + sources; if !JConfig.dependency_mode then capture_libs never_null_matcher linereader program tenv; - if !JConfig.create_harness then Harness.create_harness proc_file_map tenv; save_tenv tenv; JClasspath.cleanup program; JUtils.log "done @." diff --git a/infer/tests/codetoanalyze/java/harness/CallbackActivity.java b/infer/tests/codetoanalyze/java/harness/CallbackActivity.java deleted file mode 100644 index 1f1c90ba7..000000000 --- a/infer/tests/codetoanalyze/java/harness/CallbackActivity.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2015 - 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. - */ - -package codetoanalyze.java.harness; - -import android.app.Activity; -import android.os.Bundle; -import android.view.View; -import android.widget.Button; - -/* - * Test if harness generation knows how to call a callback defined in an inner class - */ -public class CallbackActivity extends Activity { - - private Object mObj; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - Button btn = new Button(this.getApplicationContext()); - mObj = new Object(); - Button.OnClickListener listener = new Button.OnClickListener() { - - @Override - public void onClick(View v) { - // oops! what if I get nulled out later? - mObj.toString(); - } - }; - btn.setOnClickListener(listener); - mObj = null; - } - -} diff --git a/infer/tests/codetoanalyze/java/harness/FindViewByIdActivity.java b/infer/tests/codetoanalyze/java/harness/FindViewByIdActivity.java deleted file mode 100644 index 48e538228..000000000 --- a/infer/tests/codetoanalyze/java/harness/FindViewByIdActivity.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2015 - 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. - */ - -package codetoanalyze.java.harness; - -import android.app.Activity; -import android.os.Bundle; -import android.view.View; -import android.widget.Button; - - -public class FindViewByIdActivity extends Activity { - - private MyView mView; - - @Override - public void onCreate(Bundle b) { - mView = (MyView) findViewById(-1); - // replacing the above line with this reveals the bug - // mView = new MyView(this.getApplicationContext()); - Button btn = new Button(this.getApplicationContext()); - Button.OnClickListener listener = new Button.OnClickListener() { - - @Override - public void onClick(View v) { - // oops! what if I get nulled out later? - mView.toString(); - } - }; - btn.setOnClickListener(listener); - } - - - @Override - public void onDestroy() { - mView = null; - } -} diff --git a/infer/tests/codetoanalyze/java/harness/SubclassActivity.java b/infer/tests/codetoanalyze/java/harness/SubclassActivity.java deleted file mode 100644 index cc8228c09..000000000 --- a/infer/tests/codetoanalyze/java/harness/SubclassActivity.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) 2015 - 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. - */ - -package codetoanalyze.java.harness; - -public class SubclassActivity extends SuperclassActivity { - - @Override - public void onPause() { - super.onPause(); - mObj = null; - } -} diff --git a/infer/tests/codetoanalyze/java/harness/SuperclassActivity.java b/infer/tests/codetoanalyze/java/harness/SuperclassActivity.java deleted file mode 100644 index 5101f0c84..000000000 --- a/infer/tests/codetoanalyze/java/harness/SuperclassActivity.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2015 - 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. - */ - -package codetoanalyze.java.harness; - -import android.app.Activity; - -/** - * If my subclasses null out mObj in an earlier lifecycle method, it will cause - * a NPE in onDestroy. - */ -public class SuperclassActivity extends Activity { - - Object mObj = new Object(); - - @Override - public void onDestroy() { - super.onDestroy(); - mObj.toString(); - } -} diff --git a/infer/tests/endtoend/java/harness/CallbackTest.java b/infer/tests/endtoend/java/harness/CallbackTest.java deleted file mode 100644 index a02909ba7..000000000 --- a/infer/tests/endtoend/java/harness/CallbackTest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2013 - 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. - */ - -package endtoend.java.harness; - -import static org.hamcrest.MatcherAssert.assertThat; -import static utils.matchers.ResultContainsErrorNoFilename.contains; - -import com.google.common.collect.ImmutableList; - -import org.junit.BeforeClass; -import org.junit.ClassRule; -import org.junit.Test; - -import java.io.IOException; - -import utils.DebuggableTemporaryFolder; -import utils.InferException; -import utils.InferResults; -import utils.InferRunner; - -public class CallbackTest { - - public static final String CallbackActivity = - "infer/tests/codetoanalyze/java/harness/CallbackActivity.java"; - - public static final String NULL_DEREFERENCE = "NULL_DEREFERENCE"; - - @ClassRule - public static DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); - - private static ImmutableList inferCmd; - - - @BeforeClass - public static void runInfer() throws IOException { - inferCmd = InferRunner.createJavaInferHarnessCommand(folder, CallbackActivity); - } - - - @Test - public void harnessRevealsNpe() - throws InterruptedException, IOException, InferException { - InferResults inferResults = InferRunner.runInferJava(inferCmd); - assertThat( - "Results should contain NPE", - inferResults, - contains( - NULL_DEREFERENCE, - "java.harness.CallbackActivity.InferGeneratedHarness" - ) - ); - } - -} diff --git a/infer/tests/endtoend/java/harness/FindViewByIdTest.java b/infer/tests/endtoend/java/harness/FindViewByIdTest.java deleted file mode 100644 index a9376f416..000000000 --- a/infer/tests/endtoend/java/harness/FindViewByIdTest.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2013 - 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. - */ - -package endtoend.java.harness; - - -import static org.hamcrest.MatcherAssert.assertThat; -import static utils.matchers.ResultContainsErrorNoFilename.contains; - -import com.google.common.collect.ImmutableList; - -import org.junit.BeforeClass; -import org.junit.ClassRule; -import org.junit.Test; - -import java.io.IOException; - -import utils.DebuggableTemporaryFolder; -import utils.InferException; -import utils.InferResults; -import utils.InferRunner; - -public class FindViewByIdTest { - - public static final String FindViewByIdActivity = - "infer/tests/codetoanalyze/java/harness/FindViewByIdActivity.java"; - - public static final String MyView = - "infer/tests/codetoanalyze/java/harness/MyView.java"; - - public static final String NULL_DEREFERENCE = "NULL_DEREFERENCE"; - - @ClassRule - public static DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); - - private static ImmutableList inferCmd; - - - @BeforeClass - public static void runInfer() throws IOException { - inferCmd = InferRunner.createJavaInferAngelicHarnessCommand( - folder, - ImmutableList.of(FindViewByIdActivity, MyView)); - } - - - @Test - public void harnessRevealsNpe() - throws InterruptedException, IOException, InferException { - InferResults inferResults = InferRunner.runInferJava(inferCmd); - assertThat( - "Results should contain NPE", - inferResults, - contains( - NULL_DEREFERENCE, - "java.harness.FindViewByIdActivity.InferGeneratedHarness" - ) - ); - } - -} diff --git a/infer/tests/endtoend/java/harness/SuperclassTest.java b/infer/tests/endtoend/java/harness/SuperclassTest.java deleted file mode 100644 index 39ab7f225..000000000 --- a/infer/tests/endtoend/java/harness/SuperclassTest.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2013 - 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. - */ - -package endtoend.java.harness; - - -import static org.hamcrest.MatcherAssert.assertThat; -import static utils.matchers.ResultContainsErrorNoFilename.contains; - -import com.google.common.collect.ImmutableList; - -import org.junit.BeforeClass; -import org.junit.ClassRule; -import org.junit.Test; - -import java.io.IOException; - -import utils.DebuggableTemporaryFolder; -import utils.InferException; -import utils.InferResults; -import utils.InferRunner; - -public class SuperclassTest { - - public static final String SuperclassActivity = - "infer/tests/codetoanalyze/java/harness/SuperclassActivity.java"; - - public static final String SubclassActivity = - "infer/tests/codetoanalyze/java/harness/SubclassActivity.java"; - - public static final String NULL_DEREFERENCE = "NULL_DEREFERENCE"; - - @ClassRule - public static DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); - - private static ImmutableList inferCmd; - - - @BeforeClass - public static void runInfer() throws IOException { - inferCmd = InferRunner.createJavaInferHarnessCommand( - folder, - ImmutableList.of(SuperclassActivity, SubclassActivity)); - } - - - @Test - public void harnessRevealsNpe() - throws InterruptedException, IOException, InferException { - InferResults inferResults = InferRunner.runInferJava(inferCmd); - assertThat( - "Results should contain NPE", - inferResults, - contains( - NULL_DEREFERENCE, - "java.harness.SubclassActivity.InferGeneratedHarness" - ) - ); - } - -}