[pulse] split PulseDomain.ml

Summary:
Split into:
- `PulseDiagnostic`, formerly `PulseDomain.Diagnostic`
- `PulseOperations`, formerly `PulseDomain.Operations`

This breaks down the now quite large and complex PulseDomain.ml into
more manageable pieces. More importantly, it will allow us to build a
bigger pulse domain later, where elements of the domain are pairs of the
base domain that include a biabductive "footprint".

What's not as nice is that more of the interface of `PulseDomain` is
exposed, in particular `PulseDomain.Memory` and `PulseDomain.Stack`.
We'll have to be careful not to break abstraction barriers and prefer
`PulseOperations` to `PulseDomain` outside of the domain implementation.
OCaml forces us to do that because of the multi-file approach. It could
be solved by introducing pulse domains as a library but who has time for
that...

Sending early because rebasing that diff is painful.

Reviewed By: ngorogiannis

Differential Revision: D13537602

fbshipit-source-id: d211d6e84
master
Jules Villard 6 years ago committed by Facebook Github Bot
parent 4c1ee2a485
commit 11eca94be7

@ -9,7 +9,7 @@ module F = Format
open Result.Monad_infix open Result.Monad_infix
let report summary diagnostic = let report summary diagnostic =
let open PulseDomain.Diagnostic in let open PulseDiagnostic in
Reporting.log_error summary ~loc:(get_location diagnostic) ~ltr:(get_trace diagnostic) Reporting.log_error summary ~loc:(get_location diagnostic) ~ltr:(get_trace diagnostic)
(get_issue_type diagnostic) (get_message diagnostic) (get_issue_type diagnostic) (get_message diagnostic)
@ -47,29 +47,30 @@ module PulseTransferFunctions (CFG : ProcCfg.S) = struct
let crumb = PulseTrace.Assignment {lhs= lhs_access; location= loc} in let crumb = PulseTrace.Assignment {lhs= lhs_access; location= loc} in
match rhs_exp with match rhs_exp with
| AccessExpression rhs_access -> ( | AccessExpression rhs_access -> (
PulseDomain.read loc rhs_access astate PulseOperations.read loc rhs_access astate
>>= fun (astate, (rhs_addr, rhs_trace)) -> >>= fun (astate, (rhs_addr, rhs_trace)) ->
let return_addr_trace = (rhs_addr, crumb :: rhs_trace) in let return_addr_trace = (rhs_addr, crumb :: rhs_trace) in
PulseDomain.write loc lhs_access return_addr_trace astate PulseOperations.write loc lhs_access return_addr_trace astate
>>= fun astate -> >>= fun astate ->
match lhs_access with match lhs_access with
| Base (var, _) when Var.is_return var -> | Base (var, _) when Var.is_return var ->
PulseDomain.check_address_of_local_variable summary.Summary.proc_desc rhs_addr astate PulseOperations.check_address_of_local_variable summary.Summary.proc_desc rhs_addr
astate
| _ -> | _ ->
Ok astate ) Ok astate )
| Closure (pname, captured) -> | Closure (pname, captured) ->
PulseDomain.Closures.record loc lhs_access pname captured astate PulseOperations.Closures.record loc lhs_access pname captured astate
| Cast (_, e) -> | Cast (_, e) ->
exec_assign summary lhs_access e loc astate exec_assign summary lhs_access e loc astate
| _ -> | _ ->
PulseDomain.read_all loc (HilExp.get_access_exprs rhs_exp) astate PulseOperations.read_all loc (HilExp.get_access_exprs rhs_exp) astate
>>= PulseDomain.havoc [crumb] loc lhs_access >>= PulseOperations.havoc [crumb] loc lhs_access
let exec_call summary _ret (call : HilInstr.call) (actuals : HilExp.t list) _flags call_loc let exec_call summary _ret (call : HilInstr.call) (actuals : HilExp.t list) _flags call_loc
astate = astate =
let read_all args astate = let read_all args astate =
PulseDomain.read_all call_loc (List.concat_map args ~f:HilExp.get_access_exprs) astate PulseOperations.read_all call_loc (List.concat_map args ~f:HilExp.get_access_exprs) astate
in in
let crumb = PulseTrace.Call {f= `HilCall call; actuals; location= call_loc} in let crumb = PulseTrace.Call {f= `HilCall call; actuals; location= call_loc} in
match call with match call with
@ -80,7 +81,7 @@ module PulseTransferFunctions (CFG : ProcCfg.S) = struct
Ok astate Ok astate
| [AccessExpression destroyed_access] -> | [AccessExpression destroyed_access] ->
let destroyed_object = HilExp.AccessExpression.dereference destroyed_access in let destroyed_object = HilExp.AccessExpression.dereference destroyed_access in
PulseDomain.invalidate PulseOperations.invalidate
(CppDestructor (callee_pname, destroyed_object, call_loc)) (CppDestructor (callee_pname, destroyed_object, call_loc))
call_loc destroyed_object astate call_loc destroyed_object astate
| _ -> | _ ->
@ -89,7 +90,7 @@ module PulseTransferFunctions (CFG : ProcCfg.S) = struct
match actuals with match actuals with
| AccessExpression constructor_access :: rest -> | AccessExpression constructor_access :: rest ->
let constructed_object = HilExp.AccessExpression.dereference constructor_access in let constructed_object = HilExp.AccessExpression.dereference constructor_access in
PulseDomain.havoc [crumb] call_loc constructed_object astate >>= read_all rest PulseOperations.havoc [crumb] call_loc constructed_object astate >>= read_all rest
| _ -> | _ ->
Ok astate ) Ok astate )
| Direct (Typ.Procname.ObjC_Cpp callee_pname) | Direct (Typ.Procname.ObjC_Cpp callee_pname)
@ -104,8 +105,8 @@ module PulseTransferFunctions (CFG : ProcCfg.S) = struct
| [AccessExpression lhs; HilExp.AccessExpression rhs] -> | [AccessExpression lhs; HilExp.AccessExpression rhs] ->
let lhs_deref = HilExp.AccessExpression.dereference lhs in let lhs_deref = HilExp.AccessExpression.dereference lhs in
let rhs_deref = HilExp.AccessExpression.dereference rhs in let rhs_deref = HilExp.AccessExpression.dereference rhs in
PulseDomain.havoc [crumb] call_loc lhs_deref astate PulseOperations.havoc [crumb] call_loc lhs_deref astate
>>= fun astate -> PulseDomain.read call_loc rhs_deref astate >>| fst >>= fun astate -> PulseOperations.read call_loc rhs_deref astate >>| fst
| _ -> | _ ->
read_all actuals astate ) read_all actuals astate )
| _ -> | _ ->
@ -125,7 +126,7 @@ module PulseTransferFunctions (CFG : ProcCfg.S) = struct
match model with match model with
| None -> | None ->
exec_call summary ret call actuals flags call_loc astate exec_call summary ret call actuals flags call_loc astate
>>| PulseDomain.havoc_var >>| PulseOperations.havoc_var
[PulseTrace.Call {f= `HilCall call; actuals; location= call_loc}] [PulseTrace.Call {f= `HilCall call; actuals; location= call_loc}]
(fst ret) (fst ret)
| Some model -> | Some model ->
@ -138,11 +139,12 @@ module PulseTransferFunctions (CFG : ProcCfg.S) = struct
| Assign (lhs_access, rhs_exp, loc) -> | Assign (lhs_access, rhs_exp, loc) ->
exec_assign summary lhs_access rhs_exp loc astate |> check_error summary exec_assign summary lhs_access rhs_exp loc astate |> check_error summary
| Assume (condition, _, _, loc) -> | Assume (condition, _, _, loc) ->
PulseDomain.read_all loc (HilExp.get_access_exprs condition) astate |> check_error summary PulseOperations.read_all loc (HilExp.get_access_exprs condition) astate
|> check_error summary
| Call (ret, call, actuals, flags, loc) -> | Call (ret, call, actuals, flags, loc) ->
dispatch_call summary ret call actuals flags loc astate |> check_error summary dispatch_call summary ret call actuals flags loc astate |> check_error summary
| ExitScope (vars, _) -> | ExitScope (vars, _) ->
PulseDomain.remove_vars vars astate PulseOperations.remove_vars vars astate
let pp_session_name _node fmt = F.pp_print_string fmt "Pulse" let pp_session_name _node fmt = F.pp_print_string fmt "Pulse"

@ -0,0 +1,66 @@
(*
* Copyright (c) 2018-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 F = Format
module AbstractAddress = PulseDomain.AbstractAddress
type actor = {access_expr: HilExp.AccessExpression.t; location: Location.t} [@@deriving compare]
type t =
| AccessToInvalidAddress of
{ invalidated_by: PulseInvalidation.t
; accessed_by: actor
; trace: PulseTrace.t
; address: AbstractAddress.t }
| StackVariableAddressEscape of {variable: Var.t; location: Location.t}
let get_location = function
| AccessToInvalidAddress {accessed_by= {location}} | StackVariableAddressEscape {location} ->
location
let get_message = function
| AccessToInvalidAddress {accessed_by; invalidated_by; address; trace} ->
let pp_debug_address f =
if Config.debug_mode then F.fprintf f " (debug: %a)" AbstractAddress.pp address
in
F.asprintf "`%a` accesses address %a%a past its lifetime%t" HilExp.AccessExpression.pp
accessed_by.access_expr PulseTrace.pp_interesting_events trace PulseInvalidation.pp
invalidated_by pp_debug_address
| StackVariableAddressEscape {variable} ->
let pp_var f var =
if Var.is_cpp_temporary var then F.pp_print_string f "C++ temporary"
else F.fprintf f "stack variable `%a`" Var.pp var
in
F.asprintf "address of %a is returned by the function" pp_var variable
let get_trace = function
| AccessToInvalidAddress {accessed_by; invalidated_by; trace} ->
let invalidated_by_trace =
PulseInvalidation.get_location invalidated_by
|> Option.map ~f:(fun location ->
Errlog.make_trace_element 0 location
(F.asprintf "%a here" PulseInvalidation.pp invalidated_by)
[] )
|> Option.to_list
in
PulseTrace.make_errlog_trace ~depth:0 trace
@ invalidated_by_trace
@ [ Errlog.make_trace_element 0 accessed_by.location
(F.asprintf "accessed `%a` here" HilExp.AccessExpression.pp accessed_by.access_expr)
[] ]
| StackVariableAddressEscape _ ->
[]
let get_issue_type = function
| AccessToInvalidAddress {invalidated_by} ->
PulseInvalidation.issue_type_of_cause invalidated_by
| StackVariableAddressEscape _ ->
IssueType.stack_variable_address_escape

@ -0,0 +1,26 @@
(*
* Copyright (c) 2018-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
type actor = {access_expr: HilExp.AccessExpression.t; location: Location.t} [@@deriving compare]
type t =
| AccessToInvalidAddress of
{ invalidated_by: PulseInvalidation.t
; accessed_by: actor
; trace: PulseTrace.t
; address: PulseDomain.AbstractAddress.t }
| StackVariableAddressEscape of {variable: Var.t; location: Location.t}
val get_message : t -> string
val get_location : t -> Location.t
val get_issue_type : t -> IssueType.t
val get_trace : t -> Errlog.loc_trace

@ -263,8 +263,7 @@ module Stack = struct
let compare = compare ValueDomain.compare let compare = compare ValueDomain.compare
end end
(** the domain *) type t = {heap: Memory.t; stack: Stack.t} [@@deriving compare]
type astate = {heap: Memory.t; stack: Stack.t} [@@deriving compare]
let initial = let initial =
{ heap= { heap=
@ -274,10 +273,7 @@ let initial =
; stack= Stack.empty } ; stack= Stack.empty }
module Domain : AbstractDomain.S with type t = astate = struct let piecewise_lessthan lhs rhs =
type t = astate
let piecewise_lessthan lhs rhs =
Stack.( <= ) ~lhs:lhs.stack ~rhs:rhs.stack Stack.( <= ) ~lhs:lhs.stack ~rhs:rhs.stack
&& Memory.for_all && Memory.for_all
(fun addr_src (edges_lhs, attrs_lhs) -> (fun addr_src (edges_lhs, attrs_lhs) ->
@ -296,7 +292,7 @@ module Domain : AbstractDomain.S with type t = astate = struct
lhs.heap lhs.heap
module JoinState = struct module JoinState = struct
module AddressUnionSet = struct module AddressUnionSet = struct
module Set = PrettyPrintable.MakePPSet (AbstractAddress) module Set = PrettyPrintable.MakePPSet (AbstractAddress)
@ -417,8 +413,7 @@ module Domain : AbstractDomain.S with type t = astate = struct
in in
if has_converged then ( if has_converged then (
let pp_union_find_classes f subst = let pp_union_find_classes f subst =
Container.iter subst ~fold:AddressUF.fold_sets Container.iter subst ~fold:AddressUF.fold_sets ~f:(fun ((repr : AddressUF.Repr.t), set) ->
~f:(fun ((repr : AddressUF.Repr.t), set) ->
F.fprintf f "%a=%a@;" AbstractAddress.pp F.fprintf f "%a=%a@;" AbstractAddress.pp
(repr :> AbstractAddress.t) (repr :> AbstractAddress.t)
AddressUnionSet.pp set ) AddressUnionSet.pp set )
@ -431,9 +426,9 @@ module Domain : AbstractDomain.S with type t = astate = struct
in in
{heap; stack} ) {heap; stack} )
else normalize {state with astate= {state.astate with heap}} else normalize {state with astate= {state.astate with heap}}
end end
(** Given (** Given
- stacks S1, S2 : Var -> Address, - stacks S1, S2 : Var -> Address,
@ -453,7 +448,7 @@ module Domain : AbstractDomain.S with type t = astate = struct
reachable location are still reachable (up to the substitution) instead of only the locations reachable location are still reachable (up to the substitution) instead of only the locations
leading to invalid ones. leading to invalid ones.
*) *)
let join astate1 astate2 = let join astate1 astate2 =
if phys_equal astate1 astate2 then astate1 if phys_equal astate1 astate2 then astate1
else else
(* high-level idea: maintain some union-find data structure to identify locations in one heap (* high-level idea: maintain some union-find data structure to identify locations in one heap
@ -474,432 +469,16 @@ module Domain : AbstractDomain.S with type t = astate = struct
JoinState.from_astate_union astate1 astate2 |> JoinState.normalize JoinState.from_astate_union astate1 astate2 |> JoinState.normalize
(* TODO: this could be [piecewise_lessthan lhs' (join lhs rhs)] where [lhs'] is [lhs] renamed (* TODO: this could be [piecewise_lessthan lhs' (join lhs rhs)] where [lhs'] is [lhs] renamed
according to the unification discovered while joining [lhs] and [rhs]. *) according to the unification discovered while joining [lhs] and [rhs]. *)
let ( <= ) ~lhs ~rhs = phys_equal lhs rhs || piecewise_lessthan lhs rhs let ( <= ) ~lhs ~rhs = phys_equal lhs rhs || piecewise_lessthan lhs rhs
let max_widening = 5 let max_widening = 5
let widen ~prev ~next ~num_iters = let widen ~prev ~next ~num_iters =
(* widening is underapproximation for now... TODO *) (* widening is underapproximation for now... TODO *)
if num_iters > max_widening then prev if num_iters > max_widening then prev else if phys_equal prev next then prev else join prev next
else if phys_equal prev next then prev
else join prev next
let pp fmt {heap; stack} = let pp fmt {heap; stack} =
F.fprintf fmt "{@[<v1> heap=@[<hv>%a@];@;stack=@[<hv>%a@];@]}" Memory.pp heap Stack.pp stack F.fprintf fmt "{@[<v1> heap=@[<hv>%a@];@;stack=@[<hv>%a@];@]}" Memory.pp heap Stack.pp stack
end
(* {2 Access operations on the domain} *)
type actor = {access_expr: HilExp.AccessExpression.t; location: Location.t} [@@deriving compare]
module Diagnostic = struct
type t =
| AccessToInvalidAddress of
{ invalidated_by: Invalidation.t
; accessed_by: actor
; trace: PulseTrace.t
; address: AbstractAddress.t }
| StackVariableAddressEscape of {variable: Var.t; location: Location.t}
let get_location = function
| AccessToInvalidAddress {accessed_by= {location}} | StackVariableAddressEscape {location} ->
location
let get_message = function
| AccessToInvalidAddress {accessed_by; invalidated_by; address; trace} ->
let pp_debug_address f =
if Config.debug_mode then F.fprintf f " (debug: %a)" AbstractAddress.pp address
in
F.asprintf "`%a` accesses address %a%a past its lifetime%t" HilExp.AccessExpression.pp
accessed_by.access_expr PulseTrace.pp_interesting_events trace Invalidation.pp
invalidated_by pp_debug_address
| StackVariableAddressEscape {variable} ->
let pp_var f var =
if Var.is_cpp_temporary var then F.pp_print_string f "C++ temporary"
else F.fprintf f "stack variable `%a`" Var.pp var
in
F.asprintf "address of %a is returned by the function" pp_var variable
let get_trace = function
| AccessToInvalidAddress {accessed_by; invalidated_by; trace} ->
let invalidated_by_trace =
Invalidation.get_location invalidated_by
|> Option.map ~f:(fun location ->
Errlog.make_trace_element 0 location
(F.asprintf "%a here" Invalidation.pp invalidated_by)
[] )
|> Option.to_list
in
PulseTrace.make_errlog_trace ~depth:0 trace
@ invalidated_by_trace
@ [ Errlog.make_trace_element 0 accessed_by.location
(F.asprintf "accessed `%a` here" HilExp.AccessExpression.pp accessed_by.access_expr)
[] ]
| StackVariableAddressEscape _ ->
[]
let get_issue_type = function
| AccessToInvalidAddress {invalidated_by} ->
Invalidation.issue_type_of_cause invalidated_by
| StackVariableAddressEscape _ ->
IssueType.stack_variable_address_escape
end
type 'a access_result = ('a, Diagnostic.t) result
(** operations on the domain *)
module Operations = struct
open Result.Monad_infix
(** Check that the address is not known to be invalid *)
let check_addr_access actor (address, trace) astate =
match Memory.get_invalidation address astate.heap with
| Some invalidated_by ->
Error
(Diagnostic.AccessToInvalidAddress {invalidated_by; accessed_by= actor; address; trace})
| None ->
Ok astate
(** Walk the heap starting from [addr] and following [path]. Stop either at the element before last
and return [new_addr] if [overwrite_last] is [Some new_addr], or go until the end of the path if it
is [None]. Create more addresses into the heap as needed to follow the [path]. Check that each
address reached is valid. *)
let rec walk ~dereference_to_ignore actor ~on_last addr_trace path astate =
let check_addr_access_optional actor addr_trace astate =
match dereference_to_ignore with
| Some 0 ->
Ok astate
| _ ->
check_addr_access actor addr_trace astate
in
match (path, on_last) with
| [], `Access ->
Ok (astate, addr_trace)
| [], `Overwrite _ ->
L.die InternalError "Cannot overwrite last address in empty path"
| [a], `Overwrite new_addr_trace ->
check_addr_access_optional actor addr_trace astate
>>| fun astate ->
let heap = Memory.add_edge_and_back_edge (fst addr_trace) a new_addr_trace astate.heap in
({astate with heap}, new_addr_trace)
| a :: path, _ -> (
check_addr_access_optional actor addr_trace astate
>>= fun astate ->
let dereference_to_ignore =
Option.map ~f:(fun index -> max 0 (index - 1)) dereference_to_ignore
in
let addr = fst addr_trace in
match Memory.find_edge_opt addr a astate.heap with
| None ->
let addr_trace' = (AbstractAddress.mk_fresh (), []) in
let heap = Memory.add_edge_and_back_edge addr a addr_trace' astate.heap in
let astate = {astate with heap} in
walk ~dereference_to_ignore actor ~on_last addr_trace' path astate
| Some addr_trace' ->
walk ~dereference_to_ignore actor ~on_last addr_trace' path astate )
let write_var var new_addr_trace astate =
let astate, var_address_of =
match Stack.find_opt var astate.stack with
| Some (addr, _) ->
(astate, addr)
| None ->
let addr = AbstractAddress.mk_fresh () in
let stack = Stack.add var (addr, None) astate.stack in
({astate with stack}, addr)
in
(* Update heap with var_address_of -*-> new_addr *)
let heap =
Memory.add_edge var_address_of HilExp.Access.Dereference new_addr_trace astate.heap
in
{astate with heap}
let ends_with_addressof = function HilExp.AccessExpression.AddressOf _ -> true | _ -> false
let last_dereference access_list =
let rec last_dereference_inner access_list index result =
match access_list with
| [] ->
result
| HilExp.Access.Dereference :: rest ->
last_dereference_inner rest (index + 1) (Some index)
| _ :: rest ->
last_dereference_inner rest (index + 1) result
in
last_dereference_inner access_list 0 None
let rec to_accesses location access_expr astate =
let exception Failed_fold of Diagnostic.t in
try
HilExp.AccessExpression.to_accesses_fold access_expr ~init:astate
~f_array_offset:(fun astate hil_exp_opt ->
match hil_exp_opt with
| None ->
(astate, AbstractAddress.mk_fresh ())
| Some hil_exp -> (
match eval_hil_exp location hil_exp astate with
| Ok result ->
result
| Error diag ->
raise (Failed_fold diag) ) )
|> Result.return
with Failed_fold diag -> Error diag
(** add addresses to the state to give an address to the destination of the given access path *)
and walk_access_expr ~on_last astate access_expr location =
to_accesses location access_expr astate
>>= fun (astate, (access_var, _), access_list) ->
let dereference_to_ignore =
if ends_with_addressof access_expr then last_dereference access_list else None
in
if Config.write_html then
L.d_printfln "Accessing %a -> [%a]" Var.pp access_var
(Pp.seq ~sep:"," Memory.Access.pp)
access_list ;
match (on_last, access_list) with
| `Overwrite new_addr_trace, [] ->
Ok (write_var access_var new_addr_trace astate, new_addr_trace)
| `Access, _ | `Overwrite _, _ :: _ -> (
let astate, base_addr_trace =
match Stack.find_opt access_var astate.stack with
| Some (addr, init_loc_opt) ->
let trace =
Option.value_map init_loc_opt ~default:[] ~f:(fun init_loc ->
[PulseTrace.VariableDeclaration init_loc] )
in
(astate, (addr, trace))
| None ->
let addr = AbstractAddress.mk_fresh () in
let stack = Stack.add access_var (addr, None) astate.stack in
({astate with stack}, (addr, []))
in
match access_list with
| [HilExp.Access.TakeAddress] ->
Ok (astate, base_addr_trace)
| _ ->
let actor = {access_expr; location} in
walk ~dereference_to_ignore actor ~on_last base_addr_trace
(HilExp.Access.Dereference :: access_list)
astate )
(** Use the stack and heap to walk the access path represented by the given expression down to an
abstract address representing what the expression points to.
Return an error state if it traverses some known invalid address or if the end destination is
known to be invalid. *)
and materialize_address astate access_expr = walk_access_expr ~on_last:`Access astate access_expr
and read location access_expr astate =
materialize_address astate access_expr location
>>= fun (astate, addr_trace) ->
let actor = {access_expr; location} in
check_addr_access actor addr_trace astate >>| fun astate -> (astate, addr_trace)
and read_all location access_exprs astate =
List.fold_result access_exprs ~init:astate ~f:(fun astate access_expr ->
read location access_expr astate >>| fst )
and eval_hil_exp location (hil_exp : HilExp.t) astate =
match hil_exp with
| AccessExpression access_expr ->
read location access_expr astate >>| fun (astate, (addr, _)) -> (astate, addr)
| _ ->
read_all location (HilExp.get_access_exprs hil_exp) astate
>>| fun astate -> (astate, AbstractAddress.mk_fresh ())
(** Use the stack and heap to walk the access path represented by the given expression down to an
abstract address representing what the expression points to, and replace that with the given
address.
Return an error state if it traverses some known invalid address. *)
let overwrite_address astate access_expr new_addr_trace =
walk_access_expr ~on_last:(`Overwrite new_addr_trace) astate access_expr
(** Add the given address to the set of know invalid addresses. *)
let mark_invalid actor address astate =
{astate with heap= Memory.invalidate address actor astate.heap}
let havoc_var trace var astate = write_var var (AbstractAddress.mk_fresh (), trace) astate
let havoc trace location (access_expr : HilExp.AccessExpression.t) astate =
overwrite_address astate access_expr (AbstractAddress.mk_fresh (), trace) location >>| fst
let write location access_expr addr astate =
overwrite_address astate access_expr addr location >>| fun (astate, _) -> astate
let invalidate cause location access_expr astate =
materialize_address astate access_expr location
>>= fun (astate, addr_trace) ->
check_addr_access {access_expr; location} addr_trace astate
>>| mark_invalid cause (fst addr_trace)
let invalidate_array_elements cause location access_expr astate =
materialize_address astate access_expr location
>>= fun (astate, addr_trace) ->
check_addr_access {access_expr; location} addr_trace astate
>>| fun astate ->
match Memory.find_opt (fst addr_trace) astate.heap with
| None ->
astate
| Some (edges, _) ->
Memory.Edges.fold
(fun access (dest_addr, _) astate ->
match (access : Memory.Access.t) with
| ArrayAccess _ ->
mark_invalid cause dest_addr astate
| _ ->
astate )
edges astate
let check_address_of_local_variable proc_desc address astate =
let proc_location = Procdesc.get_loc proc_desc in
let proc_name = Procdesc.get_proc_name proc_desc in
let check_address_of_cpp_temporary () =
Memory.find_opt address astate.heap
|> Option.fold_result ~init:() ~f:(fun () (_, attrs) ->
Container.fold_result ~fold:(IContainer.fold_of_pervasives_fold ~fold:Attributes.fold)
attrs ~init:() ~f:(fun () attr ->
match attr with
| Attribute.AddressOfCppTemporary (variable, location_opt) ->
let location = Option.value ~default:proc_location location_opt in
Error (Diagnostic.StackVariableAddressEscape {variable; location})
| _ ->
Ok () ) )
in
let check_address_of_stack_variable () =
Container.fold_result ~fold:(IContainer.fold_of_pervasives_map_fold ~fold:Stack.fold)
astate.stack ~init:() ~f:(fun () (variable, (var_address, init_location)) ->
if
AbstractAddress.equal var_address address
&& ( Var.is_cpp_temporary variable
|| Var.is_local_to_procedure proc_name variable
&& not (Procdesc.is_captured_var proc_desc variable) )
then
let location = Option.value ~default:proc_location init_location in
Error (Diagnostic.StackVariableAddressEscape {variable; location})
else Ok () )
in
check_address_of_cpp_temporary () >>= check_address_of_stack_variable >>| fun () -> astate
let mark_address_of_cpp_temporary location variable address heap =
Memory.add_attributes address
(Attributes.singleton (AddressOfCppTemporary (variable, location)))
heap
let remove_vars vars astate =
let heap =
List.fold vars ~init:astate.heap ~f:(fun heap var ->
match Stack.find_opt var astate.stack with
| Some (address, location) when Var.is_cpp_temporary var ->
(* TODO: it would be good to record the location of the temporary creation in the
stack and save it here in the attribute for reporting *)
mark_address_of_cpp_temporary location var address heap
| _ ->
heap )
in
let stack = List.fold ~f:(fun stack var -> Stack.remove var stack) ~init:astate.stack vars in
if phys_equal stack astate.stack && phys_equal heap astate.heap then astate else {stack; heap}
let record_var_decl_location location var astate =
let addr =
match Stack.find_opt var astate.stack with
| Some (addr, _) ->
addr
| None ->
AbstractAddress.mk_fresh ()
in
let stack = Stack.add var (addr, Some location) astate.stack in
{astate with stack}
end
module Closures = struct
open Result.Monad_infix
let check_captured_addresses location lambda addr astate =
match Memory.find_opt addr astate.heap with
| None ->
Ok astate
| Some (_, attributes) ->
IContainer.iter_result ~fold:(IContainer.fold_of_pervasives_fold ~fold:Attributes.fold)
attributes ~f:(function
| Attribute.Closure (_, captured) ->
IContainer.iter_result ~fold:List.fold captured ~f:(fun addr_trace ->
Operations.check_addr_access {access_expr= lambda; location} addr_trace astate
>>| fun _ -> () )
| _ ->
Ok () )
>>| fun () -> astate
let write location access_expr pname captured astate =
let closure_addr = AbstractAddress.mk_fresh () in
Operations.write location access_expr
(closure_addr, [PulseTrace.Assignment {lhs= access_expr; location}])
astate
>>| fun astate ->
{ astate with
heap=
Memory.add_attributes closure_addr
(Attributes.singleton (Closure (pname, captured)))
astate.heap }
let record location access_expr pname captured astate =
List.fold_result captured ~init:(astate, [])
~f:(fun ((astate, captured) as result) (captured_as, captured_exp) ->
match captured_exp with
| HilExp.AccessExpression (AddressOf access_expr as captured_access_expr) ->
Operations.read location access_expr astate
>>= fun (astate, (address, trace)) ->
let new_trace =
PulseTrace.Capture {captured_as; captured= captured_access_expr; location} :: trace
in
Ok (astate, (address, new_trace) :: captured)
| _ ->
Ok result )
>>= fun (astate, captured_addresses) ->
write location access_expr pname captured_addresses astate
end
module StdVector = struct
open Result.Monad_infix
let is_reserved location vector_access_expr astate =
Operations.read location vector_access_expr astate
>>| fun (astate, (addr, _)) -> (astate, Memory.is_std_vector_reserved addr astate.heap)
let mark_reserved location vector_access_expr astate =
Operations.read location vector_access_expr astate
>>| fun (astate, (addr, _)) -> {astate with heap= Memory.std_vector_reserve addr astate.heap}
end
include Domain
let compare = compare_astate
include Operations

@ -6,84 +6,80 @@
*) *)
open! IStd open! IStd
module F = Format
module AbstractAddress : sig module AbstractAddress : sig
type t = private int [@@deriving compare] type t = private int [@@deriving compare]
val equal : t -> t -> bool
val init : unit -> unit val init : unit -> unit
val pp : F.formatter -> t -> unit
val mk_fresh : unit -> t
end end
include AbstractDomain.S module Stack : sig
include
AbstractDomain.MapS
with type key = Var.t
and type value = AbstractAddress.t * Location.t option
val compare : t -> t -> int (* need to shadow the declaration in [MapS] even though it is unused since [MapS.compare] has a
different type *)
val compare : t -> t -> int [@@warning "-32"]
end
val initial : t module AddrTracePair : sig
type t = AbstractAddress.t * PulseTrace.t [@@deriving compare]
end
module Diagnostic : sig module Attribute : sig
type t type t =
| Invalid of PulseInvalidation.t
| AddressOfCppTemporary of Var.t * Location.t option
| Closure of Typ.Procname.t * AddrTracePair.t list
| StdVectorReserve
[@@deriving compare]
end
val get_message : t -> string module Attributes : PrettyPrintable.PPSet with type elt = Attribute.t
val get_location : t -> Location.t module Memory : sig
module Access :
PrettyPrintable.PrintableOrderedType with type t = AbstractAddress.t HilExp.Access.t
val get_issue_type : t -> IssueType.t module Edges : PrettyPrintable.PPMap with type key = Access.t
val get_trace : t -> Errlog.loc_trace type edges = AddrTracePair.t Edges.t
end
type 'a access_result = ('a, Diagnostic.t) result type cell = edges * Attributes.t
module Closures : sig
val check_captured_addresses :
Location.t -> HilExp.AccessExpression.t -> AbstractAddress.t -> t -> t access_result
(** assert the validity of the addresses captured by the lambda *)
val record :
Location.t
-> HilExp.AccessExpression.t
-> Typ.Procname.t
-> (AccessPath.base * HilExp.t) list
-> t
-> t access_result
(** record that the access expression points to a lambda with its captured addresses *)
end
module StdVector : sig type t [@@deriving compare]
val is_reserved : Location.t -> HilExp.AccessExpression.t -> t -> (t * bool) access_result
val mark_reserved : Location.t -> HilExp.AccessExpression.t -> t -> t access_result val find_opt : AbstractAddress.t -> t -> cell option
end
val read : val add_edge : AbstractAddress.t -> Access.t -> AddrTracePair.t -> t -> t
Location.t
-> HilExp.AccessExpression.t
-> t
-> (t * (AbstractAddress.t * PulseTrace.t)) access_result
val read_all : Location.t -> HilExp.AccessExpression.t list -> t -> t access_result val add_edge_and_back_edge : AbstractAddress.t -> Access.t -> AddrTracePair.t -> t -> t
val havoc_var : PulseTrace.t -> Var.t -> t -> t val find_edge_opt : AbstractAddress.t -> Access.t -> t -> AddrTracePair.t option
val havoc : PulseTrace.t -> Location.t -> HilExp.AccessExpression.t -> t -> t access_result val add_attributes : AbstractAddress.t -> Attributes.t -> t -> t
val write_var : Var.t -> AbstractAddress.t * PulseTrace.t -> t -> t val invalidate : AbstractAddress.t -> PulseInvalidation.t -> t -> t
val write : val get_invalidation : AbstractAddress.t -> t -> PulseInvalidation.t option
Location.t (** None denotes a valid location *)
-> HilExp.AccessExpression.t
-> AbstractAddress.t * PulseTrace.t
-> t
-> t access_result
val invalidate : val std_vector_reserve : AbstractAddress.t -> t -> t
PulseInvalidation.t -> Location.t -> HilExp.AccessExpression.t -> t -> t access_result
val invalidate_array_elements : val is_std_vector_reserved : AbstractAddress.t -> t -> bool
PulseInvalidation.t -> Location.t -> HilExp.AccessExpression.t -> t -> t access_result end
val record_var_decl_location : Location.t -> Var.t -> t -> t type t = {heap: Memory.t; stack: Stack.t} [@@deriving compare]
val remove_vars : Var.t list -> t -> t val initial : t
(* TODO: better name and pass location to report where we returned *) include AbstractDomain.S with type t := t
val check_address_of_local_variable : Procdesc.t -> AbstractAddress.t -> t -> t access_result

@ -13,7 +13,7 @@ type exec_fun =
-> ret:Var.t * Typ.t -> ret:Var.t * Typ.t
-> actuals:HilExp.t list -> actuals:HilExp.t list
-> PulseDomain.t -> PulseDomain.t
-> PulseDomain.t PulseDomain.access_result -> PulseDomain.t PulseOperations.access_result
type model = exec_fun type model = exec_fun
@ -26,7 +26,9 @@ module C = struct
fun location ~ret:_ ~actuals astate -> fun location ~ret:_ ~actuals astate ->
match actuals with match actuals with
| [AccessExpression deleted_access] -> | [AccessExpression deleted_access] ->
PulseDomain.invalidate (CFree (deleted_access, location)) location deleted_access astate PulseOperations.invalidate
(CFree (deleted_access, location))
location deleted_access astate
| _ -> | _ ->
Ok astate Ok astate
@ -35,8 +37,8 @@ module C = struct
fun location ~ret:_ ~actuals astate -> fun location ~ret:_ ~actuals astate ->
match actuals with match actuals with
| [AccessExpression (AddressOf (Base (var, _)))] -> | [AccessExpression (AddressOf (Base (var, _)))] ->
PulseDomain.havoc_var [PulseTrace.VariableDeclaration location] var astate PulseOperations.havoc_var [PulseTrace.VariableDeclaration location] var astate
|> PulseDomain.record_var_decl_location location var |> PulseOperations.record_var_decl_location location var
|> Result.return |> Result.return
| _ -> | _ ->
L.die InternalError L.die InternalError
@ -48,11 +50,11 @@ end
module Cplusplus = struct module Cplusplus = struct
let delete : model = let delete : model =
fun location ~ret:_ ~actuals astate -> fun location ~ret:_ ~actuals astate ->
PulseDomain.read_all location (List.concat_map actuals ~f:HilExp.get_access_exprs) astate PulseOperations.read_all location (List.concat_map actuals ~f:HilExp.get_access_exprs) astate
>>= fun astate -> >>= fun astate ->
match actuals with match actuals with
| [AccessExpression deleted_access] -> | [AccessExpression deleted_access] ->
PulseDomain.invalidate PulseOperations.invalidate
(CppDelete (deleted_access, location)) (CppDelete (deleted_access, location))
location deleted_access astate location deleted_access astate
| _ -> | _ ->
@ -61,33 +63,36 @@ module Cplusplus = struct
let operator_call : model = let operator_call : model =
fun location ~ret:(ret_var, _) ~actuals astate -> fun location ~ret:(ret_var, _) ~actuals astate ->
PulseDomain.read_all location (List.concat_map actuals ~f:HilExp.get_access_exprs) astate PulseOperations.read_all location (List.concat_map actuals ~f:HilExp.get_access_exprs) astate
>>= fun astate -> >>= fun astate ->
( match actuals with ( match actuals with
| AccessExpression lambda :: _ -> | AccessExpression lambda :: _ ->
PulseDomain.read location HilExp.AccessExpression.(dereference lambda) astate PulseOperations.read location HilExp.AccessExpression.(dereference lambda) astate
>>= fun (astate, address) -> >>= fun (astate, address) ->
PulseDomain.Closures.check_captured_addresses location lambda (fst address) astate PulseOperations.Closures.check_captured_addresses location lambda (fst address) astate
| _ -> | _ ->
Ok astate ) Ok astate )
>>| PulseDomain.havoc_var [PulseTrace.Call {f= `Model "<lambda>"; actuals; location}] ret_var >>| PulseOperations.havoc_var
[PulseTrace.Call {f= `Model "<lambda>"; actuals; location}]
ret_var
let placement_new : model = let placement_new : model =
fun location ~ret:(ret_var, _) ~actuals astate -> fun location ~ret:(ret_var, _) ~actuals astate ->
let read_all args astate = let read_all args astate =
PulseDomain.read_all location (List.concat_map args ~f:HilExp.get_access_exprs) astate PulseOperations.read_all location (List.concat_map args ~f:HilExp.get_access_exprs) astate
in in
let crumb = PulseTrace.Call {f= `Model "<placement new>"; actuals; location} in let crumb = PulseTrace.Call {f= `Model "<placement new>"; actuals; location} in
match List.rev actuals with match List.rev actuals with
| HilExp.AccessExpression expr :: other_actuals -> | HilExp.AccessExpression expr :: other_actuals ->
PulseDomain.read location expr astate PulseOperations.read location expr astate
>>= fun (astate, (address, trace)) -> >>= fun (astate, (address, trace)) ->
PulseDomain.write_var ret_var (address, crumb :: trace) astate |> read_all other_actuals PulseOperations.write_var ret_var (address, crumb :: trace) astate
|> read_all other_actuals
| _ :: other_actuals -> | _ :: other_actuals ->
read_all other_actuals astate >>| PulseDomain.havoc_var [crumb] ret_var read_all other_actuals astate >>| PulseOperations.havoc_var [crumb] ret_var
| _ -> | _ ->
Ok (PulseDomain.havoc_var [crumb] ret_var astate) Ok (PulseOperations.havoc_var [crumb] ret_var astate)
end end
module StdVector = struct module StdVector = struct
@ -111,9 +116,9 @@ module StdVector = struct
let array_address = to_internal_array vector in let array_address = to_internal_array vector in
let array = deref_internal_array vector in let array = deref_internal_array vector in
let invalidation = PulseInvalidation.StdVector (vector_f, vector, location) in let invalidation = PulseInvalidation.StdVector (vector_f, vector, location) in
PulseDomain.invalidate_array_elements invalidation location array astate PulseOperations.invalidate_array_elements invalidation location array astate
>>= PulseDomain.invalidate invalidation location array >>= PulseOperations.invalidate invalidation location array
>>= PulseDomain.havoc trace location array_address >>= PulseOperations.havoc trace location array_address
let invalidate_references vector_f : model = let invalidate_references vector_f : model =
@ -136,13 +141,16 @@ module StdVector = struct
let crumb = PulseTrace.Call {f= `Model "std::vector::at"; actuals; location} in let crumb = PulseTrace.Call {f= `Model "std::vector::at"; actuals; location} in
match actuals with match actuals with
| [AccessExpression vector_access_expr; index_exp] -> | [AccessExpression vector_access_expr; index_exp] ->
PulseDomain.read location PulseOperations.read location
(element_of_internal_array vector_access_expr (Some index_exp)) (element_of_internal_array vector_access_expr (Some index_exp))
astate astate
>>= fun (astate, (addr, trace)) -> >>= fun (astate, (addr, trace)) ->
PulseDomain.write location (HilExp.AccessExpression.base ret) (addr, crumb :: trace) astate PulseOperations.write location
(HilExp.AccessExpression.base ret)
(addr, crumb :: trace)
astate
| _ -> | _ ->
Ok (PulseDomain.havoc_var [crumb] (fst ret) astate) Ok (PulseOperations.havoc_var [crumb] (fst ret) astate)
let reserve : model = let reserve : model =
@ -151,7 +159,7 @@ module StdVector = struct
| [AccessExpression vector; _value] -> | [AccessExpression vector; _value] ->
let crumb = PulseTrace.Call {f= `Model "std::vector::reserve"; actuals; location} in let crumb = PulseTrace.Call {f= `Model "std::vector::reserve"; actuals; location} in
reallocate_internal_array [crumb] vector Reserve location astate reallocate_internal_array [crumb] vector Reserve location astate
>>= PulseDomain.StdVector.mark_reserved location vector >>= PulseOperations.StdVector.mark_reserved location vector
| _ -> | _ ->
Ok astate Ok astate
@ -161,7 +169,7 @@ module StdVector = struct
match actuals with match actuals with
| [AccessExpression vector; _value] -> | [AccessExpression vector; _value] ->
let crumb = PulseTrace.Call {f= `Model "std::vector::push_back"; actuals; location} in let crumb = PulseTrace.Call {f= `Model "std::vector::push_back"; actuals; location} in
PulseDomain.StdVector.is_reserved location vector astate PulseOperations.StdVector.is_reserved location vector astate
>>= fun (astate, is_reserved) -> >>= fun (astate, is_reserved) ->
if is_reserved then if is_reserved then
(* assume that any call to [push_back] is ok after one called [reserve] on the same vector (* assume that any call to [push_back] is ok after one called [reserve] on the same vector

@ -11,7 +11,7 @@ type exec_fun =
-> ret:Var.t * Typ.t -> ret:Var.t * Typ.t
-> actuals:HilExp.t list -> actuals:HilExp.t list
-> PulseDomain.t -> PulseDomain.t
-> PulseDomain.t PulseDomain.access_result -> PulseDomain.t PulseOperations.access_result
type model = exec_fun type model = exec_fun

@ -0,0 +1,357 @@
(*
* Copyright (c) 2018-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 L = Logging
module AbstractAddress = PulseDomain.AbstractAddress
module Attribute = PulseDomain.Attribute
module Attributes = PulseDomain.Attributes
module Memory = PulseDomain.Memory
module Stack = PulseDomain.Stack
open Result.Monad_infix
type t = PulseDomain.t = {heap: Memory.t; stack: Stack.t}
type 'a access_result = ('a, PulseDiagnostic.t) result
(** Check that the address is not known to be invalid *)
let check_addr_access actor (address, trace) astate =
match Memory.get_invalidation address astate.heap with
| Some invalidated_by ->
Error
(PulseDiagnostic.AccessToInvalidAddress {invalidated_by; accessed_by= actor; address; trace})
| None ->
Ok astate
(** Walk the heap starting from [addr] and following [path]. Stop either at the element before last
and return [new_addr] if [overwrite_last] is [Some new_addr], or go until the end of the path if it
is [None]. Create more addresses into the heap as needed to follow the [path]. Check that each
address reached is valid. *)
let rec walk ~dereference_to_ignore actor ~on_last addr_trace path astate =
let check_addr_access_optional actor addr_trace astate =
match dereference_to_ignore with
| Some 0 ->
Ok astate
| _ ->
check_addr_access actor addr_trace astate
in
match (path, on_last) with
| [], `Access ->
Ok (astate, addr_trace)
| [], `Overwrite _ ->
L.die InternalError "Cannot overwrite last address in empty path"
| [a], `Overwrite new_addr_trace ->
check_addr_access_optional actor addr_trace astate
>>| fun astate ->
let heap = Memory.add_edge_and_back_edge (fst addr_trace) a new_addr_trace astate.heap in
({astate with heap}, new_addr_trace)
| a :: path, _ -> (
check_addr_access_optional actor addr_trace astate
>>= fun astate ->
let dereference_to_ignore =
Option.map ~f:(fun index -> max 0 (index - 1)) dereference_to_ignore
in
let addr = fst addr_trace in
match Memory.find_edge_opt addr a astate.heap with
| None ->
let addr_trace' = (AbstractAddress.mk_fresh (), []) in
let heap = Memory.add_edge_and_back_edge addr a addr_trace' astate.heap in
let astate = {astate with heap} in
walk ~dereference_to_ignore actor ~on_last addr_trace' path astate
| Some addr_trace' ->
walk ~dereference_to_ignore actor ~on_last addr_trace' path astate )
let write_var var new_addr_trace astate =
let astate, var_address_of =
match Stack.find_opt var astate.stack with
| Some (addr, _) ->
(astate, addr)
| None ->
let addr = AbstractAddress.mk_fresh () in
let stack = Stack.add var (addr, None) astate.stack in
({astate with stack}, addr)
in
(* Update heap with var_address_of -*-> new_addr *)
let heap = Memory.add_edge var_address_of HilExp.Access.Dereference new_addr_trace astate.heap in
{astate with heap}
let ends_with_addressof = function HilExp.AccessExpression.AddressOf _ -> true | _ -> false
let last_dereference access_list =
let rec last_dereference_inner access_list index result =
match access_list with
| [] ->
result
| HilExp.Access.Dereference :: rest ->
last_dereference_inner rest (index + 1) (Some index)
| _ :: rest ->
last_dereference_inner rest (index + 1) result
in
last_dereference_inner access_list 0 None
let rec to_accesses location access_expr astate =
let exception Failed_fold of PulseDiagnostic.t in
try
HilExp.AccessExpression.to_accesses_fold access_expr ~init:astate
~f_array_offset:(fun astate hil_exp_opt ->
match hil_exp_opt with
| None ->
(astate, AbstractAddress.mk_fresh ())
| Some hil_exp -> (
match eval_hil_exp location hil_exp astate with
| Ok result ->
result
| Error diag ->
raise (Failed_fold diag) ) )
|> Result.return
with Failed_fold diag -> Error diag
(** add addresses to the state to give an address to the destination of the given access path *)
and walk_access_expr ~on_last astate access_expr location =
to_accesses location access_expr astate
>>= fun (astate, (access_var, _), access_list) ->
let dereference_to_ignore =
if ends_with_addressof access_expr then last_dereference access_list else None
in
if Config.write_html then
L.d_printfln "Accessing %a -> [%a]" Var.pp access_var
(Pp.seq ~sep:"," Memory.Access.pp)
access_list ;
match (on_last, access_list) with
| `Overwrite new_addr_trace, [] ->
Ok (write_var access_var new_addr_trace astate, new_addr_trace)
| `Access, _ | `Overwrite _, _ :: _ -> (
let astate, base_addr_trace =
match Stack.find_opt access_var astate.stack with
| Some (addr, init_loc_opt) ->
let trace =
Option.value_map init_loc_opt ~default:[] ~f:(fun init_loc ->
[PulseTrace.VariableDeclaration init_loc] )
in
(astate, (addr, trace))
| None ->
let addr = AbstractAddress.mk_fresh () in
let stack = Stack.add access_var (addr, None) astate.stack in
({astate with stack}, (addr, []))
in
match access_list with
| [HilExp.Access.TakeAddress] ->
Ok (astate, base_addr_trace)
| _ ->
let actor = {PulseDiagnostic.access_expr; location} in
walk ~dereference_to_ignore actor ~on_last base_addr_trace
(HilExp.Access.Dereference :: access_list)
astate )
(** Use the stack and heap to walk the access path represented by the given expression down to an
abstract address representing what the expression points to.
Return an error state if it traverses some known invalid address or if the end destination is
known to be invalid. *)
and materialize_address astate access_expr = walk_access_expr ~on_last:`Access astate access_expr
and read location access_expr astate =
materialize_address astate access_expr location
>>= fun (astate, addr_trace) ->
let actor = {PulseDiagnostic.access_expr; location} in
check_addr_access actor addr_trace astate >>| fun astate -> (astate, addr_trace)
and read_all location access_exprs astate =
List.fold_result access_exprs ~init:astate ~f:(fun astate access_expr ->
read location access_expr astate >>| fst )
and eval_hil_exp location (hil_exp : HilExp.t) astate =
match hil_exp with
| AccessExpression access_expr ->
read location access_expr astate >>| fun (astate, (addr, _)) -> (astate, addr)
| _ ->
read_all location (HilExp.get_access_exprs hil_exp) astate
>>| fun astate -> (astate, AbstractAddress.mk_fresh ())
(** Use the stack and heap to walk the access path represented by the given expression down to an
abstract address representing what the expression points to, and replace that with the given
address.
Return an error state if it traverses some known invalid address. *)
let overwrite_address astate access_expr new_addr_trace =
walk_access_expr ~on_last:(`Overwrite new_addr_trace) astate access_expr
(** Add the given address to the set of know invalid addresses. *)
let mark_invalid actor address astate =
{astate with heap= Memory.invalidate address actor astate.heap}
let havoc_var trace var astate = write_var var (AbstractAddress.mk_fresh (), trace) astate
let havoc trace location (access_expr : HilExp.AccessExpression.t) astate =
overwrite_address astate access_expr (AbstractAddress.mk_fresh (), trace) location >>| fst
let write location access_expr addr astate =
overwrite_address astate access_expr addr location >>| fun (astate, _) -> astate
let invalidate cause location access_expr astate =
materialize_address astate access_expr location
>>= fun (astate, addr_trace) ->
check_addr_access {access_expr; location} addr_trace astate
>>| mark_invalid cause (fst addr_trace)
let invalidate_array_elements cause location access_expr astate =
materialize_address astate access_expr location
>>= fun (astate, addr_trace) ->
check_addr_access {access_expr; location} addr_trace astate
>>| fun astate ->
match Memory.find_opt (fst addr_trace) astate.heap with
| None ->
astate
| Some (edges, _) ->
Memory.Edges.fold
(fun access (dest_addr, _) astate ->
match (access : Memory.Access.t) with
| ArrayAccess _ ->
mark_invalid cause dest_addr astate
| _ ->
astate )
edges astate
let check_address_of_local_variable proc_desc address astate =
let proc_location = Procdesc.get_loc proc_desc in
let proc_name = Procdesc.get_proc_name proc_desc in
let check_address_of_cpp_temporary () =
Memory.find_opt address astate.heap
|> Option.fold_result ~init:() ~f:(fun () (_, attrs) ->
Container.fold_result ~fold:(IContainer.fold_of_pervasives_fold ~fold:Attributes.fold)
attrs ~init:() ~f:(fun () attr ->
match attr with
| Attribute.AddressOfCppTemporary (variable, location_opt) ->
let location = Option.value ~default:proc_location location_opt in
Error (PulseDiagnostic.StackVariableAddressEscape {variable; location})
| _ ->
Ok () ) )
in
let check_address_of_stack_variable () =
Container.fold_result ~fold:(IContainer.fold_of_pervasives_map_fold ~fold:Stack.fold)
astate.stack ~init:() ~f:(fun () (variable, (var_address, init_location)) ->
if
AbstractAddress.equal var_address address
&& ( Var.is_cpp_temporary variable
|| Var.is_local_to_procedure proc_name variable
&& not (Procdesc.is_captured_var proc_desc variable) )
then
let location = Option.value ~default:proc_location init_location in
Error (PulseDiagnostic.StackVariableAddressEscape {variable; location})
else Ok () )
in
check_address_of_cpp_temporary () >>= check_address_of_stack_variable >>| fun () -> astate
let mark_address_of_cpp_temporary location variable address heap =
Memory.add_attributes address
(Attributes.singleton (AddressOfCppTemporary (variable, location)))
heap
let remove_vars vars astate =
let heap =
List.fold vars ~init:astate.heap ~f:(fun heap var ->
match Stack.find_opt var astate.stack with
| Some (address, location) when Var.is_cpp_temporary var ->
(* TODO: it would be good to record the location of the temporary creation in the
stack and save it here in the attribute for reporting *)
mark_address_of_cpp_temporary location var address heap
| _ ->
heap )
in
let stack = List.fold ~f:(fun stack var -> Stack.remove var stack) ~init:astate.stack vars in
if phys_equal stack astate.stack && phys_equal heap astate.heap then astate else {stack; heap}
let record_var_decl_location location var astate =
let addr =
match Stack.find_opt var astate.stack with
| Some (addr, _) ->
addr
| None ->
AbstractAddress.mk_fresh ()
in
let stack = Stack.add var (addr, Some location) astate.stack in
{astate with stack}
module Closures = struct
open Result.Monad_infix
let check_captured_addresses location lambda addr astate =
match Memory.find_opt addr astate.heap with
| None ->
Ok astate
| Some (_, attributes) ->
IContainer.iter_result ~fold:(IContainer.fold_of_pervasives_fold ~fold:Attributes.fold)
attributes ~f:(function
| Attribute.Closure (_, captured) ->
IContainer.iter_result ~fold:List.fold captured ~f:(fun addr_trace ->
check_addr_access {access_expr= lambda; location} addr_trace astate
>>| fun _ -> () )
| _ ->
Ok () )
>>| fun () -> astate
let write location access_expr pname captured astate =
let closure_addr = AbstractAddress.mk_fresh () in
write location access_expr
(closure_addr, [PulseTrace.Assignment {lhs= access_expr; location}])
astate
>>| fun astate ->
{ astate with
heap=
Memory.add_attributes closure_addr
(Attributes.singleton (Closure (pname, captured)))
astate.heap }
let record location access_expr pname captured astate =
List.fold_result captured ~init:(astate, [])
~f:(fun ((astate, captured) as result) (captured_as, captured_exp) ->
match captured_exp with
| HilExp.AccessExpression (AddressOf access_expr as captured_access_expr) ->
read location access_expr astate
>>= fun (astate, (address, trace)) ->
let new_trace =
PulseTrace.Capture {captured_as; captured= captured_access_expr; location} :: trace
in
Ok (astate, (address, new_trace) :: captured)
| _ ->
Ok result )
>>= fun (astate, captured_addresses) ->
write location access_expr pname captured_addresses astate
end
module StdVector = struct
open Result.Monad_infix
let is_reserved location vector_access_expr astate =
read location vector_access_expr astate
>>| fun (astate, (addr, _)) -> (astate, Memory.is_std_vector_reserved addr astate.heap)
let mark_reserved location vector_access_expr astate =
read location vector_access_expr astate
>>| fun (astate, (addr, _)) -> {astate with heap= Memory.std_vector_reserve addr astate.heap}
end

@ -0,0 +1,72 @@
(*
* Copyright (c) 2018-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
type t = PulseDomain.t = {heap: PulseDomain.Memory.t; stack: PulseDomain.Stack.t}
type 'a access_result = ('a, PulseDiagnostic.t) result
module Closures : sig
val check_captured_addresses :
Location.t
-> HilExp.AccessExpression.t
-> PulseDomain.AbstractAddress.t
-> t
-> t access_result
(** assert the validity of the addresses captured by the lambda *)
val record :
Location.t
-> HilExp.AccessExpression.t
-> Typ.Procname.t
-> (AccessPath.base * HilExp.t) list
-> t
-> t access_result
(** record that the access expression points to a lambda with its captured addresses *)
end
module StdVector : sig
val is_reserved : Location.t -> HilExp.AccessExpression.t -> t -> (t * bool) access_result
val mark_reserved : Location.t -> HilExp.AccessExpression.t -> t -> t access_result
end
val read :
Location.t
-> HilExp.AccessExpression.t
-> t
-> (t * (PulseDomain.AbstractAddress.t * PulseTrace.t)) access_result
val read_all : Location.t -> HilExp.AccessExpression.t list -> t -> t access_result
val havoc_var : PulseTrace.t -> Var.t -> t -> t
val havoc : PulseTrace.t -> Location.t -> HilExp.AccessExpression.t -> t -> t access_result
val write_var : Var.t -> PulseDomain.AbstractAddress.t * PulseTrace.t -> t -> t
val write :
Location.t
-> HilExp.AccessExpression.t
-> PulseDomain.AbstractAddress.t * PulseTrace.t
-> t
-> t access_result
val invalidate :
PulseInvalidation.t -> Location.t -> HilExp.AccessExpression.t -> t -> t access_result
val invalidate_array_elements :
PulseInvalidation.t -> Location.t -> HilExp.AccessExpression.t -> t -> t access_result
val record_var_decl_location : Location.t -> Var.t -> t -> t
val remove_vars : Var.t list -> t -> t
(* TODO: better name and pass location to report where we returned *)
val check_address_of_local_variable :
Procdesc.t -> PulseDomain.AbstractAddress.t -> t -> t access_result
Loading…
Cancel
Save