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.

224 lines
7.4 KiB

(*
* 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.
*)
open! Utils
(** Module for on-demand analysis. *)
module L = Logging
module F = Format
(** Directories to analyze from the ondemand file. *)
let dirs_to_analyze =
let process_changed_files changed_files =
SourceFile.Set.fold
(fun source_file source_dir_set ->
let source_dir = DB.source_dir_from_source_file source_file in
StringSet.add (DB.source_dir_to_string source_dir) source_dir_set
)
changed_files StringSet.empty in
Option.map process_changed_files SourceFile.changed_files_set
type analyze_ondemand = SourceFile.t -> Procdesc.t -> unit
type get_proc_desc = Procname.t -> Procdesc.t option
type callbacks =
{
analyze_ondemand : analyze_ondemand;
get_proc_desc : get_proc_desc;
}
let callbacks_ref = ref None
let set_callbacks (callbacks : callbacks) =
callbacks_ref := Some callbacks
let unset_callbacks () =
callbacks_ref := None
let nesting = ref 0
let should_be_analyzed proc_attributes proc_name =
let currently_analyzed () =
match Specs.get_summary proc_name with
| None -> false
| Some summary -> Specs.is_active summary in
let already_analyzed () =
match Specs.get_summary proc_name with
| Some summary ->
Specs.get_timestamp summary > 0
| None ->
false in
proc_attributes.ProcAttributes.is_defined && (* we have the implementation *)
not (currently_analyzed ()) && (* avoid infinite loops *)
not (already_analyzed ()) (* avoid re-analysis of the same procedure *)
let procedure_should_be_analyzed proc_name =
match AttributesTable.load_attributes proc_name with
| Some proc_attributes ->
should_be_analyzed proc_attributes proc_name
| None ->
false
type global_state =
{
abs_val : int;
abstraction_rules : Abs.rules;
delayed_prints : L.print_action list;
footprint_mode : bool;
html_formatter : F.formatter;
name_generator : Ident.NameGenerator.t;
symexec_state : State.t
}
let save_global_state () =
Timeout.suspend_existing_timeout
~keep_symop_total:false; (* use a new global counter for the callee *)
{
abs_val = !Config.abs_val;
abstraction_rules = Abs.get_current_rules ();
delayed_prints = L.get_delayed_prints ();
footprint_mode = !Config.footprint;
html_formatter = !Printer.curr_html_formatter;
name_generator = Ident.NameGenerator.get_current ();
symexec_state = State.save_state ();
}
let restore_global_state st =
Config.abs_val := st.abs_val;
Abs.set_current_rules st.abstraction_rules;
L.set_delayed_prints st.delayed_prints;
Config.footprint := st.footprint_mode;
Printer.curr_html_formatter := st.html_formatter;
Ident.NameGenerator.set_current st.name_generator;
State.restore_state st.symexec_state;
Timeout.resume_previous_timeout ()
let run_proc_analysis ~propagate_exceptions analyze_proc curr_pdesc callee_pdesc =
let curr_pname = Procdesc.get_proc_name curr_pdesc in
let callee_pname = Procdesc.get_proc_name callee_pdesc in
(* Dot means start of a procedure *)
L.log_progress_procedure ();
if Config.trace_ondemand then L.stderr "[%d] run_proc_analysis %a -> %a@."
!nesting
Procname.pp curr_pname
Procname.pp callee_pname;
let preprocess () =
incr nesting;
let attributes_opt =
Specs.proc_resolve_attributes callee_pname in
let source = Option.map_default
(fun (attributes : ProcAttributes.t) ->
let attribute_pname = attributes.proc_name in
if not (Procname.equal callee_pname attribute_pname) then
failwith ("ERROR: "^(Procname.to_string callee_pname)
^" not equal to "^(Procname.to_string attribute_pname));
attributes.loc.file)
SourceFile.empty
attributes_opt in
let call_graph =
let cg = Cg.create (Some source) in
Cg.add_defined_node cg callee_pname;
cg in
let callee_pdesc_option =
if Config.dynamic_dispatch = `Lazy
then Some callee_pdesc
else None in
Specs.reset_summary call_graph callee_pname attributes_opt callee_pdesc_option;
Specs.set_status callee_pname Specs.ACTIVE;
source in
let postprocess source =
decr nesting;
let summary = Specs.get_summary_unsafe "ondemand" callee_pname in
let summary' =
{ summary with
Specs.status = Specs.INACTIVE;
timestamp = summary.Specs.timestamp + 1 } in
Specs.add_summary callee_pname summary';
Checkers.ST.store_summary callee_pname;
Printer.write_proc_html source false callee_pdesc in
let log_error_and_continue exn kind =
Reporting.log_error callee_pname exn;
let prev_summary = Specs.get_summary_unsafe "Ondemand.do_analysis" callee_pname in
let timestamp = max 1 (prev_summary.Specs.timestamp) in
let stats = { prev_summary.Specs.stats with Specs.stats_failure = Some kind } in
let payload =
{ prev_summary.Specs.payload with Specs.preposts = Some []; } in
let new_summary =
{ prev_summary with Specs.stats; payload; timestamp; } in
Specs.add_summary callee_pname new_summary in
let old_state = save_global_state () in
let source = preprocess () in
try
analyze_proc source callee_pdesc;
postprocess source;
restore_global_state old_state;
with exn ->
L.stderr "@.ONDEMAND EXCEPTION %a %s@.@.CALL STACK@.%s@.BACK TRACE@.%s@."
Procname.pp callee_pname
(Printexc.to_string exn)
(Printexc.raw_backtrace_to_string (Printexc.get_callstack 1000))
(Printexc.get_backtrace ());
restore_global_state old_state;
if propagate_exceptions
then
raise exn
else
match exn with
| SymOp.Analysis_failure_exe kind ->
(* in production mode, log the timeout/crash and continue with the summary we had before
the failure occurred *)
log_error_and_continue exn kind
| _ ->
(* this happens with assert false or some other unrecognized exception *)
log_error_and_continue exn (FKcrash (Printexc.to_string exn))
let analyze_proc_desc ~propagate_exceptions curr_pdesc callee_pdesc =
let callee_pname = Procdesc.get_proc_name callee_pdesc in
let proc_attributes = Procdesc.get_attributes callee_pdesc in
match !callbacks_ref with
| Some callbacks
when should_be_analyzed proc_attributes callee_pname ->
run_proc_analysis ~propagate_exceptions callbacks.analyze_ondemand curr_pdesc callee_pdesc
| _ -> ()
(** analyze_proc_name curr_pdesc proc_name
performs an on-demand analysis of proc_name
triggered during the analysis of curr_pname. *)
let analyze_proc_name ~propagate_exceptions curr_pdesc callee_pname =
match !callbacks_ref with
| Some callbacks
when procedure_should_be_analyzed callee_pname ->
begin
match callbacks.get_proc_desc callee_pname with
| Some callee_pdesc -> analyze_proc_desc ~propagate_exceptions curr_pdesc callee_pdesc
| None -> ()
end
| _ ->
() (* skipping *)
(** Find a proc desc for the procedure, perhaps loading it from disk. *)
let get_proc_desc callee_pname =
match !callbacks_ref with
| Some callbacks ->
callbacks.get_proc_desc callee_pname
| None ->
None