(*
* 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