massive refactoring of harness generation

Reviewed By: jeremydubreil

Differential Revision: D3059627

fb-gh-sync-id: d3c3f18
shipit-source-id: d3c3f18
master
Sam Blackshear 9 years ago committed by Facebook Github Bot 5
parent 675009a2ee
commit 91ae1baebc

@ -647,10 +647,7 @@ let report_context_leaks pname sigma tenv =
when Sil.pvar_is_global pv -> when Sil.pvar_is_global pv ->
IList.iter IList.iter
(fun (f_name, f_strexp) -> (fun (f_name, f_strexp) ->
if not (Harness.is_generated_field f_name) check_reachable_context_from_fld (f_name, f_strexp) context_exps)
then
check_reachable_context_from_fld
(f_name, f_strexp) context_exps)
static_flds static_flds
| _ -> ()) | _ -> ())
sigma sigma

@ -12,112 +12,6 @@ module F = Format
(** Automatically create a harness method to exercise code under test *) (** 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 (** if [typ] is a lifecycle type, generate a list of (method call, receiver) pairs constituting a
lifecycle trace *) lifecycle trace *)
let try_create_lifecycle_trace typ lifecycle_typ lifecycle_procs tenv = match typ with 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 [] else []
| _ -> [] | _ -> []
(** get all the callbacks registered in [lifecycle_trace], transform the SIL to "extract" them into (** generate a harness for a lifecycle type in an Android application *)
global static fields belong to the harness so that they are easily callable, and return a list let create_harness cfg cg tenv =
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 =
IList.iter (fun (pkg, clazz, lifecycle_methods) -> IList.iter (fun (pkg, clazz, lifecycle_methods) ->
let typ_name = Mangled.from_package_class pkg clazz in let typ_name = Mangled.from_package_class pkg clazz in
match AndroidFramework.get_lifecycle_for_framework_typ_opt typ_name lifecycle_methods tenv with match AndroidFramework.get_lifecycle_for_framework_typ_opt typ_name lifecycle_methods tenv with
| Some (framework_typ, framework_procs) -> | Some (framework_typ, framework_procs) ->
(* iterate through the type environment and generate a lifecycle harness for each subclass of (* iterate through the type environment and generate a lifecycle harness for each
* [lifecycle_typ] *) 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 -> Sil.tenv_iter (fun _ struct_typ ->
let typ = Sil.Tstruct struct_typ in let typ = Sil.Tstruct struct_typ in
match try_create_lifecycle_trace typ framework_typ framework_procs tenv with match try_create_lifecycle_trace typ framework_typ framework_procs tenv with
| [] -> () | [] -> ()
| lifecycle_trace -> | 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_procname =
let harness_cls_name = PatternMatch.get_type_name typ in let harness_cls_name = PatternMatch.get_type_name typ in
let pname = let pname =
@ -200,13 +55,7 @@ let create_android_harness proc_file_map tenv =
match pname with match pname with
| Procname.Java harness_procname -> harness_procname | Procname.Java harness_procname -> harness_procname
| _ -> assert false in | _ -> assert false in
let callback_fields = Inhabit.inhabit_trace lifecycle_trace harness_procname cg cfg
extract_callbacks lifecycle_trace harness_procname proc_file_map tenv in
Inhabit.inhabit_trace
lifecycle_trace callback_fields harness_procname proc_file_map
) tenv ) tenv
| None -> () | None -> ()
) AndroidFramework.get_lifecycles ) 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

@ -9,8 +9,5 @@
(** Automatically create a harness method to exercise code under test *) (** 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 *) (** 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

@ -18,7 +18,6 @@ module TypSet = Sil.TypSet
module TypMap = Sil.TypMap module TypMap = Sil.TypMap
type lifecycle_trace = (Procname.t * Sil.typ option) list 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 (** list of instrs and temporary variables created during inhabitation and a cache of types that
* have already been inhabited *) * have already been inhabited *)
@ -30,6 +29,20 @@ type env = { instrs : Sil.instr list;
pc : Location.t; pc : Location.t;
harness_name : Procname.java } 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 *) (** add an instruction to the env, update tmp_vars, and bump the pc *)
let env_add_instr instr tmp_vars env = let env_add_instr instr tmp_vars env =
let incr_pc pc = { pc with Location.line = pc.Location.line + 1 } in 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 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 local_name_cntr = ref 0
let create_fresh_local_name () = 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) (inhabited_exp, env_add_instr call_instr [retval] env)
(** find or create a Sil expression with type typ *) (** 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) try (TypMap.find typ env.cache, env)
with Not_found -> with Not_found ->
let inhabit_internal typ env = match typ with 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 } -> | Sil.Tstruct { Sil.csu = Csu.Class _; def_methods } ->
let is_suitable_constructor p = let is_suitable_constructor p =
let try_get_non_receiver_formals p = let try_get_non_receiver_formals p =
try get_non_receiver_formals (formals_from_name p proc_file_map) get_non_receiver_formals (formals_from_name cfg p) in
with Not_found -> [] in
Procname.is_constructor p && IList.for_all (fun (_, typ) -> Procname.is_constructor p && IList.for_all (fun (_, typ) ->
not (TypSet.mem typ env.cur_inhabiting)) (try_get_non_receiver_formals p) in not (TypSet.mem typ env.cur_inhabiting)) (try_get_non_receiver_formals p) in
IList.filter (fun p -> is_suitable_constructor p) def_methods 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 (* arbitrarily choose a constructor for typ and invoke it. eventually, we may want to
* nondeterministically call all possible constructors instead *) * nondeterministically call all possible constructors instead *)
let env = 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 (* 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 *) * we can use it as a descriptive local variable name in the harness *)
let typ_class_name = let typ_class_name =
@ -154,21 +140,21 @@ let rec inhabit_typ typ proc_file_map env =
cur_inhabiting = env.cur_inhabiting }) cur_inhabiting = env.cur_inhabiting })
(** inhabit each of the types in the formals list *) (** 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 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 ((exp, formal_typ) :: args, env) in
IList.fold_right inhabit_arg formals ([], env) IList.fold_right inhabit_arg formals ([], env)
(** create Sil that calls the constructor in constr_name on allocated_obj and inhabits the (** create Sil that calls the constructor in constr_name on allocated_obj and inhabits the
* remaining arguments *) * 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 try
(* this lookup can fail when we try to get the procdesc of a procedure from a different (* 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 *) * module. this could be solved with a whole - program class hierarchy analysis *)
let (args, env) = let (args, env) =
let non_receiver_formals = tl_or_empty (formals_from_name constr_name proc_file_map) in let non_receiver_formals = tl_or_empty (formals_from_name cfg constr_name) in
inhabit_args non_receiver_formals proc_file_map env in inhabit_args non_receiver_formals cfg env in
let constr_instr = let constr_instr =
let fun_exp = fun_exp_from_name constr_name in 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 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 env_add_instr call_instr retval env
(** create Sil that inhabits args to and calls proc_name *) (** 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 try
let procdesc = procdesc_from_name procname proc_file_map in match procdesc_from_name cfg procname with
(* swap the type of the 'this' formal with the receiver type, if there is one *) | Some procdesc ->
let formals = match (Cfg.Procdesc.get_formals procdesc, receiver) with (* swap the type of the 'this' formal with the receiver type, if there is one *)
| ((name, _) :: formals, Some receiver) -> (name, receiver) :: formals let formals = match (Cfg.Procdesc.get_formals procdesc, receiver) with
| (formals, None) -> formals | ((name, _) :: formals, Some receiver) -> (name, receiver) :: formals
| ([], Some _) -> | (formals, None) -> formals
L.err | ([], Some _) ->
"Expected at least one formal to bind receiver to in method %a@." Procname.pp procname; L.err
assert false in "Expected at least one formal to bind receiver to in method %a@."
let (args, env) = inhabit_args formals proc_file_map env in Procname.pp procname;
inhabit_call_with_args procname procdesc args env 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 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 *) (** create a dummy file for the harness and associate them in the exe_env *)
let create_dummy_harness_file harness_name = let create_dummy_harness_file harness_name =
let dummy_file_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 (** create and fill the appropriate nodes and add them to the harness cfg. also add the harness
* proc to the cg *) * 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 *) (* each procedure has different scope: start names from id 0 *)
Ident.NameGenerator.reset (); 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 procdesc =
let proc_attributes = let proc_attributes =
{ (ProcAttributes.default (Procname.Java harness_name) Config.Java) with { (ProcAttributes.default (Procname.Java harness_name) Config.Java) with
ProcAttributes.is_defined = true; ProcAttributes.is_defined = true;
loc = env.pc; loc = env.pc;
} in } in
Cfg.Procdesc.create harness_cfg proc_attributes in Cfg.Procdesc.create cfg proc_attributes in
let harness_node = let harness_node =
(* important to reverse the list or there will be scoping issues! *) (* important to reverse the list or there will be scoping issues! *)
let instrs = (IList.rev env.instrs) in let instrs = (IList.rev env.instrs) in
let nodekind = Cfg.Node.Stmt_node "method_body" 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 (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 start_kind = Cfg.Node.Start_node procdesc in
let exit_kind = Cfg.Node.Exit_node procdesc in let exit_kind = Cfg.Node.Exit_node procdesc in
(create_node start_kind, create_node exit_kind) 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.add_locals_ret_declaration start_node [];
Cfg.Node.set_succs_exn start_node [harness_node] [exit_node]; Cfg.Node.set_succs_exn start_node [harness_node] [exit_node];
Cfg.Node.set_succs_exn harness_node [exit_node] [exit_node]; Cfg.Node.set_succs_exn harness_node [exit_node] [exit_node];
add_harness_to_cg harness_name harness_node cg; 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
(** create a procedure named harness_name that calls each of the methods in trace in the specified (** 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 *) * 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 if IList.length trace > 0 then
(* pick an arbitrary cg and cfg to piggyback the harness code onto *) let source_file = Cg.get_source cg in
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 harness_file = create_dummy_harness_file harness_name in let harness_file = create_dummy_harness_file harness_name in
let start_line = (Cg.get_nLOC cg) + 1 in let start_line = (Cg.get_nLOC cg) + 1 in
let empty_env = let empty_env =
@ -316,14 +265,9 @@ let inhabit_trace trace cb_flds harness_name proc_file_map =
pc = pc; pc = pc;
cur_inhabiting = TypSet.empty; cur_inhabiting = TypSet.empty;
harness_name = harness_name; } in harness_name = harness_name; } in
(* synthesize the harness body *) (* invoke lifecycle methods *)
let env'' = let env'' = IList.fold_left (fun env to_call -> inhabit_call to_call cfg env) empty_env trace in
(* 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
try 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 write_harness_to_file (IList.rev env''.instrs) harness_file
with Not_found -> () with Not_found -> ()

@ -11,17 +11,7 @@
type lifecycle_trace = (Procname.t * Sil.typ option) list 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

@ -103,6 +103,7 @@ let store_icfg tenv cg cfg program =
begin begin
let cfg_file = DB.source_dir_get_internal_file source_dir ".cfg" in 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 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; Preanal.doit ~f_translate_typ:(Some f_translate_typ) cfg cg tenv;
Cg.store_to_file cg_file cg; Cg.store_to_file cg_file cg;
Cfg.store_cfg_to_file cfg_file true cfg; 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. *) (* environment are obtained and saved. *)
let do_source_file let do_source_file
never_null_matcher linereader classes program tenv 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)@." JUtils.log "\nfilename: %s (%s)@."
(DB.source_file_to_string source_file) source_basename; (DB.source_file_to_string source_file) source_basename;
let call_graph, cfg = let call_graph, cfg =
JFrontend.compute_source_icfg JFrontend.compute_source_icfg
never_null_matcher linereader classes program tenv never_null_matcher linereader classes program tenv
source_basename package_opt in source_basename package_opt in
store_icfg tenv call_graph cfg program; 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
let capture_libs never_null_matcher linereader program tenv = 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 Inferconfig.SkipTranslationMatcher.load_matcher (Inferconfig.inferconfig ()) in
let never_null_matcher = let never_null_matcher =
Inferconfig.NeverReturnNull.load_matcher (Inferconfig.inferconfig ()) in Inferconfig.NeverReturnNull.load_matcher (Inferconfig.inferconfig ()) in
let proc_file_map = let skip source_file =
let skip source_file = skip_translation_matcher source_file Procname.empty in
skip_translation_matcher source_file Procname.empty in let translate_source_file basename (package_opt, _) source_file =
let translate_source_file basename (package_opt, _) source_file map = init_global_state source_file;
init_global_state source_file; if not (skip source_file) then
if skip source_file then map do_source_file
else do_source_file never_null_matcher linereader classes program tenv basename (package_opt, source_file) in
never_null_matcher linereader classes program tenv StringMap.iter
basename (package_opt, source_file) map in (fun basename file_entry ->
StringMap.fold match file_entry with
(fun basename file_entry map -> | JClasspath.Singleton source_file ->
match file_entry with translate_source_file basename (None, source_file) source_file
| JClasspath.Singleton source_file -> | JClasspath.Duplicate source_files ->
translate_source_file basename (None, source_file) source_file map IList.iter
| JClasspath.Duplicate source_files -> (fun (package, source_file) ->
IList.fold_left translate_source_file basename (Some package, source_file) source_file)
(fun accu (package, source_file) -> source_files)
translate_source_file basename (Some package, source_file) source_file accu) sources;
map source_files)
sources
Procname.Map.empty in
if !JConfig.dependency_mode then if !JConfig.dependency_mode then
capture_libs never_null_matcher linereader program tenv; capture_libs never_null_matcher linereader program tenv;
if !JConfig.create_harness then Harness.create_harness proc_file_map tenv;
save_tenv tenv; save_tenv tenv;
JClasspath.cleanup program; JClasspath.cleanup program;
JUtils.log "done @." JUtils.log "done @."

@ -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;
}
}

@ -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;
}
}

@ -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;
}
}

@ -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();
}
}

@ -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<String> 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"
)
);
}
}

@ -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<String> 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"
)
);
}
}

@ -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<String> 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"
)
);
}
}
Loading…
Cancel
Save