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.
383 lines
15 KiB
383 lines
15 KiB
(*
|
|
* Copyright (c) 2016-present, Programming Research Laboratory (ROPAS)
|
|
* Seoul National University, Korea
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* 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 = BufferOverrunAnalysisSummary.t
|
|
|
|
let field = Payloads.Fields.buffer_overrun_analysis
|
|
end)
|
|
|
|
type summary_and_formals = BufferOverrunAnalysisSummary.t * (Pvar.t * Typ.t) list
|
|
|
|
type get_proc_summary_and_formals = Typ.Procname.t -> summary_and_formals option
|
|
|
|
type extras = {get_proc_summary_and_formals: get_proc_summary_and_formals; oenv: Dom.OndemandEnv.t}
|
|
|
|
module CFG = ProcCfg.NormalOneInstrPerNode
|
|
|
|
module Init = struct
|
|
let initial_state {ProcData.summary; tenv; extras= {oenv}} start_node =
|
|
let try_decl_local =
|
|
let pname = Summary.get_proc_name summary 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 (Summary.get_proc_desc summary))
|
|
in
|
|
mem
|
|
end
|
|
|
|
module TransferFunctions = struct
|
|
module CFG = CFG
|
|
module Domain = Dom.Mem
|
|
|
|
type nonrec extras = extras
|
|
|
|
let instantiate_latest_prune ~ret_id ~callee_exit_mem eval_sym_trace location mem =
|
|
match
|
|
Dom.Mem.get_latest_prune callee_exit_mem
|
|
|> Dom.LatestPrune.subst ~ret_id eval_sym_trace location
|
|
with
|
|
| Ok latest_prune' ->
|
|
Dom.Mem.set_latest_prune latest_prune' mem
|
|
| Error `SubstBottom ->
|
|
Dom.Mem.bottom
|
|
| Error `SubstFail ->
|
|
mem
|
|
|
|
|
|
let instantiate_mem_reachable (ret_id, _) callee_formals callee_pname ~callee_exit_mem
|
|
({Dom.eval_locpath} as eval_sym_trace) mem location =
|
|
let formal_locs =
|
|
List.fold callee_formals ~init:PowLoc.bot ~f:(fun acc (formal, _) ->
|
|
PowLoc.add (Loc.of_pvar formal) acc )
|
|
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 callee_formals locs callee_exit_mem in
|
|
PowLoc.fold copy (PowLoc.diff reachable_locs formal_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 =
|
|
match Procdesc.load callee_pname with
|
|
| Some callee_pdesc when Procdesc.has_added_return_param callee_pdesc ->
|
|
Dom.Val.of_loc (Loc.of_pvar (Pvar.get_ret_param_pvar callee_pname))
|
|
| _ ->
|
|
Dom.Mem.find (Loc.of_pvar (Pvar.get_ret_pvar callee_pname)) callee_exit_mem
|
|
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))
|
|
|> instantiate_latest_prune ~ret_id ~callee_exit_mem eval_sym_trace location
|
|
|
|
|
|
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.relation_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
|
|
-> (Pvar.t * Typ.t) list
|
|
-> Typ.Procname.t
|
|
-> (Exp.t * Typ.t) list
|
|
-> Dom.Mem.t
|
|
-> BufferOverrunAnalysisSummary.t
|
|
-> Location.t
|
|
-> Dom.Mem.t =
|
|
fun tenv integer_type_widths ret callee_formals callee_pname params caller_mem callee_exit_mem
|
|
location ->
|
|
let rel_subst_map =
|
|
Sem.get_subst_map tenv integer_type_widths callee_formals params caller_mem callee_exit_mem
|
|
in
|
|
let eval_sym_trace =
|
|
Sem.mk_eval_sym_trace integer_type_widths callee_formals params caller_mem
|
|
~mode:Sem.EvalNormal
|
|
in
|
|
let caller_mem =
|
|
instantiate_mem_reachable ret callee_formals 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 {summary; tenv; extras= {get_proc_summary_and_formals; 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 get_proc_summary_and_formals callee_pname 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, typ, _) ->
|
|
BoUtils.Exec.load_locs id typ (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 = Summary.get_proc_name summary 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 = Summary.get_proc_name summary in
|
|
match Typ.Procname.get_method pname with
|
|
| "__inferbo_empty" when Loc.is_return loc_v -> (
|
|
match Procdesc.get_pvar_formals (Summary.get_proc_desc summary) 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 get_proc_summary_and_formals callee_pname with
|
|
| Some (callee_exit_mem, callee_formals) ->
|
|
instantiate_mem tenv integer_type_widths ret callee_formals 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
|
|
| Metadata (VariableLifetimeBegins (pvar, typ, location)) when Pvar.is_global pvar ->
|
|
let model_env =
|
|
let pname = Summary.get_proc_name summary in
|
|
let node_hash = CFG.Node.hash node in
|
|
BoUtils.ModelEnv.mk_model_env pname ~node_hash location tenv integer_type_widths
|
|
in
|
|
let mem, _ = BoUtils.Exec.decl_local model_env (mem, 1) (Loc.of_pvar pvar, typ) in
|
|
mem
|
|
| Metadata (ExitScope (dead_vars, _)) ->
|
|
Dom.Mem.remove_temps (List.filter_map dead_vars ~f:Var.get_ident) mem
|
|
| Metadata (Abstract _ | Nullify _ | Skip | VariableLifetimeBegins _) ->
|
|
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 = BufferOverrunAnalysisSummary.t
|
|
|
|
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 compute_invariant_map :
|
|
Summary.t -> Tenv.t -> Typ.IntegerWidths.t -> get_proc_summary_and_formals -> invariant_map =
|
|
fun summary tenv integer_type_widths get_proc_summary_and_formals ->
|
|
let pdesc = Summary.get_proc_desc summary in
|
|
let cfg = CFG.from_pdesc pdesc in
|
|
let pdata =
|
|
let oenv = Dom.OndemandEnv.mk pdesc tenv integer_type_widths in
|
|
ProcData.make summary tenv {get_proc_summary_and_formals; oenv}
|
|
in
|
|
let initial = Init.initial_state pdata (CFG.start_node cfg) in
|
|
Analyzer.exec_pdesc ~do_narrowing:true ~initial pdata
|
|
|
|
|
|
let cached_compute_invariant_map =
|
|
(* 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 summary tenv integer_type_widths ->
|
|
let pname = Summary.get_proc_name summary 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 get_proc_summary_and_formals callee_pname =
|
|
Ondemand.analyze_proc_name ~caller_summary:summary callee_pname
|
|
|> Option.bind ~f:(fun summary ->
|
|
Payload.of_summary summary
|
|
|> Option.map ~f:(fun payload ->
|
|
(payload, Summary.get_proc_desc summary |> Procdesc.get_pvar_formals) ) )
|
|
in
|
|
let inv_map =
|
|
compute_invariant_map summary tenv integer_type_widths get_proc_summary_and_formals
|
|
in
|
|
WeakInvMapHashTbl.add inv_map_cache (pname, Some inv_map) ;
|
|
inv_map
|
|
|
|
|
|
let compute_summary :
|
|
local_decls -> (Pvar.t * Typ.t) list -> CFG.t -> invariant_map -> memory_summary =
|
|
fun locals formals 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_unreachable_locs ~formals
|
|
|> Dom.Mem.relation_forget_locs locals
|
|
|> Dom.Mem.unset_oenv
|
|
| None ->
|
|
Bottom
|
|
|
|
|
|
let do_analysis : Callbacks.proc_callback_args -> Summary.t =
|
|
fun {tenv; integer_type_widths; summary} ->
|
|
let proc_desc = Summary.get_proc_desc summary in
|
|
let inv_map = cached_compute_invariant_map summary tenv integer_type_widths in
|
|
let locals = get_local_decls proc_desc in
|
|
let formals = Procdesc.get_pvar_formals proc_desc in
|
|
let cfg = CFG.from_pdesc proc_desc in
|
|
let payload = compute_summary locals formals cfg inv_map in
|
|
Payload.update_summary payload summary
|