You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
232 lines
8.9 KiB
232 lines
8.9 KiB
(*
|
|
* Copyright (c) 2009 - 2013 Monoidics ltd.
|
|
* 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.
|
|
*)
|
|
|
|
(** Support for Execution environments *)
|
|
|
|
open Utils
|
|
module L = Logging
|
|
|
|
(** per-file data: type environment and cfg *)
|
|
type file_data =
|
|
{ source: DB.source_file;
|
|
nLOC : int;
|
|
tenv_file: DB.filename;
|
|
mutable tenv: Sil.tenv option;
|
|
cfg_file: DB.filename;
|
|
mutable cfg: Cfg.cfg option;
|
|
}
|
|
|
|
|
|
(** get the path to the tenv file, which either one tenv file per source file or a global tenv file *)
|
|
let tenv_filename file_base =
|
|
let per_source_tenv_filename = DB.filename_add_suffix file_base ".tenv" in
|
|
if Sys.file_exists (DB.filename_to_string per_source_tenv_filename) then
|
|
per_source_tenv_filename
|
|
else
|
|
DB.global_tenv_fname ()
|
|
|
|
(** create a new file_data *)
|
|
let new_file_data source nLOC cg_fname =
|
|
let file_base = DB.chop_extension cg_fname in
|
|
let tenv_file = tenv_filename file_base in
|
|
let cfg_file = DB.filename_add_suffix file_base ".cfg" in
|
|
{ source = source;
|
|
nLOC = nLOC;
|
|
tenv_file = tenv_file;
|
|
tenv = None; (* Sil.load_tenv_from_file tenv_file *)
|
|
cfg_file = cfg_file;
|
|
cfg = None; (* Cfg.load_cfg_from_file cfg_file *)
|
|
}
|
|
|
|
|
|
(** execution environment *)
|
|
type t =
|
|
{ cg: Cg.t; (** global call graph *)
|
|
proc_map: file_data Procname.Hash.t; (** map from procedure name to file data *)
|
|
file_map: (DB.source_file, file_data) Hashtbl.t; (** map from filaname to file data *)
|
|
mutable active_opt : Procname.Set.t option; (** if not None, restrict the active procedures to the given set *)
|
|
mutable callees : Procname.Set.t; (** callees of active procedures *)
|
|
mutable procs_defined_in_several_files : Procname.Set.t; (** Procedures defined in more than one file *)
|
|
}
|
|
|
|
(** initial state, used to add cg's *)
|
|
type initial = t
|
|
|
|
(** freeze the execution environment, so it can be queried *)
|
|
let freeze exe_env = exe_env (* TODO: unclear what this function is used for *)
|
|
|
|
(** create a new execution environment *)
|
|
let create procset_opt =
|
|
{ cg = Cg.create ();
|
|
proc_map = Procname.Hash.create 17;
|
|
file_map = Hashtbl.create 17;
|
|
active_opt = procset_opt;
|
|
callees = Procname.Set.empty;
|
|
procs_defined_in_several_files = Procname.Set.empty;
|
|
}
|
|
|
|
(** check if a procedure is marked as active *)
|
|
let proc_is_active exe_env proc_name =
|
|
match exe_env.active_opt with
|
|
| None -> true
|
|
| Some procset -> Procname.Set.mem proc_name procset
|
|
|
|
(** add a procedure to the set of active procedures *)
|
|
let add_active_proc exe_env proc_name =
|
|
match exe_env.active_opt with
|
|
| None -> ()
|
|
| Some procset -> exe_env.active_opt <- Some (Procname.Set.add proc_name procset)
|
|
|
|
(** Add a callee to the exe_env, and extend the file_map and proc_map. *)
|
|
let add_callee (exe_env: t) (source_file : DB.source_file) (pname: Procname.t) =
|
|
exe_env.callees <- Procname.Set.add pname exe_env.callees;
|
|
let file_data_opt =
|
|
try Some (Hashtbl.find exe_env.file_map source_file)
|
|
with Not_found ->
|
|
let source_dir = DB.source_dir_from_source_file source_file in
|
|
let cg_fname = DB.source_dir_get_internal_file source_dir ".cg" in
|
|
(match Cg.load_from_file cg_fname with
|
|
| None -> None
|
|
| Some cg ->
|
|
let nLOC = Cg.get_nLOC cg in
|
|
let file_data = new_file_data source_file nLOC cg_fname in
|
|
Some file_data) in
|
|
match file_data_opt with
|
|
| None -> ()
|
|
| Some file_data ->
|
|
if (not (Procname.Hash.mem exe_env.proc_map pname))
|
|
then Procname.Hash.replace exe_env.proc_map pname file_data
|
|
|
|
(** like add_cg, but use exclude_fun to determine files to be excluded *)
|
|
let add_cg_exclude_fun (exe_env: t) (source_dir : DB.source_dir) exclude_fun =
|
|
let cg_fname = DB.source_dir_get_internal_file source_dir ".cg" in
|
|
let cg = match Cg.load_from_file cg_fname with
|
|
| None -> (L.err "cannot load %s@." (DB.filename_to_string cg_fname); assert false)
|
|
| Some cg ->
|
|
Cg.restrict_defined cg exe_env.active_opt;
|
|
cg in
|
|
let source = Cg.get_source cg in
|
|
if exclude_fun source then None
|
|
else
|
|
let nLOC = Cg.get_nLOC cg in
|
|
Cg.extend exe_env.cg cg;
|
|
let file_data = new_file_data source nLOC cg_fname in
|
|
let defined_procs = Cg.get_defined_nodes cg in
|
|
list_iter (fun pname ->
|
|
let should_update =
|
|
if Procname.Hash.mem exe_env.proc_map pname then
|
|
let old_source = (Procname.Hash.find exe_env.proc_map pname).source in
|
|
exe_env.procs_defined_in_several_files <- Procname.Set.add pname exe_env.procs_defined_in_several_files;
|
|
(* L.err "Warning: procedure %a is defined in both %s and %s@." Procname.pp pname (DB.source_file_to_string source) (DB.source_file_to_string old_source); *)
|
|
source < old_source (* when a procedure is defined in several files, map to the first alphabetically *)
|
|
else true in
|
|
if should_update then Procname.Hash.replace exe_env.proc_map pname file_data) defined_procs;
|
|
Hashtbl.add exe_env.file_map source file_data;
|
|
Some cg
|
|
|
|
(** add call graph from fname in the spec db, with relative tenv and cfg, to the execution environment *)
|
|
let add_cg exe_env (source_dir : DB.source_dir) =
|
|
add_cg_exclude_fun exe_env source_dir (fun _ -> false)
|
|
|
|
(** add a new source file -> file data mapping. arguments are the components of the file_data
|
|
* record *)
|
|
let add_file_mapping exe_env source nLOC tenv_file tenv cfg_file cfg =
|
|
let file_data =
|
|
{ source = source;
|
|
nLOC = nLOC;
|
|
tenv_file = tenv_file;
|
|
tenv = tenv;
|
|
cfg_file = cfg_file;
|
|
cfg = cfg;
|
|
} in
|
|
Hashtbl.add exe_env.file_map source file_data
|
|
|
|
(** get the procedures defined in more than one file *)
|
|
let get_procs_defined_in_several_files exe_env =
|
|
exe_env.procs_defined_in_several_files
|
|
|
|
(** get the global call graph *)
|
|
let get_cg exe_env =
|
|
exe_env.cg
|
|
|
|
let get_file_data exe_env pname =
|
|
try
|
|
Procname.Hash.find exe_env.proc_map pname
|
|
with Not_found ->
|
|
L.err "can't find tenv_cfg_object for %a@." Procname.pp pname;
|
|
raise Not_found
|
|
|
|
(** return the source file associated to the procedure *)
|
|
let get_source exe_env pname =
|
|
(get_file_data exe_env pname).source
|
|
|
|
let file_data_to_tenv file_data =
|
|
if file_data.tenv == None then file_data.tenv <- Sil.load_tenv_from_file file_data.tenv_file;
|
|
match file_data.tenv with
|
|
| None ->
|
|
L.err "Cannot find tenv for %s@." (DB.filename_to_string file_data.tenv_file);
|
|
assert false
|
|
| Some tenv -> tenv
|
|
|
|
(** return the procs enabled: active and not shadowed, plus the procs they call directly *)
|
|
let procs_enabled exe_env source =
|
|
let is_not_shadowed proc_name = (* not shadowed by a definition in another file *)
|
|
DB.source_file_equal (get_source exe_env proc_name) source in
|
|
match exe_env.active_opt with
|
|
| Some pset ->
|
|
let res = ref Procname.Set.empty in
|
|
let do_pname proc_name = (* add any proc which is not shadowed, and all the procs it calls *)
|
|
if is_not_shadowed proc_name then
|
|
let pset' = Cg.get_all_children exe_env.cg proc_name in
|
|
let pset'' = Procname.Set.add proc_name pset' in
|
|
res := Procname.Set.union pset'' !res in
|
|
Procname.Set.iter do_pname pset;
|
|
Some (Procname.Set.union !res exe_env.callees) (* keep callees in the cfg *)
|
|
| None -> None
|
|
|
|
let file_data_to_cfg exe_env file_data =
|
|
match file_data.cfg with
|
|
| None ->
|
|
let cfg = match Cfg.load_cfg_from_file file_data.cfg_file with
|
|
| None ->
|
|
L.err "Cannot find cfg for %s@." (DB.filename_to_string file_data.tenv_file);
|
|
assert false
|
|
| Some cfg -> cfg in
|
|
Cfg.Node.cfg_restrict_enabled cfg file_data.source (procs_enabled exe_env file_data.source);
|
|
file_data.cfg <- Some cfg;
|
|
cfg
|
|
| Some cfg -> cfg
|
|
|
|
(** return the type environment associated to the procedure *)
|
|
let get_tenv exe_env pname =
|
|
let file_data = get_file_data exe_env pname in
|
|
file_data_to_tenv file_data
|
|
|
|
(** return the cfg associated to the procedure *)
|
|
let get_cfg exe_env pname =
|
|
let file_data = get_file_data exe_env pname in
|
|
file_data_to_cfg exe_env file_data
|
|
|
|
(** [iter_files f exe_env] applies [f] to the filename and tenv and cfg for each file in [exe_env] *)
|
|
let iter_files f exe_env =
|
|
let do_file fname file_data =
|
|
DB.current_source := fname;
|
|
Config.nLOC := file_data.nLOC;
|
|
f fname (file_data_to_tenv file_data) (file_data_to_cfg exe_env file_data) in
|
|
Hashtbl.iter do_file exe_env.file_map
|
|
|
|
(** [fold_files f exe_env] folds f through the source file, tenv, and cfg for each file in [exe_env] *)
|
|
let fold_files f acc exe_env =
|
|
let fold_file fname file_data acc =
|
|
DB.current_source := fname;
|
|
Config.nLOC := file_data.nLOC;
|
|
f fname (file_data_to_tenv file_data) (file_data_to_cfg exe_env file_data) acc in
|
|
Hashtbl.fold fold_file exe_env.file_map acc
|