|
|
|
(*
|
|
|
|
* Copyright (c) 2016 - present
|
|
|
|
*
|
|
|
|
* Programming Research Laboratory (ROPAS)
|
|
|
|
* Seoul National University, Korea
|
|
|
|
* 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! IStd
|
|
|
|
open AbsLoc
|
|
|
|
module F = Format
|
|
|
|
module L = Logging
|
|
|
|
module Dom = BufferOverrunDomain
|
|
|
|
module Trace = BufferOverrunTrace
|
|
|
|
module TraceSet = Trace.Set
|
|
|
|
|
|
|
|
module Summary = Summary.Make (struct
|
|
|
|
type payload = Dom.Summary.t
|
|
|
|
|
|
|
|
let update_payload astate (summary: Specs.summary) =
|
|
|
|
{summary with payload= {summary.payload with buffer_overrun= Some astate}}
|
|
|
|
|
|
|
|
let read_payload (summary: Specs.summary) = summary.payload.buffer_overrun
|
|
|
|
end)
|
|
|
|
|
|
|
|
module TransferFunctions (CFG : ProcCfg.S) = struct
|
|
|
|
module CFG = CFG
|
|
|
|
module Domain = Dom.Mem
|
|
|
|
module Sem = BufferOverrunSemantics.Make (CFG)
|
|
|
|
|
|
|
|
type extras = Typ.Procname.t -> Procdesc.t option
|
|
|
|
|
|
|
|
let set_uninitialized node (typ: Typ.t) loc mem =
|
|
|
|
match typ.desc with
|
|
|
|
| Tint _ | Tfloat _
|
|
|
|
-> Dom.Mem.weak_update_heap loc Dom.Val.Itv.top mem
|
|
|
|
| _
|
|
|
|
-> L.(debug BufferOverrun Verbose)
|
|
|
|
"/!\\ Do not know how to uninitialize type %a at %a@\n" (Typ.pp Pp.text) typ Location.pp
|
|
|
|
(CFG.loc node) ;
|
|
|
|
mem
|
|
|
|
|
|
|
|
(* NOTE: heuristic *)
|
|
|
|
let get_malloc_info : Exp.t -> Typ.t * Int.t option * Exp.t = function
|
|
|
|
| Exp.BinOp (Binop.Mult, Exp.Sizeof {typ; nbytes}, length)
|
|
|
|
| Exp.BinOp (Binop.Mult, length, Exp.Sizeof {typ; nbytes})
|
|
|
|
-> (typ, nbytes, length)
|
|
|
|
| Exp.Sizeof {typ; nbytes}
|
|
|
|
-> (typ, nbytes, Exp.one)
|
|
|
|
| x
|
|
|
|
-> (Typ.mk (Typ.Tint Typ.IChar), Some 1, x)
|
|
|
|
|
|
|
|
let model_malloc
|
|
|
|
: Typ.Procname.t -> (Ident.t * Typ.t) option -> (Exp.t * Typ.t) list -> CFG.node
|
|
|
|
-> Location.t -> Dom.Mem.astate -> Dom.Mem.astate =
|
|
|
|
fun pname ret params node location mem ->
|
|
|
|
match ret with
|
|
|
|
| Some (id, _)
|
|
|
|
-> let typ, stride, length0 = get_malloc_info (List.hd_exn params |> fst) in
|
|
|
|
let length = Sem.eval length0 mem (CFG.loc node) in
|
|
|
|
let traces = TraceSet.add_elem (Trace.ArrDecl location) (Dom.Val.get_traces length) in
|
|
|
|
let v =
|
|
|
|
Sem.eval_array_alloc pname node typ ?stride Itv.zero (Dom.Val.get_itv length) 0 1
|
|
|
|
|> Dom.Val.set_traces traces
|
|
|
|
in
|
|
|
|
mem |> Dom.Mem.add_stack (Loc.of_id id) v
|
|
|
|
|> set_uninitialized node typ (Dom.Val.get_array_locs v)
|
|
|
|
| _
|
|
|
|
-> L.(debug BufferOverrun Verbose)
|
|
|
|
"/!\\ Do not know where to model malloc at %a@\n" Location.pp (CFG.loc node) ;
|
|
|
|
mem
|
|
|
|
|
|
|
|
let model_realloc
|
|
|
|
: Typ.Procname.t -> (Ident.t * Typ.t) option -> (Exp.t * Typ.t) list -> CFG.node
|
|
|
|
-> Location.t -> Dom.Mem.astate -> Dom.Mem.astate =
|
|
|
|
fun pname ret params node location mem ->
|
|
|
|
model_malloc pname ret (List.tl_exn params) node location mem
|
|
|
|
|
|
|
|
let model_by_value value ret mem =
|
|
|
|
match ret with
|
|
|
|
| Some (id, _)
|
|
|
|
-> Dom.Mem.add_stack (Loc.of_id id) value mem
|
|
|
|
| None
|
|
|
|
-> L.(debug BufferOverrun Verbose)
|
|
|
|
"/!\\ Do not know where to model value %a@\n" Dom.Val.pp value ;
|
|
|
|
mem
|
|
|
|
|
|
|
|
let model_infer_print : (Exp.t * Typ.t) list -> Dom.Mem.astate -> Location.t -> Dom.Mem.astate =
|
|
|
|
fun params mem loc ->
|
|
|
|
match params with
|
|
|
|
| (e, _) :: _
|
|
|
|
-> L.(debug BufferOverrun Medium)
|
|
|
|
"@[<v>=== Infer Print === at %a@,%a@]%!" Location.pp loc Dom.Val.pp
|
|
|
|
(Sem.eval e mem loc) ;
|
|
|
|
mem
|
|
|
|
| _
|
|
|
|
-> mem
|
|
|
|
|
|
|
|
let model_infer_set_array_length pname node params mem loc =
|
|
|
|
match params with
|
|
|
|
| [(Exp.Lvar array_pvar, {Typ.desc= Typ.Tarray (typ, _, stride0)}); (length_exp, _)]
|
|
|
|
-> let length = Sem.eval length_exp mem loc |> Dom.Val.get_itv in
|
|
|
|
let stride = Option.map ~f:IntLit.to_int stride0 in
|
|
|
|
let v = Sem.eval_array_alloc pname node typ ?stride Itv.zero length 0 1 in
|
|
|
|
mem |> Dom.Mem.add_stack (Loc.of_pvar array_pvar) v
|
|
|
|
|> set_uninitialized node typ (Dom.Val.get_array_locs v)
|
|
|
|
| [_; _]
|
|
|
|
-> failwithf "Unexpected type of arguments for __set_array_length()"
|
|
|
|
| _
|
|
|
|
-> failwithf "Unexpected number of arguments for __set_array_length()"
|
|
|
|
|
|
|
|
let handle_unknown_call
|
|
|
|
: Typ.Procname.t -> (Ident.t * Typ.t) option -> Typ.Procname.t -> (Exp.t * Typ.t) list
|
|
|
|
-> CFG.node -> Dom.Mem.astate -> Location.t -> Dom.Mem.astate =
|
|
|
|
fun pname ret callee_pname params node mem loc ->
|
|
|
|
match Typ.Procname.get_method callee_pname with
|
|
|
|
| "__exit" | "exit"
|
|
|
|
-> Dom.Mem.Bottom
|
|
|
|
| "fgetc"
|
|
|
|
-> model_by_value Dom.Val.Itv.m1_255 ret mem
|
|
|
|
| "infer_print"
|
|
|
|
-> model_infer_print params mem loc
|
|
|
|
| "malloc" | "__new_array"
|
|
|
|
-> model_malloc pname ret params node loc mem
|
|
|
|
| "realloc"
|
|
|
|
-> model_realloc pname ret params node loc mem
|
|
|
|
| "__set_array_length"
|
|
|
|
-> model_infer_set_array_length pname node params mem loc
|
|
|
|
| "strlen"
|
|
|
|
-> model_by_value Dom.Val.Itv.nat ret mem
|
|
|
|
| proc_name
|
|
|
|
-> L.(debug BufferOverrun Verbose)
|
|
|
|
"/!\\ Unknown call to %s at %a@\n" proc_name Location.pp loc ;
|
|
|
|
model_by_value Dom.Val.unknown ret mem |> Dom.Mem.add_heap Loc.unknown Dom.Val.unknown
|
|
|
|
|
|
|
|
let rec declare_array
|
|
|
|
: Typ.Procname.t -> CFG.node -> Location.t -> Loc.t -> Typ.t -> length:IntLit.t option
|
|
|
|
-> ?stride:int -> inst_num:int -> dimension:int -> Dom.Mem.astate -> Dom.Mem.astate =
|
|
|
|
fun pname node location loc typ ~length ?stride ~inst_num ~dimension mem ->
|
|
|
|
let size = Option.value_map ~default:Itv.top ~f:Itv.of_int_lit length in
|
|
|
|
let arr =
|
|
|
|
Sem.eval_array_alloc pname node typ Itv.zero size ?stride inst_num dimension
|
|
|
|
|> Dom.Val.add_trace_elem (Trace.ArrDecl location)
|
|
|
|
in
|
|
|
|
let mem =
|
|
|
|
if Int.equal dimension 1 then Dom.Mem.add_stack loc arr mem
|
|
|
|
else Dom.Mem.add_heap loc arr mem
|
|
|
|
in
|
|
|
|
let loc = Loc.of_allocsite (Sem.get_allocsite pname node inst_num dimension) in
|
|
|
|
match typ.Typ.desc with
|
|
|
|
| Typ.Tarray (typ, length, stride)
|
|
|
|
-> declare_array pname node location loc typ ~length
|
|
|
|
?stride:(Option.map ~f:IntLit.to_int stride) ~inst_num ~dimension:(dimension + 1) mem
|
|
|
|
| _
|
|
|
|
-> mem
|
|
|
|
|
|
|
|
let counter_gen init =
|
|
|
|
let num_ref = ref init in
|
|
|
|
let get_num () =
|
|
|
|
let v = !num_ref in
|
|
|
|
num_ref := v + 1 ;
|
|
|
|
v
|
|
|
|
in
|
|
|
|
get_num
|
|
|
|
|
|
|
|
let declare_symbolic_val
|
|
|
|
: Typ.Procname.t -> Tenv.t -> CFG.node -> Location.t -> Loc.t -> Typ.typ -> inst_num:int
|
|
|
|
-> new_sym_num:(unit -> int) -> Domain.t -> Domain.t =
|
|
|
|
fun pname tenv node location loc typ ~inst_num ~new_sym_num mem ->
|
|
|
|
let max_depth = 2 in
|
|
|
|
let new_alloc_num = counter_gen 1 in
|
|
|
|
let rec decl_sym_val ~depth loc typ mem =
|
|
|
|
if depth > max_depth then mem
|
|
|
|
else
|
|
|
|
let depth = depth + 1 in
|
|
|
|
match typ.Typ.desc with
|
|
|
|
| Typ.Tint ikind
|
|
|
|
-> let unsigned = Typ.ikind_is_unsigned ikind in
|
|
|
|
let v =
|
|
|
|
Dom.Val.make_sym ~unsigned pname new_sym_num
|
|
|
|
|> Dom.Val.add_trace_elem (Trace.SymAssign location)
|
|
|
|
in
|
|
|
|
Dom.Mem.add_heap loc v mem
|
|
|
|
| Typ.Tfloat _
|
|
|
|
-> let v =
|
|
|
|
Dom.Val.make_sym pname new_sym_num
|
|
|
|
|> Dom.Val.add_trace_elem (Trace.SymAssign location)
|
|
|
|
in
|
|
|
|
Dom.Mem.add_heap loc v mem
|
|
|
|
| Typ.Tptr (typ, _)
|
|
|
|
-> decl_sym_arr ~depth loc location typ mem
|
|
|
|
| Typ.Tarray (typ, opt_int_lit, _)
|
|
|
|
-> let opt_size = Option.map ~f:Itv.of_int_lit opt_int_lit in
|
|
|
|
let opt_offset = Some Itv.zero in
|
|
|
|
decl_sym_arr ~depth loc location typ ~opt_offset ~opt_size mem
|
|
|
|
| Typ.Tstruct typename
|
|
|
|
-> let decl_fld mem (fn, typ, _) =
|
|
|
|
let loc_fld = Loc.append_field loc fn in
|
|
|
|
decl_sym_val ~depth loc_fld typ mem
|
|
|
|
in
|
|
|
|
let decl_flds str = List.fold ~f:decl_fld ~init:mem str.Typ.Struct.fields in
|
|
|
|
let opt_struct = Tenv.lookup tenv typename in
|
|
|
|
Option.value_map opt_struct ~default:mem ~f:decl_flds
|
|
|
|
| _
|
|
|
|
-> if Config.bo_debug >= 3 then
|
|
|
|
L.(debug BufferOverrun Verbose)
|
|
|
|
"/!\\ decl_fld of unhandled type: %a at %a@." (Typ.pp Pp.text) typ Location.pp
|
|
|
|
(CFG.loc node) ;
|
|
|
|
mem
|
|
|
|
and decl_sym_arr ~depth loc location typ ?(opt_offset= None) ?(opt_size= None) mem =
|
|
|
|
let option_value opt_x default_f = match opt_x with Some x -> x | None -> default_f () in
|
|
|
|
let itv_make_sym () = Itv.make_sym pname new_sym_num in
|
|
|
|
let offset = option_value opt_offset itv_make_sym in
|
|
|
|
let size = option_value opt_size itv_make_sym in
|
|
|
|
let alloc_num = new_alloc_num () in
|
|
|
|
let elem = Trace.SymAssign location in
|
|
|
|
let arr =
|
|
|
|
Sem.eval_array_alloc pname node typ offset size inst_num alloc_num
|
|
|
|
|> Dom.Val.add_trace_elem elem
|
|
|
|
in
|
|
|
|
let mem = Dom.Mem.add_heap loc arr mem in
|
|
|
|
let deref_loc = Loc.of_allocsite (Sem.get_allocsite pname node inst_num alloc_num) in
|
|
|
|
decl_sym_val ~depth deref_loc typ mem
|
|
|
|
in
|
|
|
|
decl_sym_val ~depth:0 loc typ mem
|
|
|
|
|
|
|
|
let declare_symbolic_parameter
|
|
|
|
: Procdesc.t -> Tenv.t -> CFG.node -> Location.t -> int -> Dom.Mem.astate -> Dom.Mem.astate =
|
|
|
|
fun pdesc tenv node location inst_num mem ->
|
|
|
|
let pname = Procdesc.get_proc_name pdesc in
|
|
|
|
let new_sym_num = counter_gen 0 in
|
|
|
|
let add_formal (mem, inst_num) (pvar, typ) =
|
|
|
|
let loc = Loc.of_pvar pvar in
|
|
|
|
let mem =
|
|
|
|
declare_symbolic_val pname tenv node location loc typ ~inst_num ~new_sym_num mem
|
|
|
|
in
|
|
|
|
(mem, inst_num + 1)
|
|
|
|
in
|
|
|
|
List.fold ~f:add_formal ~init:(mem, inst_num) (Sem.get_formals pdesc) |> fst
|
|
|
|
|
|
|
|
let instantiate_ret ret callee_pname callee_exit_mem subst_map mem ret_alias loc =
|
|
|
|
match ret with
|
|
|
|
| Some (id, _)
|
|
|
|
-> let ret_loc = Loc.of_pvar (Pvar.get_ret_pvar callee_pname) in
|
|
|
|
let ret_val = Dom.Mem.find_heap ret_loc callee_exit_mem in
|
|
|
|
let ret_var = Loc.of_var (Var.of_id id) in
|
|
|
|
let add_ret_alias l = Dom.Mem.load_alias id l mem in
|
|
|
|
let mem = Option.value_map ret_alias ~default:mem ~f:add_ret_alias in
|
|
|
|
Dom.Val.subst ret_val subst_map loc |> Dom.Val.add_trace_elem (Trace.Return loc)
|
|
|
|
|> Fn.flip (Dom.Mem.add_stack ret_var) mem
|
|
|
|
| None
|
|
|
|
-> mem
|
|
|
|
|
|
|
|
let instantiate_param tenv pdesc params callee_entry_mem callee_exit_mem subst_map location mem =
|
|
|
|
let formals = Sem.get_formals pdesc in
|
|
|
|
let actuals = List.map ~f:(fun (a, _) -> Sem.eval a mem location) params in
|
|
|
|
let f mem formal actual =
|
|
|
|
match (snd formal).Typ.desc with
|
|
|
|
| Typ.Tptr (typ, _) -> (
|
|
|
|
match typ.Typ.desc with
|
|
|
|
| Typ.Tstruct typename -> (
|
|
|
|
match Tenv.lookup tenv typename with
|
|
|
|
| Some str
|
|
|
|
-> let formal_locs =
|
|
|
|
Dom.Mem.find_heap (Loc.of_pvar (fst formal)) callee_entry_mem
|
|
|
|
|> Dom.Val.get_array_blk |> ArrayBlk.get_pow_loc
|
|
|
|
in
|
|
|
|
let instantiate_fld mem (fn, _, _) =
|
|
|
|
let formal_fields = PowLoc.append_field formal_locs fn in
|
|
|
|
let v = Dom.Mem.find_heap_set formal_fields callee_exit_mem in
|
|
|
|
let actual_fields = PowLoc.append_field (Dom.Val.get_all_locs actual) fn in
|
|
|
|
Dom.Val.subst v subst_map location
|
|
|
|
|> Fn.flip (Dom.Mem.strong_update_heap actual_fields) mem
|
|
|
|
in
|
|
|
|
List.fold ~f:instantiate_fld ~init:mem str.Typ.Struct.fields
|
|
|
|
| _
|
|
|
|
-> mem )
|
|
|
|
| _
|
|
|
|
-> let formal_locs =
|
|
|
|
Dom.Mem.find_heap (Loc.of_pvar (fst formal)) callee_entry_mem
|
|
|
|
|> Dom.Val.get_array_blk |> ArrayBlk.get_pow_loc
|
|
|
|
in
|
|
|
|
let v = Dom.Mem.find_heap_set formal_locs callee_exit_mem in
|
|
|
|
let actual_locs = Dom.Val.get_all_locs actual in
|
|
|
|
Dom.Val.subst v subst_map location
|
|
|
|
|> Fn.flip (Dom.Mem.strong_update_heap actual_locs) mem )
|
|
|
|
| _
|
|
|
|
-> mem
|
|
|
|
in
|
|
|
|
try List.fold2_exn formals actuals ~init:mem ~f
|
|
|
|
with Invalid_argument _ -> mem
|
|
|
|
|
|
|
|
let instantiate_mem
|
|
|
|
: Tenv.t -> (Ident.t * Typ.t) option -> Procdesc.t option -> Typ.Procname.t
|
|
|
|
-> (Exp.t * Typ.t) list -> Dom.Mem.astate -> Dom.Summary.t -> Location.t -> Dom.Mem.astate =
|
|
|
|
fun tenv ret callee_pdesc callee_pname params caller_mem summary loc ->
|
|
|
|
let callee_entry_mem = Dom.Summary.get_input summary in
|
|
|
|
let callee_exit_mem = Dom.Summary.get_output summary in
|
|
|
|
let callee_ret_alias = Dom.Mem.find_ret_alias callee_exit_mem in
|
|
|
|
match callee_pdesc with
|
|
|
|
| Some pdesc
|
|
|
|
-> let subst_map, ret_alias =
|
|
|
|
Sem.get_subst_map tenv pdesc params caller_mem callee_entry_mem ~callee_ret_alias loc
|
|
|
|
in
|
|
|
|
instantiate_ret ret callee_pname callee_exit_mem subst_map caller_mem ret_alias loc
|
|
|
|
|> instantiate_param tenv pdesc params callee_entry_mem callee_exit_mem subst_map loc
|
|
|
|
| None
|
|
|
|
-> caller_mem
|
|
|
|
|
|
|
|
let print_debug_info : Sil.instr -> Dom.Mem.astate -> Dom.Mem.astate -> unit =
|
|
|
|
fun instr pre post ->
|
|
|
|
L.(debug BufferOverrun Verbose) "@\n@\n================================@\n" ;
|
|
|
|
L.(debug BufferOverrun Verbose) "@[<v 2>Pre-state : @,%a" Dom.Mem.pp pre ;
|
|
|
|
L.(debug BufferOverrun Verbose) "@]@\n@\n%a" (Sil.pp_instr Pp.text) instr ;
|
|
|
|
L.(debug BufferOverrun Verbose) "@\n@\n" ;
|
|
|
|
L.(debug BufferOverrun Verbose) "@[<v 2>Post-state : @,%a" Dom.Mem.pp post ;
|
|
|
|
L.(debug BufferOverrun Verbose) "@]@\n" ;
|
|
|
|
L.(debug BufferOverrun Verbose) "================================@\n@."
|
|
|
|
|
|
|
|
let exec_instr : Dom.Mem.astate -> extras ProcData.t -> CFG.node -> Sil.instr -> Dom.Mem.astate =
|
|
|
|
fun mem {pdesc; tenv; extras} node instr ->
|
|
|
|
let pname = Procdesc.get_proc_name pdesc in
|
|
|
|
let output_mem =
|
|
|
|
match instr with
|
|
|
|
| Load (id, exp, _, loc)
|
|
|
|
-> let locs = Sem.eval exp mem loc |> Dom.Val.get_all_locs in
|
|
|
|
let v = Dom.Mem.find_heap_set locs mem in
|
|
|
|
if Ident.is_none id then mem
|
|
|
|
else
|
|
|
|
let mem = Dom.Mem.add_stack (Loc.of_var (Var.of_id id)) v mem in
|
|
|
|
if PowLoc.is_singleton locs then Dom.Mem.load_alias id (PowLoc.choose locs) mem
|
|
|
|
else mem
|
|
|
|
| Store (exp1, _, exp2, loc)
|
|
|
|
-> let locs = Sem.eval exp1 mem loc |> Dom.Val.get_all_locs in
|
|
|
|
let v = Sem.eval exp2 mem loc |> Dom.Val.add_trace_elem (Trace.Assign loc) in
|
|
|
|
let mem = Dom.Mem.update_mem locs v mem in
|
|
|
|
if PowLoc.is_singleton locs then Dom.Mem.store_alias (PowLoc.choose locs) exp2 mem
|
|
|
|
else mem
|
|
|
|
| Prune (exp, loc, _, _)
|
|
|
|
-> Sem.prune exp loc mem
|
|
|
|
| Call (ret, Const Cfun callee_pname, params, loc, _) -> (
|
|
|
|
match Summary.read_summary pdesc callee_pname with
|
|
|
|
| Some summary
|
|
|
|
-> let callee = extras callee_pname in
|
|
|
|
instantiate_mem tenv ret callee callee_pname params mem summary loc
|
|
|
|
| None
|
|
|
|
-> handle_unknown_call pname ret callee_pname params node mem loc )
|
|
|
|
| Declare_locals (locals, location)
|
|
|
|
-> (* array allocation in stack e.g., int arr[10] *)
|
|
|
|
let try_decl_arr location (mem, inst_num) (pvar, typ) =
|
|
|
|
match typ.Typ.desc with
|
|
|
|
| Typ.Tarray (typ, length, stride0)
|
|
|
|
-> let loc = Loc.of_pvar pvar in
|
|
|
|
let stride = Option.map ~f:IntLit.to_int stride0 in
|
|
|
|
let mem =
|
|
|
|
declare_array pname node location loc typ ~length ?stride ~inst_num
|
|
|
|
~dimension:1 mem
|
|
|
|
in
|
|
|
|
(mem, inst_num + 1)
|
|
|
|
| _
|
|
|
|
-> (mem, inst_num)
|
|
|
|
in
|
|
|
|
let mem, inst_num = List.fold ~f:(try_decl_arr location) ~init:(mem, 1) locals in
|
|
|
|
declare_symbolic_parameter pdesc tenv node location inst_num mem
|
|
|
|
| Call (_, fun_exp, _, loc, _)
|
|
|
|
-> let () =
|
|
|
|
L.(debug BufferOverrun Verbose)
|
|
|
|
"/!\\ Call to non-const function %a at %a" Exp.pp fun_exp Location.pp loc
|
|
|
|
in
|
|
|
|
mem
|
|
|
|
| Remove_temps (temps, _)
|
|
|
|
-> Dom.Mem.remove_temps temps mem
|
|
|
|
| Abstract _ | Nullify _
|
|
|
|
-> mem
|
|
|
|
in
|
|
|
|
print_debug_info instr mem output_mem ; output_mem
|
|
|
|
end
|
|
|
|
|
|
|
|
module Analyzer = AbstractInterpreter.Make (ProcCfg.Normal) (TransferFunctions)
|
|
|
|
module CFG = Analyzer.TransferFunctions.CFG
|
|
|
|
module Sem = BufferOverrunSemantics.Make (CFG)
|
|
|
|
|
|
|
|
module Report = struct
|
|
|
|
type extras = Typ.Procname.t -> Procdesc.t option
|
|
|
|
|
|
|
|
let add_condition
|
|
|
|
: Typ.Procname.t -> CFG.node -> Exp.t -> Location.t -> Dom.Mem.astate -> Dom.ConditionSet.t
|
|
|
|
-> Dom.ConditionSet.t =
|
|
|
|
fun pname node exp loc mem cond_set ->
|
|
|
|
let array_access =
|
|
|
|
match exp with
|
|
|
|
| Exp.Var _
|
|
|
|
-> let v = Sem.eval exp mem loc in
|
|
|
|
let arr = Dom.Val.get_array_blk v in
|
|
|
|
let arr_traces = Dom.Val.get_traces v in
|
|
|
|
Some (arr, arr_traces, Itv.zero, TraceSet.empty, true)
|
|
|
|
| Exp.Lindex (e1, e2)
|
|
|
|
-> let locs = Sem.eval_locs e1 mem loc |> Dom.Val.get_all_locs in
|
|
|
|
let v_arr = Dom.Mem.find_set locs mem in
|
|
|
|
let arr = Dom.Val.get_array_blk v_arr in
|
|
|
|
let arr_traces = Dom.Val.get_traces v_arr in
|
|
|
|
let v_idx = Sem.eval e2 mem loc in
|
|
|
|
let idx = Dom.Val.get_itv v_idx in
|
|
|
|
let idx_traces = Dom.Val.get_traces v_idx in
|
|
|
|
Some (arr, arr_traces, idx, idx_traces, true)
|
|
|
|
| Exp.BinOp ((Binop.PlusA as bop), e1, e2) | Exp.BinOp ((Binop.MinusA as bop), e1, e2)
|
|
|
|
-> let v_arr = Sem.eval e1 mem loc in
|
|
|
|
let arr = Dom.Val.get_array_blk v_arr in
|
|
|
|
let arr_traces = Dom.Val.get_traces v_arr in
|
|
|
|
let v_idx = Sem.eval e2 mem loc in
|
|
|
|
let idx = Dom.Val.get_itv v_idx in
|
|
|
|
let idx_traces = Dom.Val.get_traces v_idx in
|
|
|
|
let is_plus = Binop.equal bop Binop.PlusA in
|
|
|
|
Some (arr, arr_traces, idx, idx_traces, is_plus)
|
|
|
|
| _
|
|
|
|
-> None
|
|
|
|
in
|
|
|
|
match array_access with
|
|
|
|
| Some (arr, traces_arr, idx, traces_idx, is_plus)
|
|
|
|
-> let site = Sem.get_allocsite pname node 0 0 in
|
|
|
|
let size = ArrayBlk.sizeof arr in
|
|
|
|
let offset = ArrayBlk.offsetof arr in
|
|
|
|
let idx = (if is_plus then Itv.plus else Itv.minus) offset idx in
|
|
|
|
L.(debug BufferOverrun Verbose) "@[<v 2>Add condition :@," ;
|
|
|
|
L.(debug BufferOverrun Verbose) "array: %a@," ArrayBlk.pp arr ;
|
|
|
|
L.(debug BufferOverrun Verbose) " idx: %a@," Itv.pp idx ;
|
|
|
|
L.(debug BufferOverrun Verbose) "@]@." ;
|
|
|
|
if size <> Itv.bot && idx <> Itv.bot then
|
|
|
|
let traces = TraceSet.merge ~traces_arr ~traces_idx loc in
|
|
|
|
Dom.ConditionSet.add_bo_safety pname loc site ~size ~idx traces cond_set
|
|
|
|
else cond_set
|
|
|
|
| None
|
|
|
|
-> cond_set
|
|
|
|
|
|
|
|
let instantiate_cond
|
|
|
|
: Tenv.t -> Typ.Procname.t -> Procdesc.t option -> (Exp.t * Typ.t) list -> Dom.Mem.astate
|
|
|
|
-> Summary.payload -> Location.t -> Dom.ConditionSet.t =
|
|
|
|
fun tenv caller_pname callee_pdesc params caller_mem summary loc ->
|
|
|
|
let callee_entry_mem = Dom.Summary.get_input summary in
|
|
|
|
let callee_cond = Dom.Summary.get_cond_set summary in
|
|
|
|
match callee_pdesc with
|
|
|
|
| Some pdesc
|
|
|
|
-> let subst_map, _ =
|
|
|
|
Sem.get_subst_map tenv pdesc params caller_mem callee_entry_mem ~callee_ret_alias:None
|
|
|
|
loc
|
|
|
|
in
|
|
|
|
let pname = Procdesc.get_proc_name pdesc in
|
|
|
|
Dom.ConditionSet.subst callee_cond subst_map caller_pname pname loc
|
|
|
|
| _
|
|
|
|
-> callee_cond
|
|
|
|
|
|
|
|
let print_debug_info : Sil.instr -> Dom.Mem.astate -> Dom.ConditionSet.t -> unit =
|
|
|
|
fun instr pre cond_set ->
|
|
|
|
L.(debug BufferOverrun Verbose) "@\n@\n================================@\n" ;
|
|
|
|
L.(debug BufferOverrun Verbose) "@[<v 2>Pre-state : @,%a" Dom.Mem.pp pre ;
|
|
|
|
L.(debug BufferOverrun Verbose) "@]@\n@\n%a" (Sil.pp_instr Pp.text) instr ;
|
|
|
|
L.(debug BufferOverrun Verbose) "@[<v 2>@\n@\n%a" Dom.ConditionSet.pp cond_set ;
|
|
|
|
L.(debug BufferOverrun Verbose) "@]@\n" ;
|
|
|
|
L.(debug BufferOverrun Verbose) "================================@\n@."
|
|
|
|
|
|
|
|
let is_last_statement_of_if_branch rem_instrs node =
|
|
|
|
if rem_instrs <> [] then false
|
|
|
|
else
|
|
|
|
match Procdesc.Node.get_succs node with
|
|
|
|
| [succ] -> (
|
|
|
|
match Procdesc.Node.get_preds succ with _ :: _ :: _ -> true | _ -> false )
|
|
|
|
| _
|
|
|
|
-> false
|
|
|
|
|
|
|
|
let rec collect_instrs
|
|
|
|
: extras ProcData.t -> CFG.node -> Sil.instr list -> Dom.Mem.astate -> Dom.ConditionSet.t
|
|
|
|
-> Dom.ConditionSet.t =
|
|
|
|
fun ({pdesc; tenv; extras} as pdata) node instrs mem cond_set ->
|
|
|
|
match instrs with
|
|
|
|
| []
|
|
|
|
-> cond_set
|
|
|
|
| instr :: rem_instrs
|
|
|
|
-> let pname = Procdesc.get_proc_name pdesc in
|
|
|
|
let cond_set =
|
|
|
|
match instr with
|
|
|
|
| Sil.Load (_, exp, _, loc) | Sil.Store (exp, _, _, loc)
|
|
|
|
-> add_condition pname node exp loc mem cond_set
|
|
|
|
| Sil.Call (_, Const Cfun callee_pname, params, loc, _) -> (
|
|
|
|
match Summary.read_summary pdesc callee_pname with
|
|
|
|
| Some summary
|
|
|
|
-> let callee = extras callee_pname in
|
|
|
|
instantiate_cond tenv pname callee params mem summary loc
|
|
|
|
|> Dom.ConditionSet.rm_invalid |> Dom.ConditionSet.join cond_set
|
|
|
|
| _
|
|
|
|
-> cond_set )
|
|
|
|
| _
|
|
|
|
-> cond_set
|
|
|
|
in
|
|
|
|
let mem' = Analyzer.TransferFunctions.exec_instr mem pdata node instr in
|
|
|
|
let () =
|
|
|
|
match (mem, mem') with
|
|
|
|
| NonBottom _, Bottom -> (
|
|
|
|
match instr with
|
|
|
|
| Sil.Prune (_, _, _, (Ik_land_lor | Ik_bexp))
|
|
|
|
-> ()
|
|
|
|
| Sil.Prune (cond, loc, true_branch, _)
|
|
|
|
-> let i = match cond with Exp.Const Const.Cint i -> i | _ -> IntLit.zero in
|
|
|
|
let desc = Errdesc.explain_condition_always_true_false tenv i cond node loc in
|
|
|
|
let exn =
|
|
|
|
Exceptions.Condition_always_true_false (desc, not true_branch, __POS__)
|
|
|
|
in
|
|
|
|
Reporting.log_warning_deprecated pname ~loc exn
|
|
|
|
| Sil.Call (_, Const Cfun pname, _, _, _)
|
|
|
|
when String.equal (Typ.Procname.get_method pname) "exit"
|
|
|
|
&& is_last_statement_of_if_branch rem_instrs node
|
|
|
|
-> ()
|
|
|
|
| _
|
|
|
|
-> let loc = Sil.instr_get_loc instr in
|
|
|
|
let desc = Errdesc.explain_unreachable_code_after loc in
|
|
|
|
let exn = Exceptions.Unreachable_code_after (desc, __POS__) in
|
|
|
|
Reporting.log_error_deprecated pname ~loc exn )
|
|
|
|
| _
|
|
|
|
-> ()
|
|
|
|
in
|
|
|
|
print_debug_info instr mem' cond_set ; collect_instrs pdata node rem_instrs mem' cond_set
|
|
|
|
|
|
|
|
let collect_node
|
|
|
|
: extras ProcData.t -> Analyzer.invariant_map -> Dom.ConditionSet.t -> CFG.node
|
|
|
|
-> Dom.ConditionSet.t =
|
|
|
|
fun pdata inv_map cond_set node ->
|
|
|
|
match Analyzer.extract_pre (CFG.id node) inv_map with
|
|
|
|
| Some mem
|
|
|
|
-> let instrs = CFG.instrs node in
|
|
|
|
collect_instrs pdata node instrs mem cond_set
|
|
|
|
| _
|
|
|
|
-> cond_set
|
|
|
|
|
|
|
|
let collect : extras ProcData.t -> Analyzer.invariant_map -> Dom.ConditionSet.t =
|
|
|
|
fun ({pdesc} as pdata) inv_map ->
|
|
|
|
let add_node1 acc node = collect_node pdata inv_map acc node in
|
|
|
|
Procdesc.fold_nodes add_node1 Dom.ConditionSet.empty pdesc
|
|
|
|
|
|
|
|
let make_err_trace : Trace.t -> string -> Errlog.loc_trace =
|
|
|
|
fun trace desc ->
|
|
|
|
let f elem (trace, depth) =
|
|
|
|
match elem with
|
|
|
|
| Trace.Assign loc
|
|
|
|
-> (Errlog.make_trace_element depth loc "Assignment" [] :: trace, depth)
|
|
|
|
| Trace.ArrDecl loc
|
|
|
|
-> (Errlog.make_trace_element depth loc "ArrayDeclaration" [] :: trace, depth)
|
|
|
|
| Trace.Call loc
|
|
|
|
-> (Errlog.make_trace_element depth loc "Call" [] :: trace, depth + 1)
|
|
|
|
| Trace.Return loc
|
|
|
|
-> (Errlog.make_trace_element (depth - 1) loc "Return" [] :: trace, depth - 1)
|
|
|
|
| Trace.SymAssign _
|
|
|
|
-> (trace, depth)
|
|
|
|
| Trace.ArrAccess loc
|
|
|
|
-> (Errlog.make_trace_element depth loc ("ArrayAccess: " ^ desc) [] :: trace, depth)
|
|
|
|
in
|
|
|
|
List.fold_right ~f ~init:([], 0) trace.trace |> fst |> List.rev
|
|
|
|
|
|
|
|
let report_error : Procdesc.t -> Dom.ConditionSet.t -> unit =
|
|
|
|
fun pdesc conds ->
|
|
|
|
let pname = Procdesc.get_proc_name pdesc in
|
|
|
|
let report1 cond =
|
|
|
|
let alarm = Dom.Condition.check cond in
|
|
|
|
let caller_pname, loc =
|
|
|
|
match Dom.Condition.get_cond_trace cond with
|
|
|
|
| Dom.Condition.Inter (caller_pname, _, loc)
|
|
|
|
-> (caller_pname, loc)
|
|
|
|
| Dom.Condition.Intra pname
|
|
|
|
-> (pname, Dom.Condition.get_location cond)
|
|
|
|
in
|
|
|
|
match alarm with
|
|
|
|
| None
|
|
|
|
-> ()
|
|
|
|
| Some bucket when Typ.Procname.equal pname caller_pname
|
|
|
|
-> let description = Dom.Condition.to_string cond in
|
|
|
|
let error_desc = Localise.desc_buffer_overrun bucket description in
|
|
|
|
let exn =
|
|
|
|
Exceptions.Checkers (Localise.to_issue_id Localise.buffer_overrun, error_desc)
|
|
|
|
in
|
|
|
|
let trace =
|
|
|
|
match TraceSet.choose_shortest cond.Dom.Condition.traces with
|
|
|
|
| trace
|
|
|
|
-> make_err_trace trace description
|
|
|
|
| exception _
|
|
|
|
-> [Errlog.make_trace_element 0 loc description []]
|
|
|
|
in
|
|
|
|
Reporting.log_error_deprecated pname ~loc ~ltr:trace exn
|
|
|
|
| _
|
|
|
|
-> ()
|
|
|
|
in
|
|
|
|
Dom.ConditionSet.iter report1 conds
|
|
|
|
end
|
|
|
|
|
|
|
|
let compute_post : Analyzer.TransferFunctions.extras ProcData.t -> Summary.payload option =
|
|
|
|
fun {pdesc; tenv; extras= get_pdesc} ->
|
|
|
|
let cfg = CFG.from_pdesc pdesc in
|
|
|
|
let pdata = ProcData.make pdesc tenv get_pdesc in
|
|
|
|
let inv_map = Analyzer.exec_pdesc ~initial:Dom.Mem.init pdata in
|
|
|
|
let entry_mem =
|
|
|
|
let entry_id = CFG.id (CFG.start_node cfg) in
|
|
|
|
Analyzer.extract_post entry_id inv_map
|
|
|
|
in
|
|
|
|
let exit_mem =
|
|
|
|
let exit_id = CFG.id (CFG.exit_node cfg) in
|
|
|
|
Analyzer.extract_post exit_id inv_map
|
|
|
|
in
|
|
|
|
let cond_set = Report.collect pdata inv_map in
|
|
|
|
Report.report_error pdesc cond_set ;
|
|
|
|
match (entry_mem, exit_mem) with
|
|
|
|
| Some entry_mem, Some exit_mem
|
|
|
|
-> Some (entry_mem, exit_mem, cond_set)
|
|
|
|
| _
|
|
|
|
-> None
|
|
|
|
|
|
|
|
let print_summary : Typ.Procname.t -> Dom.Summary.t -> unit =
|
|
|
|
fun proc_name s ->
|
|
|
|
L.(debug BufferOverrun Medium)
|
|
|
|
"@\n@[<v 2>Summary of %a :@,%a@]@." Typ.Procname.pp proc_name Dom.Summary.pp_summary s
|
|
|
|
|
|
|
|
let checker : Callbacks.proc_callback_args -> Specs.summary =
|
|
|
|
fun {proc_desc; tenv; summary; get_proc_desc} ->
|
|
|
|
let proc_name = Specs.get_proc_name summary in
|
|
|
|
let proc_data = ProcData.make proc_desc tenv get_proc_desc in
|
|
|
|
if not (Procdesc.did_preanalysis proc_desc) then Preanal.do_liveness proc_desc tenv ;
|
|
|
|
match compute_post proc_data with
|
|
|
|
| Some post
|
|
|
|
-> if Config.bo_debug >= 1 then print_summary proc_name post ;
|
|
|
|
Summary.update_summary post summary
|
|
|
|
| None
|
|
|
|
-> summary
|