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.

229 lines
8.0 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.
*)
open! IStd
open! PVariant
module Hashtbl = Caml.Hashtbl
(** Support for Execution environments *)
module L = Logging
module F = Format
(** per-file data: type environment and cfg *)
type file_data =
{ source: SourceFile.t;
tenv_file: DB.filename;
mutable tenv: Tenv.t 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) = `Yes then
per_source_tenv_filename
else
DB.global_tenv_fname
module FilenameHash = Hashtbl.Make(
struct
type t = DB.filename
let equal file1 file2 = DB.equal_filename file1 file2
let hash = Hashtbl.hash
end)
(** create a new file_data *)
let new_file_data source 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;
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 *)
}
let create_file_data table source cg_fname =
match FilenameHash.find table cg_fname with
| file_data ->
file_data
| exception Not_found ->
let file_data = new_file_data source cg_fname in
FilenameHash.add table cg_fname file_data;
file_data
(** execution environment *)
type t =
{ cg: Cg.t; (** global call graph *)
proc_map: file_data Typ.Procname.Hash.t; (** map from procedure name to file data *)
file_map: file_data FilenameHash.t; (** map from cg fname to file data *)
mutable source_files : SourceFile.Set.t; (** Source files in the execution environment *)
}
(** initial state, used to add cg's *)
type initial = t
(** create a new execution environment *)
let create () =
{ cg = Cg.create (SourceFile.invalid __FILE__);
proc_map = Typ.Procname.Hash.create 17;
file_map = FilenameHash.create 1;
source_files = SourceFile.Set.empty;
}
(** add call graph from fname in the spec db,
with relative tenv and cfg, to the execution environment *)
let add_cg (exe_env: t) (source_dir : DB.source_dir) =
let cg_fname = DB.source_dir_get_internal_file source_dir ".cg" in
match Cg.load_from_file cg_fname with
| None ->
L.internal_error "Error: cannot load %s@." (DB.filename_to_string cg_fname)
| Some cg ->
let source = Cg.get_source cg in
exe_env.source_files <- SourceFile.Set.add source exe_env.source_files;
let defined_procs = Cg.get_defined_nodes cg in
let duplicate_procs_to_print = List.filter_map defined_procs
~f:(fun pname ->
(match AttributesTable.find_file_capturing_procedure ~cache:false pname with
| None -> None
| Some (source_captured, origin) ->
let multiply_defined = SourceFile.compare source source_captured <> 0 in
if multiply_defined then Cg.remove_node_defined cg pname;
if multiply_defined && origin <> `Include then
Some (pname, source_captured)
else None
)) in
(if Config.dump_duplicate_symbols then
Out_channel.with_file (Config.results_dir ^/ Config.duplicates_filename)
~append:true ~perm:0o666 ~f:(fun outc ->
let fmt = F.formatter_of_out_channel outc in
List.iter duplicate_procs_to_print ~f:(fun (pname, source_captured) ->
F.fprintf fmt "@.DUPLICATE_SYMBOLS source: %a source_captured:%a pname:%a@."
SourceFile.pp source
SourceFile.pp source_captured
Typ.Procname.pp pname
);
)
);
Cg.extend exe_env.cg cg
(** get the global call graph *)
let get_cg exe_env =
exe_env.cg
let get_file_data exe_env pname =
try
Some (Typ.Procname.Hash.find exe_env.proc_map pname)
with Not_found ->
begin
let source_file_opt =
match AttributesTable.load_attributes ~cache:true pname with
| None ->
L.(debug Analysis Medium) "can't find tenv_cfg_object for %a@." Typ.Procname.pp pname;
None
| Some proc_attributes when Config.reactive_capture ->
let get_captured_file {ProcAttributes.source_file_captured} = source_file_captured in
OndemandCapture.try_capture proc_attributes |> Option.map ~f:get_captured_file
| Some proc_attributes ->
Some proc_attributes.ProcAttributes.source_file_captured in
let get_file_data_for_source source_file =
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
let file_data = create_file_data exe_env.file_map source_file cg_fname in
Typ.Procname.Hash.replace exe_env.proc_map pname file_data;
file_data in
Option.map ~f:get_file_data_for_source source_file_opt
end
(** return the source file associated to the procedure *)
let get_source exe_env pname =
Option.map
~f:(fun file_data -> file_data.source)
(get_file_data exe_env pname)
let file_data_to_tenv file_data =
if is_none file_data.tenv
then file_data.tenv <- Tenv.load_from_file file_data.tenv_file;
file_data.tenv
let file_data_to_cfg file_data =
if is_none file_data.cfg
then file_data.cfg <- Cfg.load_cfg_from_file file_data.cfg_file;
file_data.cfg
let java_global_tenv =
lazy
(match Tenv.load_from_file DB.global_tenv_fname with
| None ->
failwithf
"Could not load the global tenv at path %s@."
(DB.filename_to_string DB.global_tenv_fname)
| Some tenv -> tenv)
(** return the type environment associated to the procedure *)
let get_tenv exe_env proc_name =
match proc_name with
| Typ.Procname.Java _ ->
Lazy.force java_global_tenv
| _ ->
begin
match get_file_data exe_env proc_name with
| Some file_data -> (
match file_data_to_tenv file_data with
| Some tenv ->
tenv
| None ->
failwithf "get_tenv: tenv not found for %a in file %s"
Typ.Procname.pp proc_name (DB.filename_to_string file_data.tenv_file)
)
| None ->
failwithf "get_tenv: file_data not found for %a" Typ.Procname.pp proc_name
end
(** return the cfg associated to the procedure *)
let get_cfg exe_env pname =
match get_file_data exe_env pname with
| None ->
None
| Some file_data ->
file_data_to_cfg file_data
(** return the proc desc associated to the procedure *)
let get_proc_desc exe_env pname =
match get_cfg exe_env pname with
| Some cfg ->
Cfg.find_proc_desc_from_name cfg pname
| None ->
None
(** Create an exe_env from a source dir *)
let from_cluster cluster =
let exe_env = create () in
add_cg exe_env cluster;
exe_env
(** [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 _ file_data seen_files_acc =
let fname = file_data.source in
if SourceFile.Set.mem fname seen_files_acc ||
(* only files added with add_cg* functions *)
not (SourceFile.Set.mem fname exe_env.source_files)
then seen_files_acc
else
begin
Option.iter ~f:(fun cfg -> f fname cfg) (file_data_to_cfg file_data);
SourceFile.Set.add fname seen_files_acc
end in
ignore (Typ.Procname.Hash.fold do_file exe_env.proc_map SourceFile.Set.empty)