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

(*
* 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