Reviewed By: skcho Differential Revision: D13852741 fbshipit-source-id: f0da54257master
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
Loading…
Reference in new issue