(* * 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. *) open Utils module L = Logging (** Module to register and invoke callbacks *) (** Inline a synthetic (access or bridge) method. *) let inline_synthetic_method ret_ids etl proc_desc proc_name loc_call : Sil.instr option = let modified = ref None in let debug = false in let found instr instr' = modified := Some instr'; if debug then begin L.stderr "XX inline_synthetic_method found instr: %a@." (Sil.pp_instr pe_text) instr; L.stderr "XX inline_synthetic_method instr': %a@." (Sil.pp_instr pe_text) instr' end in let do_instr node instr = match instr, ret_ids, etl with | Sil.Letderef (id1, Sil.Lfield (Sil.Var id2, fn, ft), bt, loc), [ret_id], [(e1, t1)] -> (* getter for fields *) let instr' = Sil.Letderef (ret_id, Sil.Lfield (e1, fn, ft), bt, loc_call) in found instr instr' | Sil.Letderef (id1, Sil.Lfield (Sil.Lvar pvar, fn, ft), bt, loc), [ret_id], [] when Sil.pvar_is_global pvar -> (* getter for static fields *) let instr' = Sil.Letderef (ret_id, Sil.Lfield (Sil.Lvar pvar, fn, ft), bt, loc_call) in found instr instr' | Sil.Set (Sil.Lfield (ex1, fn, ft), bt , ex2, loc), _, [(e1, t1); (e2, t2)] -> (* setter for fields *) let instr' = Sil.Set (Sil.Lfield (e1, fn, ft), bt , e2, loc_call) in found instr instr' | Sil.Set (Sil.Lfield (Sil.Lvar pvar, fn, ft), bt , ex2, loc), _, [(e1, t1)] when Sil.pvar_is_global pvar -> (* setter for static fields *) let instr' = Sil.Set (Sil.Lfield (Sil.Lvar pvar, fn, ft), bt , e1, loc_call) in found instr instr' | Sil.Call (ret_ids', Sil.Const (Sil.Cfun pn), etl', loc', cf), _, _ when list_length ret_ids = list_length ret_ids' && list_length etl' = list_length etl -> let instr' = Sil.Call (ret_ids, Sil.Const (Sil.Cfun pn), etl, loc_call, cf) in found instr instr' | Sil.Call (ret_ids', Sil.Const (Sil.Cfun pn), etl', loc', cf), _, _ when list_length ret_ids = list_length ret_ids' && list_length etl' + 1 = list_length etl -> let etl1 = match list_rev etl with (* remove last element *) | _ :: l -> list_rev l | [] -> assert false in let instr' = Sil.Call (ret_ids, Sil.Const (Sil.Cfun pn), etl1, loc_call, cf) in found instr instr' | _ -> () in Cfg.Procdesc.iter_instrs do_instr proc_desc; !modified (** Find synthetic (access or bridge) methods in the procedure and inline them in the cfg. *) let proc_inline_synthetic_methods cfg proc_desc : unit = let instr_inline_synthetic_method = function | Sil.Call (ret_ids, Sil.Const (Sil.Cfun pn), etl, loc, _) -> (match Cfg.Procdesc.find_from_name cfg pn with | Some pd -> let is_access = Procname.java_is_access_method pn in let attributes = Cfg.Procdesc.get_attributes pd in let is_synthetic = attributes.Sil.is_synthetic_method in let is_bridge = attributes.Sil.is_bridge_method in if is_access || is_bridge || is_synthetic then inline_synthetic_method ret_ids etl pd pn loc else None | None -> None) | _ -> None in let node_inline_synthetic_methods node = let modified = ref false in let do_instr instr = match instr_inline_synthetic_method instr with | None -> instr | Some instr' -> modified := true; instr' in let instrs = Cfg.Node.get_instrs node in let instrs' = list_map do_instr instrs in if !modified then Cfg.Node.replace_instrs node instrs' in Cfg.Procdesc.iter_nodes node_inline_synthetic_methods proc_desc type proc_callback_t = Procname.t list -> (Procname.t -> Cfg.Procdesc.t option) -> Idenv.t -> Sil.tenv -> Procname.t -> Cfg.Procdesc.t -> unit type cluster_callback_t = Procname.t list -> (Procname.t -> Cfg.Procdesc.t option) -> (Idenv.t * Sil.tenv * Procname.t * Cfg.Procdesc.t) list -> unit let procedure_callbacks = ref [] let cluster_callbacks = ref [] let register_procedure_callback language_opt (callback: proc_callback_t) = procedure_callbacks := (language_opt, callback):: !procedure_callbacks let register_cluster_callback language_opt (callback: cluster_callback_t) = cluster_callbacks := (language_opt, callback):: !cluster_callbacks let unregister_all_callbacks () = procedure_callbacks := []; cluster_callbacks := [] (** Collect what we need to know about a procedure for the analysis. *) let get_procedure_definition exe_env proc_name = let cfg = Exe_env.get_cfg exe_env proc_name in let tenv = Exe_env.get_tenv exe_env proc_name in Option.map (fun proc_desc -> proc_inline_synthetic_methods cfg proc_desc; let idenv = Idenv.create cfg proc_desc and language = (Cfg.Procdesc.get_attributes proc_desc).Sil.language in (idenv, tenv, proc_name, proc_desc, language)) (Cfg.Procdesc.find_from_name cfg proc_name) let get_language proc_name = if Procname.is_java proc_name then Config.Java else Config.C_CPP (** Invoke all registered procedure callbacks on a set of procedures. *) let iterate_procedure_callbacks all_procs exe_env proc_name = let procedure_language = get_language proc_name in Config.curr_language := procedure_language; let cfg = Exe_env.get_cfg exe_env proc_name in let get_procdesc proc_name = let cfg = try Exe_env.get_cfg exe_env proc_name with Not_found -> cfg in Cfg.Procdesc.find_from_name cfg proc_name in let update_time proc_name elapsed = let prev_summary = Specs.get_summary_unsafe proc_name in let stats_time = prev_summary.Specs.stats.Specs.stats_time +. elapsed in let stats = { prev_summary.Specs.stats with Specs.stats_time = stats_time } in let summary = { prev_summary with Specs.stats = stats } in Specs.add_summary proc_name summary in Option.may (fun (idenv, tenv, proc_name, proc_desc, language) -> list_iter (fun (language_opt, proc_callback) -> let language_matches = match language_opt with | Some language -> language = procedure_language | None -> true in if language_matches then begin let init_time = Unix.gettimeofday () in proc_callback all_procs get_procdesc idenv tenv proc_name proc_desc; let elapsed = Unix.gettimeofday () -. init_time in update_time proc_name elapsed end) !procedure_callbacks) (get_procedure_definition exe_env proc_name) (** Invoke all registered cluster callbacks on a cluster of procedures. *) let iterate_cluster_callbacks all_procs exe_env proc_names = let get_procdesc proc_name = try let cfg = Exe_env.get_cfg exe_env proc_name in Cfg.Procdesc.find_from_name cfg proc_name with Not_found -> None in let procedure_definitions = list_map (get_procedure_definition exe_env) proc_names |> list_flatten_options in let environment = list_map (fun (idenv, tenv, proc_name, proc_desc, _) -> (idenv, tenv, proc_name, proc_desc)) procedure_definitions in (** Procedures matching the given language or all if no language is specified. *) let relevant_procedures language_opt = Option.map_default (fun l -> list_filter (fun p -> l = get_language p) proc_names) proc_names language_opt in list_iter (fun (language_opt, cluster_callback) -> let proc_names = relevant_procedures language_opt in if list_length proc_names > 0 then cluster_callback all_procs get_procdesc environment) !cluster_callbacks (** Invoke all procedure and cluster callbacks on a given environment. *) let iterate_callbacks store_summary call_graph exe_env = let proc_names = Cg.get_defined_nodes call_graph in let saved_language = !Config.curr_language in let cluster_id proc_name = match get_language proc_name with | Config.Java -> Procname.java_get_class proc_name | _ -> "unknown" in let cluster proc_names = let cluster_map = list_fold_left (fun map proc_name -> let proc_cluster = cluster_id proc_name in let bucket = try StringMap.find proc_cluster map with Not_found -> [] in StringMap.add proc_cluster (proc_name:: bucket) map) StringMap.empty proc_names in (* Return all values of the map *) list_map snd (StringMap.bindings cluster_map) in let reset_summary proc_name = let cfg_opt = try Some (Exe_env.get_cfg exe_env proc_name) with | Not_found -> None in let procdesc_opt = match cfg_opt with | Some cfg -> Cfg.Procdesc.find_from_name cfg proc_name | None -> None in let loc = match procdesc_opt with | Some proc_desc -> Cfg.Procdesc.get_loc proc_desc | None -> Location.loc_none in Specs.reset_summary call_graph proc_name loc in (* Make sure summaries exists. *) list_iter reset_summary proc_names; (* Invoke callbacks. *) list_iter (iterate_procedure_callbacks proc_names exe_env) proc_names; list_iter (iterate_cluster_callbacks proc_names exe_env) (cluster proc_names); list_iter store_summary proc_names; Config.curr_language := saved_language