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.

163 lines
6.5 KiB

(*
* Copyright (c) 2016 - 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
type 'a state = { pre: 'a; post: 'a; visit_count: int; }
module type S = sig
module TransferFunctions : TransferFunctions.S
module InvariantMap : Map.S with type key = TransferFunctions.CFG.id
module Interprocedural
(Summary : Summary.S with type summary = TransferFunctions.Domain.astate) :
sig
val checker : Callbacks.proc_callback_args -> TransferFunctions.extras ->
TransferFunctions.Domain.astate option
end
type invariant_map = TransferFunctions.Domain.astate state InvariantMap.t
val compute_post : TransferFunctions.extras ProcData.t -> TransferFunctions.Domain.astate option
val exec_cfg : TransferFunctions.CFG.t -> TransferFunctions.extras ProcData.t -> invariant_map
val exec_pdesc : TransferFunctions.extras ProcData.t -> invariant_map
val extract_post : InvariantMap.key -> 'a state InvariantMap.t -> 'a option
val extract_pre : InvariantMap.key -> 'a state InvariantMap.t -> 'a option
val extract_state : InvariantMap.key -> 'a InvariantMap.t -> 'a option
end
module MakeNoCFG
(Scheduler : Scheduler.S)
(TransferFunctions : TransferFunctions.S with module CFG = Scheduler.CFG) = struct
module CFG = Scheduler.CFG
module InvariantMap = ProcCfg.NodeIdMap (CFG)
module TransferFunctions = TransferFunctions
module Domain = TransferFunctions.Domain
type invariant_map = Domain.astate state InvariantMap.t
(** extract the state of node [n] from [inv_map] *)
let extract_state node_id inv_map =
try Some (InvariantMap.find node_id inv_map)
with Not_found -> None
(** extract the postcondition of node [n] from [inv_map] *)
let extract_post node_id inv_map =
match extract_state node_id inv_map with
| Some state -> Some state.post
| None -> None
(** extract the precondition of node [n] from [inv_map] *)
let extract_pre node_id inv_map =
match extract_state node_id inv_map with
| Some state -> Some state.pre
| None -> None
let exec_node node astate_pre work_queue inv_map proc_data =
let node_id = CFG.id node in
let update_inv_map pre visit_count =
let compute_post (pre, inv_map) (instr, id_opt) =
let post = TransferFunctions.exec_instr pre proc_data node instr in
match id_opt with
| Some id -> post, InvariantMap.add id { pre; post; visit_count; } inv_map
| None -> post, inv_map in
(* hack to ensure that we call `exec_instr` on a node even if it has no instructions *)
let instr_ids = match CFG.instr_ids node with
| [] -> [Sil.skip_instr, None]
| l -> l in
let astate_post, inv_map_post = IList.fold_left compute_post (pre, inv_map) instr_ids in
let inv_map'' =
InvariantMap.add node_id { pre; post=astate_post; visit_count; } inv_map_post in
inv_map'', Scheduler.schedule_succs work_queue node in
if InvariantMap.mem node_id inv_map
then
let old_state = InvariantMap.find node_id inv_map in
let widened_pre =
Domain.widen ~prev:old_state.pre ~next:astate_pre ~num_iters:old_state.visit_count in
if Domain.(<=) ~lhs:widened_pre ~rhs:old_state.pre
then inv_map, work_queue
else update_inv_map widened_pre (old_state.visit_count + 1)
else
(* first time visiting this node *)
let visit_count = 1 in
update_inv_map astate_pre visit_count
let rec exec_worklist cfg work_queue inv_map proc_data =
let compute_pre node inv_map =
(* if the [pred] -> [node] transition was normal, use post([pred]) *)
let extract_post_ pred = extract_post (CFG.id pred) inv_map in
let normal_posts = IList.map extract_post_ (CFG.normal_preds cfg node) in
(* if the [pred] -> [node] transition was exceptional, use pre([pred]) *)
let extract_pre_f acc pred = extract_pre (CFG.id pred) inv_map :: acc in
let all_posts = IList.fold_left extract_pre_f normal_posts (CFG.exceptional_preds cfg node) in
match IList.flatten_options all_posts with
| post :: posts -> Some (IList.fold_left Domain.join post posts)
| [] -> None in
match Scheduler.pop work_queue with
| Some (_, [], work_queue') ->
exec_worklist cfg work_queue' inv_map proc_data
| Some (node, _, work_queue') ->
let inv_map_post, work_queue_post = match compute_pre node inv_map with
| Some astate_pre -> exec_node node astate_pre work_queue' inv_map proc_data
| None -> inv_map, work_queue' in
exec_worklist cfg work_queue_post inv_map_post proc_data
| None ->
inv_map
(* compute and return an invariant map for [cfg] *)
let exec_cfg cfg proc_data =
let start_node = CFG.start_node cfg in
let inv_map', work_queue' =
exec_node start_node Domain.initial (Scheduler.empty cfg) InvariantMap.empty proc_data in
exec_worklist cfg work_queue' inv_map' proc_data
(* compute and return an invariant map for [pdesc] *)
let exec_pdesc ({ ProcData.pdesc; } as proc_data) =
exec_cfg (CFG.from_pdesc pdesc) proc_data
(* compute and return the postcondition of [pdesc] *)
let compute_post ({ ProcData.pdesc; } as proc_data) =
let cfg = CFG.from_pdesc pdesc in
let inv_map = exec_cfg cfg proc_data in
extract_post (CFG.id (CFG.exit_node cfg)) inv_map
module Interprocedural (Summ : Summary.S with type summary = Domain.astate) = struct
let checker { Callbacks.get_proc_desc; proc_desc; proc_name; tenv; } extras =
let analyze_ondemand_ _ pdesc =
match compute_post (ProcData.make pdesc tenv extras) with
| Some post ->
Summ.write_summary (Cfg.Procdesc.get_proc_name pdesc) post;
Some post
| None ->
None in
let analyze_ondemand source pdesc =
ignore (analyze_ondemand_ source pdesc) in
let callbacks =
{
Ondemand.analyze_ondemand;
get_proc_desc;
} in
if Ondemand.procedure_should_be_analyzed proc_name
then
begin
Ondemand.set_callbacks callbacks;
let post_opt = analyze_ondemand_ DB.source_file_empty proc_desc in
Ondemand.unset_callbacks ();
post_opt
end
else None
end
end
module Make (C : ProcCfg.S) (S : Scheduler.Make) (T : TransferFunctions.Make) =
MakeNoCFG (S (C)) (T (C))