[inferbo] Split Analysis and Checker

Reviewed By: skcho

Differential Revision: D13852741

fbshipit-source-id: f0da54257
master
Mehdi Bouaziz 6 years ago committed by Facebook Github Bot
parent 0447c5b8d5
commit 3fabbfdcad

@ -0,0 +1,334 @@
(*
* Copyright (c) 2016-present, Programming Research Laboratory (ROPAS)
* Seoul National University, Korea
* Copyright (c) 2017-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
open! IStd
open AbsLoc
open! AbstractDomain.Types
module BoUtils = BufferOverrunUtils
module Dom = BufferOverrunDomain
module L = Logging
module Models = BufferOverrunModels
module Sem = BufferOverrunSemantics
module Payload = SummaryPayload.Make (struct
type t = BufferOverrunSummary.t
let update_payloads astate (payloads : Payloads.t) = {payloads with buffer_overrun= Some astate}
let of_payloads (payloads : Payloads.t) = payloads.buffer_overrun
end)
type extras = {oenv: Dom.OndemandEnv.t}
module CFG = ProcCfg.NormalOneInstrPerNode
module Init = struct
let initial_state {ProcData.pdesc; tenv; extras= {oenv}} start_node =
let try_decl_local =
let pname = Procdesc.get_proc_name pdesc in
let model_env =
let node_hash = CFG.Node.hash start_node in
let location = CFG.Node.loc start_node in
let integer_type_widths = oenv.Dom.OndemandEnv.integer_type_widths in
BoUtils.ModelEnv.mk_model_env pname ~node_hash location tenv integer_type_widths
in
fun (mem, inst_num) {ProcAttributes.name; typ} ->
let loc = Loc.of_pvar (Pvar.mk name pname) in
BoUtils.Exec.decl_local model_env (mem, inst_num) (loc, typ)
in
let mem = Dom.Mem.init oenv in
let mem, _ = List.fold ~f:try_decl_local ~init:(mem, 1) (Procdesc.get_locals pdesc) in
mem
end
module TransferFunctions = struct
module CFG = CFG
module Domain = Dom.Mem
type nonrec extras = extras
let instantiate_mem_reachable (ret_id, _) callee_pdesc callee_pname ~callee_exit_mem
({Dom.eval_locpath} as eval_sym_trace) mem location =
let formals = Procdesc.get_pvar_formals callee_pdesc in
let copy_reachable_locs_from locs mem =
let copy loc acc =
Option.value_map (Dom.Mem.find_opt loc callee_exit_mem) ~default:acc ~f:(fun v ->
let locs = PowLoc.subst_loc loc eval_locpath in
let v = Dom.Val.subst v eval_sym_trace location in
PowLoc.fold (fun loc acc -> Dom.Mem.add_heap loc v acc) locs acc )
in
let reachable_locs = Dom.Mem.get_reachable_locs_from formals locs callee_exit_mem in
PowLoc.fold copy reachable_locs mem
in
let instantiate_ret_alias mem =
let subst_loc l =
Option.find_map (Loc.get_path l) ~f:(fun partial ->
try
let locs = eval_locpath partial in
match PowLoc.is_singleton_or_more locs with
| IContainer.Singleton loc ->
Some loc
| _ ->
None
with Caml.Not_found -> None )
in
let ret_alias =
Option.find_map (Dom.Mem.find_ret_alias callee_exit_mem) ~f:(fun alias_target ->
Dom.AliasTarget.loc_map alias_target ~f:subst_loc )
in
Option.value_map ret_alias ~default:mem ~f:(fun l -> Dom.Mem.load_alias ret_id l mem)
in
let ret_var = Loc.of_var (Var.of_id ret_id) in
let ret_val = Dom.Mem.find (Loc.of_pvar (Pvar.get_ret_pvar callee_pname)) callee_exit_mem in
let formal_locs =
List.fold formals ~init:PowLoc.bot ~f:(fun acc (formal, _) ->
let v = Dom.Mem.find (Loc.of_pvar formal) callee_exit_mem in
PowLoc.join acc (Dom.Val.get_all_locs v) )
in
Dom.Mem.add_stack ret_var (Dom.Val.subst ret_val eval_sym_trace location) mem
|> instantiate_ret_alias
|> copy_reachable_locs_from (PowLoc.join formal_locs (Dom.Val.get_all_locs ret_val))
let forget_ret_relation ret callee_pname mem =
let ret_loc = Loc.of_pvar (Pvar.get_ret_pvar callee_pname) in
let ret_var = Loc.of_var (Var.of_id (fst ret)) in
Dom.Mem.forget_locs (PowLoc.add ret_loc (PowLoc.singleton ret_var)) mem
let is_external pname =
match pname with
| Typ.Procname.Java java_pname ->
Typ.Procname.Java.is_external java_pname
| _ ->
false
let instantiate_mem :
Tenv.t
-> Typ.IntegerWidths.t
-> Ident.t * Typ.t
-> Procdesc.t
-> Typ.Procname.t
-> (Exp.t * Typ.t) list
-> Dom.Mem.t
-> BufferOverrunSummary.memory
-> Location.t
-> Dom.Mem.t =
fun tenv integer_type_widths ret callee_pdesc callee_pname params caller_mem callee_exit_mem
location ->
let rel_subst_map =
Sem.get_subst_map tenv integer_type_widths callee_pdesc params caller_mem callee_exit_mem
in
let eval_sym_trace =
Sem.mk_eval_sym_trace integer_type_widths callee_pdesc params caller_mem ~strict:false
in
let caller_mem =
instantiate_mem_reachable ret callee_pdesc callee_pname ~callee_exit_mem eval_sym_trace
caller_mem location
|> forget_ret_relation ret callee_pname
in
Dom.Mem.instantiate_relation rel_subst_map ~caller:caller_mem ~callee:callee_exit_mem
let exec_instr : Dom.Mem.t -> extras ProcData.t -> CFG.Node.t -> Sil.instr -> Dom.Mem.t =
fun mem {pdesc; tenv; extras= {oenv= {integer_type_widths}}} node instr ->
match instr with
| Load (id, _, _, _) when Ident.is_none id ->
mem
| Load (id, Exp.Lvar pvar, _, location) when Pvar.is_compile_constant pvar || Pvar.is_ice pvar
-> (
match Pvar.get_initializer_pname pvar with
| Some callee_pname -> (
match
Ondemand.analyze_proc_name ~caller_pdesc:pdesc callee_pname
|> Option.bind ~f:Payload.of_summary
|> Option.map ~f:BufferOverrunSummary.get_output
with
| Some callee_mem ->
let v = Dom.Mem.find (Loc.of_pvar pvar) callee_mem in
Dom.Mem.add_stack (Loc.of_id id) v mem
| None ->
L.d_printfln_escaped "/!\\ Unknown initializer of global constant %a" (Pvar.pp Pp.text)
pvar ;
Dom.Mem.add_unknown_from id ~callee_pname ~location mem )
| None ->
L.d_printfln_escaped "/!\\ Failed to get initializer name of global constant %a"
(Pvar.pp Pp.text) pvar ;
Dom.Mem.add_unknown id ~location mem )
| Load (id, exp, _, _) ->
BoUtils.Exec.load_locs id (Sem.eval_locs exp mem) mem
| Store (exp1, _, Const (Const.Cstr s), location) ->
let locs = Sem.eval_locs exp1 mem in
let model_env =
let pname = Procdesc.get_proc_name pdesc in
let node_hash = CFG.Node.hash node in
BoUtils.ModelEnv.mk_model_env pname ~node_hash location tenv integer_type_widths
in
let do_alloc = not (Sem.is_stack_exp exp1 mem) in
BoUtils.Exec.decl_string model_env ~do_alloc locs s mem
| Store (exp1, typ, exp2, location) ->
let locs = Sem.eval_locs exp1 mem in
let v =
Sem.eval integer_type_widths exp2 mem |> Dom.Val.add_assign_trace_elem location locs
in
let mem =
let sym_exps =
Dom.Relation.SymExp.of_exps
~get_int_sym_f:(Sem.get_sym_f integer_type_widths mem)
~get_offset_sym_f:(Sem.get_offset_sym_f integer_type_widths mem)
~get_size_sym_f:(Sem.get_size_sym_f integer_type_widths mem)
exp2
in
Dom.Mem.store_relation locs sym_exps mem
in
let mem = Dom.Mem.update_mem locs v mem in
let mem =
if Language.curr_language_is Clang && Typ.is_char typ then
BoUtils.Exec.set_c_strlen ~tgt:(Sem.eval integer_type_widths exp1 mem) ~src:v mem
else mem
in
let mem =
if not v.represents_multiple_values then
match PowLoc.is_singleton_or_more locs with
| IContainer.Singleton loc_v -> (
let pname = Procdesc.get_proc_name pdesc in
match Typ.Procname.get_method pname with
| "__inferbo_empty" when Loc.is_return loc_v -> (
match Procdesc.get_pvar_formals pdesc with
| [(formal, _)] ->
let formal_v = Dom.Mem.find (Loc.of_pvar formal) mem in
Dom.Mem.store_empty_alias formal_v loc_v mem
| _ ->
assert false )
| _ ->
Dom.Mem.store_simple_alias loc_v exp2 mem )
| _ ->
mem
else mem
in
let mem = Dom.Mem.update_latest_prune ~updated_locs:locs exp1 exp2 mem in
mem
| Prune (exp, _, _, _) ->
Sem.Prune.prune integer_type_widths exp mem
| Call (((id, ret_typ) as ret), Const (Cfun callee_pname), params, location, _) -> (
let mem = Dom.Mem.add_stack_loc (Loc.of_id id) mem in
match Models.Call.dispatch tenv callee_pname params with
| Some {Models.exec} ->
let model_env =
let node_hash = CFG.Node.hash node in
BoUtils.ModelEnv.mk_model_env callee_pname ~node_hash location tenv
integer_type_widths
in
exec model_env ~ret mem
| None -> (
match
Ondemand.analyze_proc_name ~caller_pdesc:pdesc callee_pname
|> Option.bind ~f:(fun callee_summary ->
Payload.of_summary callee_summary
|> Option.map ~f:(fun payload ->
( BufferOverrunSummary.get_output payload
, Summary.get_proc_desc callee_summary ) ) )
with
| Some (callee_exit_mem, callee_pdesc) ->
instantiate_mem tenv integer_type_widths ret callee_pdesc callee_pname params mem
callee_exit_mem location
| None ->
(* This may happen for procedures with a biabduction model too. *)
L.d_printfln_escaped "/!\\ Unknown call to %a" Typ.Procname.pp callee_pname ;
if is_external callee_pname then (
L.(debug BufferOverrun Verbose)
"/!\\ External call to unknown %a \n\n" Typ.Procname.pp callee_pname ;
let callsite = CallSite.make callee_pname location in
let path = Symb.SymbolPath.of_callsite ~ret_typ callsite in
let loc = Loc.of_allocsite (Allocsite.make_symbol path) in
let v = Dom.Mem.find loc mem in
Dom.Mem.add_stack (Loc.of_id id) v mem )
else Dom.Mem.add_unknown_from id ~callee_pname ~location mem ) )
| Call ((id, _), fun_exp, _, location, _) ->
let mem = Dom.Mem.add_stack_loc (Loc.of_id id) mem in
L.d_printfln_escaped "/!\\ Call to non-const function %a" Exp.pp fun_exp ;
Dom.Mem.add_unknown id ~location mem
| ExitScope (dead_vars, _) ->
Dom.Mem.remove_temps (List.filter_map dead_vars ~f:Var.get_ident) mem
| Abstract _ | Nullify _ ->
mem
let pp_session_name node fmt = F.fprintf fmt "bufferoverrun %a" CFG.Node.pp_id (CFG.Node.id node)
end
module Analyzer = AbstractInterpreter.MakeWTO (TransferFunctions)
type invariant_map = Analyzer.invariant_map
type local_decls = PowLoc.t
type memory_summary = BufferOverrunSummary.memory
let extract_pre = Analyzer.extract_pre
let extract_post = Analyzer.extract_post
let extract_state = Analyzer.extract_state
let get_local_decls : Procdesc.t -> local_decls =
fun proc_desc ->
let proc_name = Procdesc.get_proc_name proc_desc in
let accum_local_decls acc {ProcAttributes.name} =
let pvar = Pvar.mk name proc_name in
let loc = Loc.of_pvar pvar in
PowLoc.add loc acc
in
Procdesc.get_locals proc_desc |> List.fold ~init:PowLoc.empty ~f:accum_local_decls
let cached_compute_invariant_map =
let compute_invariant_map : Procdesc.t -> Tenv.t -> Typ.IntegerWidths.t -> invariant_map =
fun pdesc tenv integer_type_widths ->
Preanal.do_preanalysis pdesc tenv ;
let cfg = CFG.from_pdesc pdesc in
let pdata =
let oenv = Dom.OndemandEnv.mk pdesc tenv integer_type_widths in
ProcData.make pdesc tenv {oenv}
in
let initial = Init.initial_state pdata (CFG.start_node cfg) in
Analyzer.exec_pdesc ~do_narrowing:true ~initial pdata
in
(* Use a weak Hashtbl to prevent memory leaks (GC unnecessarily keeps invariant maps around) *)
let module WeakInvMapHashTbl = Caml.Weak.Make (struct
type t = Typ.Procname.t * invariant_map option
let equal (pname1, _) (pname2, _) = Typ.Procname.equal pname1 pname2
let hash (pname, _) = Hashtbl.hash pname
end) in
let inv_map_cache = WeakInvMapHashTbl.create 100 in
fun pdesc tenv integer_type_widths ->
let pname = Procdesc.get_proc_name pdesc in
match WeakInvMapHashTbl.find_opt inv_map_cache (pname, None) with
| Some (_, Some inv_map) ->
inv_map
| Some (_, None) ->
(* this should never happen *)
assert false
| None ->
let inv_map = compute_invariant_map pdesc tenv integer_type_widths in
WeakInvMapHashTbl.add inv_map_cache (pname, Some inv_map) ;
inv_map
let compute_summary : local_decls -> CFG.t -> invariant_map -> memory_summary =
fun locals cfg inv_map ->
let exit_node_id = CFG.exit_node cfg |> CFG.Node.id in
match extract_post exit_node_id inv_map with
| Some exit_mem ->
exit_mem |> Dom.Mem.forget_locs locals |> Dom.Mem.unset_oenv
| None ->
Bottom

@ -0,0 +1,28 @@
(*
* Copyright (c) 2017-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
open! IStd
module CFG = ProcCfg.NormalOneInstrPerNode
module Payload : SummaryPayload.S with type t = BufferOverrunSummary.t
type invariant_map
type local_decls = AbsLoc.PowLoc.t
val cached_compute_invariant_map : Procdesc.t -> Tenv.t -> Typ.IntegerWidths.t -> invariant_map
val compute_summary : local_decls -> CFG.t -> invariant_map -> BufferOverrunSummary.memory
val extract_pre : CFG.Node.id -> invariant_map -> BufferOverrunDomain.Mem.t option
val extract_post : CFG.Node.id -> invariant_map -> BufferOverrunDomain.Mem.t option
val extract_state :
CFG.Node.id -> invariant_map -> BufferOverrunDomain.Mem.t AbstractInterpreter.State.t option
val get_local_decls : Procdesc.t -> local_decls

File diff suppressed because it is too large Load Diff

@ -8,13 +8,3 @@
open! IStd
val checker : Callbacks.proc_callback_t
module CFG = ProcCfg.NormalOneInstrPerNode
type invariant_map
val cached_compute_invariant_map : Procdesc.t -> Tenv.t -> Typ.IntegerWidths.t -> invariant_map
val extract_pre : CFG.Node.id -> invariant_map -> BufferOverrunDomain.Mem.t option
val extract_post : CFG.Node.id -> invariant_map -> BufferOverrunDomain.Mem.t option

@ -53,7 +53,7 @@ module TransferFunctionsNodesBasicCost = struct
module Domain = NodesBasicCostDomain
type extras =
{ inferbo_invariant_map: BufferOverrunChecker.invariant_map
{ inferbo_invariant_map: BufferOverrunAnalysis.invariant_map
; integer_type_widths: Typ.IntegerWidths.t }
let cost_atomic_instruction = BasicCost.one
@ -101,7 +101,7 @@ module TransferFunctionsNodesBasicCost = struct
let exec_instr costmap ({ProcData.extras= {inferbo_invariant_map; integer_type_widths}} as pdata)
node instr =
let inferbo_mem =
Option.value_exn (BufferOverrunChecker.extract_pre (CFG.Node.id node) inferbo_invariant_map)
Option.value_exn (BufferOverrunAnalysis.extract_pre (CFG.Node.id node) inferbo_invariant_map)
in
let costmap = exec_instr_cost integer_type_widths inferbo_mem costmap pdata node instr in
costmap
@ -151,7 +151,7 @@ module BoundMap = struct
| _ -> (
let exit_state_opt =
let instr_node_id = InstrCFG.last_of_underlying_node node |> InstrCFG.Node.id in
BufferOverrunChecker.extract_post instr_node_id inferbo_invariant_map
BufferOverrunAnalysis.extract_post instr_node_id inferbo_invariant_map
in
match exit_state_opt with
| Some entry_mem ->
@ -740,7 +740,7 @@ let check_and_report_top_and_bottom cost proc_desc summary =
let checker {Callbacks.tenv; proc_desc; integer_type_widths; summary} : Summary.t =
let inferbo_invariant_map =
BufferOverrunChecker.cached_compute_invariant_map proc_desc tenv integer_type_widths
BufferOverrunAnalysis.cached_compute_invariant_map proc_desc tenv integer_type_widths
in
let node_cfg = NodeCFG.from_pdesc proc_desc in
let proc_data = ProcData.make_default proc_desc tenv in

@ -94,7 +94,7 @@ let get_issue_to_report tenv Call.({pname; node; params}) integer_type_widths in
let instr_node_id = InstrCFG.last_of_underlying_node node |> InstrCFG.Node.id in
let inferbo_invariant_map = Lazy.force inferbo_invariant_map in
let inferbo_mem =
Option.value_exn (BufferOverrunChecker.extract_pre instr_node_id inferbo_invariant_map)
Option.value_exn (BufferOverrunAnalysis.extract_pre instr_node_id inferbo_invariant_map)
in
(* get the cost of the function call *)
Cost.instantiate_cost integer_type_widths ~inferbo_caller_mem:inferbo_mem
@ -119,7 +119,7 @@ let checker Callbacks.({tenv; summary; proc_desc; integer_type_widths}) : Summar
~initial:(ReachingDefs.init_reaching_defs_with_formals proc_desc)
in
let inferbo_invariant_map =
lazy (BufferOverrunChecker.cached_compute_invariant_map proc_desc tenv integer_type_widths)
lazy (BufferOverrunAnalysis.cached_compute_invariant_map proc_desc tenv integer_type_widths)
in
(* get dominators *)
let idom = Dominators.get_idoms proc_desc in

@ -23,7 +23,7 @@ module Payload = SummaryPayload.Make (struct
end)
type purity_extras =
{inferbo_invariant_map: BufferOverrunChecker.invariant_map; formals: Var.t list}
{inferbo_invariant_map: BufferOverrunAnalysis.invariant_map; formals: Var.t list}
module TransferFunctions = struct
module CFG = ProcCfg.Normal
@ -139,7 +139,7 @@ module TransferFunctions = struct
CFG.Node.underlying_node node |> InstrCFG.last_of_underlying_node |> InstrCFG.Node.id
in
let inferbo_mem =
Option.value_exn (BufferOverrunChecker.extract_post node_id inferbo_invariant_map)
Option.value_exn (BufferOverrunAnalysis.extract_post node_id inferbo_invariant_map)
in
match instr with
| Assign (ae, _, _) when is_heap_access ae ->
@ -185,7 +185,7 @@ let should_report pdesc =
let checker {Callbacks.tenv; summary; proc_desc; integer_type_widths} : Summary.t =
let proc_name = Procdesc.get_proc_name proc_desc in
let inferbo_invariant_map =
BufferOverrunChecker.cached_compute_invariant_map proc_desc tenv integer_type_widths
BufferOverrunAnalysis.cached_compute_invariant_map proc_desc tenv integer_type_widths
in
let formals =
Procdesc.get_formals proc_desc

Loading…
Cancel
Save