[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
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)
(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
match rhs_exp with
| AccessExpression rhs_access -> (
PulseDomain.read loc rhs_access astate
PulseOperations.read loc rhs_access astate
>>= fun (astate, (rhs_addr, rhs_trace)) ->
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 ->
match lhs_access with
| 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 )
| Closure (pname, captured) ->
PulseDomain.Closures.record loc lhs_access pname captured astate
PulseOperations.Closures.record loc lhs_access pname captured astate
| Cast (_, e) ->
exec_assign summary lhs_access e loc astate
| _ ->
PulseDomain.read_all loc (HilExp.get_access_exprs rhs_exp) astate
>>= PulseDomain.havoc [crumb] loc lhs_access
PulseOperations.read_all loc (HilExp.get_access_exprs rhs_exp) astate
>>= PulseOperations.havoc [crumb] loc lhs_access
let exec_call summary _ret (call : HilInstr.call) (actuals : HilExp.t list) _flags call_loc
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
let crumb = PulseTrace.Call {f= `HilCall call; actuals; location= call_loc} in
match call with
@ -80,7 +81,7 @@ module PulseTransferFunctions (CFG : ProcCfg.S) = struct
Ok astate
| [AccessExpression destroyed_access] ->
let destroyed_object = HilExp.AccessExpression.dereference destroyed_access in
PulseDomain.invalidate
PulseOperations.invalidate
(CppDestructor (callee_pname, destroyed_object, call_loc))
call_loc destroyed_object astate
| _ ->
@ -89,7 +90,7 @@ module PulseTransferFunctions (CFG : ProcCfg.S) = struct
match actuals with
| AccessExpression constructor_access :: rest ->
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 )
| Direct (Typ.Procname.ObjC_Cpp callee_pname)
@ -104,8 +105,8 @@ module PulseTransferFunctions (CFG : ProcCfg.S) = struct
| [AccessExpression lhs; HilExp.AccessExpression rhs] ->
let lhs_deref = HilExp.AccessExpression.dereference lhs in
let rhs_deref = HilExp.AccessExpression.dereference rhs in
PulseDomain.havoc [crumb] call_loc lhs_deref astate
>>= fun astate -> PulseDomain.read call_loc rhs_deref astate >>| fst
PulseOperations.havoc [crumb] call_loc lhs_deref astate
>>= fun astate -> PulseOperations.read call_loc rhs_deref astate >>| fst
| _ ->
read_all actuals astate )
| _ ->
@ -125,7 +126,7 @@ module PulseTransferFunctions (CFG : ProcCfg.S) = struct
match model with
| None ->
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}]
(fst ret)
| Some model ->
@ -138,11 +139,12 @@ module PulseTransferFunctions (CFG : ProcCfg.S) = struct
| Assign (lhs_access, rhs_exp, loc) ->
exec_assign summary lhs_access rhs_exp loc astate |> check_error summary
| 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) ->
dispatch_call summary ret call actuals flags loc astate |> check_error summary
| ExitScope (vars, _) ->
PulseDomain.remove_vars vars astate
PulseOperations.remove_vars vars astate
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
end
(** the domain *)
type astate = {heap: Memory.t; stack: Stack.t} [@@deriving compare]
type t = {heap: Memory.t; stack: Stack.t} [@@deriving compare]
let initial =
{ heap=
@ -274,166 +273,162 @@ let initial =
; stack= Stack.empty }
module Domain : AbstractDomain.S with type t = astate = struct
type t = astate
let piecewise_lessthan lhs rhs =
Stack.( <= ) ~lhs:lhs.stack ~rhs:rhs.stack
&& Memory.for_all
(fun addr_src (edges_lhs, attrs_lhs) ->
let edges_rhs_opt, attrs_rhs =
let cell = Memory.find_opt addr_src rhs.heap in
(Option.map ~f:fst cell, Option.value_map ~default:Attributes.empty ~f:snd cell)
in
Memory.Edges.for_all
(fun access_lhs (addr_dst, _) ->
Option.bind edges_rhs_opt ~f:(fun edges_rhs ->
Memory.Edges.find_opt access_lhs edges_rhs )
|> Option.exists ~f:(fun (addr_dst_rhs, _) ->
AbstractAddress.equal addr_dst_rhs addr_dst ) )
edges_lhs
&& Attributes.( <= ) ~lhs:attrs_lhs ~rhs:attrs_rhs )
lhs.heap
let piecewise_lessthan lhs rhs =
Stack.( <= ) ~lhs:lhs.stack ~rhs:rhs.stack
&& Memory.for_all
(fun addr_src (edges_lhs, attrs_lhs) ->
let edges_rhs_opt, attrs_rhs =
let cell = Memory.find_opt addr_src rhs.heap in
(Option.map ~f:fst cell, Option.value_map ~default:Attributes.empty ~f:snd cell)
in
Memory.Edges.for_all
(fun access_lhs (addr_dst, _) ->
Option.bind edges_rhs_opt ~f:(fun edges_rhs ->
Memory.Edges.find_opt access_lhs edges_rhs )
|> Option.exists ~f:(fun (addr_dst_rhs, _) ->
AbstractAddress.equal addr_dst_rhs addr_dst ) )
edges_lhs
&& Attributes.( <= ) ~lhs:attrs_lhs ~rhs:attrs_rhs )
lhs.heap
module JoinState = struct
module AddressUnionSet = struct
module Set = PrettyPrintable.MakePPSet (AbstractAddress)
module JoinState = struct
module AddressUnionSet = struct
module Set = PrettyPrintable.MakePPSet (AbstractAddress)
type elt = AbstractAddress.t [@@deriving compare]
type elt = AbstractAddress.t [@@deriving compare]
type t = Set.t ref
type t = Set.t ref
let create x = ref (Set.singleton x)
let create x = ref (Set.singleton x)
let compare_size _ _ = 0
let compare_size _ _ = 0
let merge ~from ~to_ = to_ := Set.union !from !to_
let merge ~from ~to_ = to_ := Set.union !from !to_
let pp f x = Set.pp f !x
end
let pp f x = Set.pp f !x
end
module AddressUF = ImperativeUnionFind.Make (AddressUnionSet)
module AddressUF = ImperativeUnionFind.Make (AddressUnionSet)
(** just to get the correct type coercion *)
let to_canonical_address subst addr = (AddressUF.find subst addr :> AbstractAddress.t)
(** just to get the correct type coercion *)
let to_canonical_address subst addr = (AddressUF.find subst addr :> AbstractAddress.t)
type nonrec t = {subst: AddressUF.t; astate: t}
type nonrec t = {subst: AddressUF.t; astate: t}
(** adds [(src_addr, access, value)] to [union_heap] and record potential new equality that
(** adds [(src_addr, access, value)] to [union_heap] and record potential new equality that
results from it in [subst] *)
let union_one_edge subst src_addr access value union_heap =
let dst_addr, _ = value in
let src_addr = to_canonical_address subst src_addr in
let dst_addr = to_canonical_address subst dst_addr in
match (Memory.find_edge_opt src_addr access union_heap, (access : Memory.Access.t)) with
| Some (dst_addr', _), _ when AbstractAddress.equal dst_addr dst_addr' ->
(* same edge *)
(union_heap, `No_new_equality)
| _, ArrayAccess _ ->
(* do not trust array accesses for now, replace the destination of the edge by a fresh location *)
( Memory.add_edge src_addr access (AbstractAddress.mk_fresh (), []) union_heap
, `No_new_equality )
| None, _ ->
(Memory.add_edge src_addr access value union_heap, `No_new_equality)
| Some (dst_addr', _), _ ->
(* new equality [dst_addr = dst_addr'] found *)
ignore (AddressUF.union subst dst_addr dst_addr') ;
(union_heap, `New_equality)
module Addresses = Caml.Set.Make (AbstractAddress)
let rec visit_address subst visited heap addr union_heap =
if Addresses.mem addr visited then (visited, union_heap)
else
let visited = Addresses.add addr visited in
let visit_edge access value (visited, union_heap) =
union_one_edge subst addr access value union_heap
|> fst
|> visit_address subst visited heap (fst value)
in
Memory.find_opt addr heap
|> Option.fold ~init:(visited, union_heap) ~f:(fun (visited, union_heap) (edges, attrs) ->
let union_heap = Memory.add_attributes addr attrs union_heap in
Memory.Edges.fold visit_edge edges (visited, union_heap) )
let visit_stack subst heap stack union_heap =
(* start graph exploration *)
let visited = Addresses.empty in
let _, union_heap =
Stack.fold
(fun _var (addr, _) (visited, union_heap) ->
visit_address subst visited heap addr union_heap )
stack (visited, union_heap)
let union_one_edge subst src_addr access value union_heap =
let dst_addr, _ = value in
let src_addr = to_canonical_address subst src_addr in
let dst_addr = to_canonical_address subst dst_addr in
match (Memory.find_edge_opt src_addr access union_heap, (access : Memory.Access.t)) with
| Some (dst_addr', _), _ when AbstractAddress.equal dst_addr dst_addr' ->
(* same edge *)
(union_heap, `No_new_equality)
| _, ArrayAccess _ ->
(* do not trust array accesses for now, replace the destination of the edge by a fresh location *)
( Memory.add_edge src_addr access (AbstractAddress.mk_fresh (), []) union_heap
, `No_new_equality )
| None, _ ->
(Memory.add_edge src_addr access value union_heap, `No_new_equality)
| Some (dst_addr', _), _ ->
(* new equality [dst_addr = dst_addr'] found *)
ignore (AddressUF.union subst dst_addr dst_addr') ;
(union_heap, `New_equality)
module Addresses = Caml.Set.Make (AbstractAddress)
let rec visit_address subst visited heap addr union_heap =
if Addresses.mem addr visited then (visited, union_heap)
else
let visited = Addresses.add addr visited in
let visit_edge access value (visited, union_heap) =
union_one_edge subst addr access value union_heap
|> fst
|> visit_address subst visited heap (fst value)
in
union_heap
Memory.find_opt addr heap
|> Option.fold ~init:(visited, union_heap) ~f:(fun (visited, union_heap) (edges, attrs) ->
let union_heap = Memory.add_attributes addr attrs union_heap in
Memory.Edges.fold visit_edge edges (visited, union_heap) )
let visit_stack subst heap stack union_heap =
(* start graph exploration *)
let visited = Addresses.empty in
let _, union_heap =
Stack.fold
(fun _var (addr, _) (visited, union_heap) ->
visit_address subst visited heap addr union_heap )
stack (visited, union_heap)
in
union_heap
let populate_subst_from_stacks subst stack1 stack2 =
ignore
((* Use [Caml.Map.merge] to detect the variables present in both stacks. Build an empty
let populate_subst_from_stacks subst stack1 stack2 =
ignore
((* Use [Caml.Map.merge] to detect the variables present in both stacks. Build an empty
result map since we don't use the result. *)
Stack.merge
(fun _var addr1_opt addr2_opt ->
Option.both addr1_opt addr2_opt
|> Option.iter ~f:(fun ((addr1, _), (addr2, _)) ->
(* stack1 says [_var = addr1] and stack2 says [_var = addr2]: unify the
Stack.merge
(fun _var addr1_opt addr2_opt ->
Option.both addr1_opt addr2_opt
|> Option.iter ~f:(fun ((addr1, _), (addr2, _)) ->
(* stack1 says [_var = addr1] and stack2 says [_var = addr2]: unify the
addresses since they are equal to the same variable *)
ignore (AddressUF.union subst addr1 addr2) ) ;
(* empty result map *)
None )
stack1 stack2)
ignore (AddressUF.union subst addr1 addr2) ) ;
(* empty result map *)
None )
stack1 stack2)
let from_astate_union {heap= heap1; stack= stack1} {heap= heap2; stack= stack2} =
let subst = AddressUF.create () in
(* gather equalities from the stacks *)
populate_subst_from_stacks subst stack1 stack2 ;
(* union the heaps, take this opportunity to do garbage collection of unreachable values by
let from_astate_union {heap= heap1; stack= stack1} {heap= heap2; stack= stack2} =
let subst = AddressUF.create () in
(* gather equalities from the stacks *)
populate_subst_from_stacks subst stack1 stack2 ;
(* union the heaps, take this opportunity to do garbage collection of unreachable values by
only copying the addresses reachable from the variables in the stacks *)
let heap = visit_stack subst heap1 stack1 Memory.empty |> visit_stack subst heap2 stack2 in
(* This keeps all the variables and picks one representative address for each variable in
let heap = visit_stack subst heap1 stack1 Memory.empty |> visit_stack subst heap2 stack2 in
(* This keeps all the variables and picks one representative address for each variable in
common thanks to [AbstractAddressDomain_JoinIsMin] *)
let stack = Stack.join stack1 stack2 in
{subst; astate= {heap; stack}}
let rec normalize state =
let one_addr subst addr (edges, attrs) (heap, has_converged) =
let heap = Memory.add_attributes addr attrs heap in
Memory.Edges.fold
(fun access addr_dest (heap, has_converged) ->
match union_one_edge subst addr access addr_dest heap with
| heap, `No_new_equality ->
(heap, has_converged)
| heap, `New_equality ->
(heap, false) )
edges (heap, has_converged)
let stack = Stack.join stack1 stack2 in
{subst; astate= {heap; stack}}
let rec normalize state =
let one_addr subst addr (edges, attrs) (heap, has_converged) =
let heap = Memory.add_attributes addr attrs heap in
Memory.Edges.fold
(fun access addr_dest (heap, has_converged) ->
match union_one_edge subst addr access addr_dest heap with
| heap, `No_new_equality ->
(heap, has_converged)
| heap, `New_equality ->
(heap, false) )
edges (heap, has_converged)
in
let heap, has_converged =
Memory.fold (one_addr state.subst) state.astate.heap (Memory.empty, true)
in
if has_converged then (
let pp_union_find_classes f subst =
Container.iter subst ~fold:AddressUF.fold_sets ~f:(fun ((repr : AddressUF.Repr.t), set) ->
F.fprintf f "%a=%a@;" AbstractAddress.pp
(repr :> AbstractAddress.t)
AddressUnionSet.pp set )
in
let heap, has_converged =
Memory.fold (one_addr state.subst) state.astate.heap (Memory.empty, true)
L.d_printfln "Join unified addresses:@\n@[<v2> %a@]" pp_union_find_classes state.subst ;
let stack =
Stack.map
(fun (addr, loc) -> (to_canonical_address state.subst addr, loc))
state.astate.stack
in
if has_converged then (
let pp_union_find_classes f subst =
Container.iter subst ~fold:AddressUF.fold_sets
~f:(fun ((repr : AddressUF.Repr.t), set) ->
F.fprintf f "%a=%a@;" AbstractAddress.pp
(repr :> AbstractAddress.t)
AddressUnionSet.pp set )
in
L.d_printfln "Join unified addresses:@\n@[<v2> %a@]" pp_union_find_classes state.subst ;
let stack =
Stack.map
(fun (addr, loc) -> (to_canonical_address state.subst addr, loc))
state.astate.stack
in
{heap; stack} )
else normalize {state with astate= {state.astate with heap}}
end
{heap; stack} )
else normalize {state with astate= {state.astate with heap}}
end
(** Given
(** Given
- stacks S1, S2 : Var -> Address,
@ -453,10 +448,10 @@ module Domain : AbstractDomain.S with type t = astate = struct
reachable location are still reachable (up to the substitution) instead of only the locations
leading to invalid ones.
*)
let join astate1 astate2 =
if phys_equal astate1 astate2 then astate1
else
(* high-level idea: maintain some union-find data structure to identify locations in one heap
let join astate1 astate2 =
if phys_equal astate1 astate2 then astate1
else
(* high-level idea: maintain some union-find data structure to identify locations in one heap
with locations in the other heap. Build the initial join state as follows:
- equate all locations that correspond to identical variables in both stacks, eg joining
@ -471,435 +466,19 @@ module Domain : AbstractDomain.S with type t = astate = struct
discovered, and return the abstract state where a canonical representative has been chosen
consistently for each equivalence class (this is what the union-find data structure gives
us). *)
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]. *)
let ( <= ) ~lhs ~rhs = phys_equal lhs rhs || piecewise_lessthan lhs rhs
let max_widening = 5
let widen ~prev ~next ~num_iters =
(* widening is underapproximation for now... TODO *)
if num_iters > max_widening then prev
else if phys_equal prev next then prev
else join prev next
let pp fmt {heap; 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
let ( <= ) ~lhs ~rhs = phys_equal lhs rhs || piecewise_lessthan lhs rhs
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
let max_widening = 5
include Domain
let widen ~prev ~next ~num_iters =
(* widening is underapproximation for now... TODO *)
if num_iters > max_widening then prev else if phys_equal prev next then prev else join prev next
let compare = compare_astate
include Operations
let pp fmt {heap; stack} =
F.fprintf fmt "{@[<v1> heap=@[<hv>%a@];@;stack=@[<hv>%a@];@]}" Memory.pp heap Stack.pp stack

@ -6,84 +6,80 @@
*)
open! IStd
module F = Format
module AbstractAddress : sig
type t = private int [@@deriving compare]
val equal : t -> t -> bool
val init : unit -> unit
val pp : F.formatter -> t -> unit
val mk_fresh : unit -> t
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
type t
module Attribute : sig
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
end
type edges = AddrTracePair.t Edges.t
type 'a access_result = ('a, Diagnostic.t) result
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
type cell = edges * Attributes.t
module StdVector : sig
val is_reserved : Location.t -> HilExp.AccessExpression.t -> t -> (t * bool) access_result
type t [@@deriving compare]
val mark_reserved : Location.t -> HilExp.AccessExpression.t -> t -> t access_result
end
val find_opt : AbstractAddress.t -> t -> cell option
val read :
Location.t
-> HilExp.AccessExpression.t
-> t
-> (t * (AbstractAddress.t * PulseTrace.t)) access_result
val add_edge : AbstractAddress.t -> Access.t -> AddrTracePair.t -> t -> t
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 :
Location.t
-> HilExp.AccessExpression.t
-> AbstractAddress.t * PulseTrace.t
-> t
-> t access_result
val get_invalidation : AbstractAddress.t -> t -> PulseInvalidation.t option
(** None denotes a valid location *)
val invalidate :
PulseInvalidation.t -> Location.t -> HilExp.AccessExpression.t -> t -> t access_result
val std_vector_reserve : AbstractAddress.t -> t -> t
val invalidate_array_elements :
PulseInvalidation.t -> Location.t -> HilExp.AccessExpression.t -> t -> t access_result
val is_std_vector_reserved : AbstractAddress.t -> t -> bool
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 *)
val check_address_of_local_variable : Procdesc.t -> AbstractAddress.t -> t -> t access_result
include AbstractDomain.S with type t := t

@ -13,7 +13,7 @@ type exec_fun =
-> ret:Var.t * Typ.t
-> actuals:HilExp.t list
-> PulseDomain.t
-> PulseDomain.t PulseDomain.access_result
-> PulseDomain.t PulseOperations.access_result
type model = exec_fun
@ -26,7 +26,9 @@ module C = struct
fun location ~ret:_ ~actuals astate ->
match actuals with
| [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
@ -35,8 +37,8 @@ module C = struct
fun location ~ret:_ ~actuals astate ->
match actuals with
| [AccessExpression (AddressOf (Base (var, _)))] ->
PulseDomain.havoc_var [PulseTrace.VariableDeclaration location] var astate
|> PulseDomain.record_var_decl_location location var
PulseOperations.havoc_var [PulseTrace.VariableDeclaration location] var astate
|> PulseOperations.record_var_decl_location location var
|> Result.return
| _ ->
L.die InternalError
@ -48,11 +50,11 @@ end
module Cplusplus = struct
let delete : model =
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 ->
match actuals with
| [AccessExpression deleted_access] ->
PulseDomain.invalidate
PulseOperations.invalidate
(CppDelete (deleted_access, location))
location deleted_access astate
| _ ->
@ -61,33 +63,36 @@ module Cplusplus = struct
let operator_call : model =
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 ->
( match actuals with
| AccessExpression lambda :: _ ->
PulseDomain.read location HilExp.AccessExpression.(dereference lambda) astate
PulseOperations.read location HilExp.AccessExpression.(dereference lambda) astate
>>= 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 )
>>| 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 =
fun location ~ret:(ret_var, _) ~actuals 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
let crumb = PulseTrace.Call {f= `Model "<placement new>"; actuals; location} in
match List.rev actuals with
| HilExp.AccessExpression expr :: other_actuals ->
PulseDomain.read location expr astate
PulseOperations.read location expr astate
>>= 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 ->
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
module StdVector = struct
@ -111,9 +116,9 @@ module StdVector = struct
let array_address = to_internal_array vector in
let array = deref_internal_array vector in
let invalidation = PulseInvalidation.StdVector (vector_f, vector, location) in
PulseDomain.invalidate_array_elements invalidation location array astate
>>= PulseDomain.invalidate invalidation location array
>>= PulseDomain.havoc trace location array_address
PulseOperations.invalidate_array_elements invalidation location array astate
>>= PulseOperations.invalidate invalidation location array
>>= PulseOperations.havoc trace location array_address
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
match actuals with
| [AccessExpression vector_access_expr; index_exp] ->
PulseDomain.read location
PulseOperations.read location
(element_of_internal_array vector_access_expr (Some index_exp))
astate
>>= 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 =
@ -151,7 +159,7 @@ module StdVector = struct
| [AccessExpression vector; _value] ->
let crumb = PulseTrace.Call {f= `Model "std::vector::reserve"; actuals; location} in
reallocate_internal_array [crumb] vector Reserve location astate
>>= PulseDomain.StdVector.mark_reserved location vector
>>= PulseOperations.StdVector.mark_reserved location vector
| _ ->
Ok astate
@ -161,7 +169,7 @@ module StdVector = struct
match actuals with
| [AccessExpression vector; _value] ->
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) ->
if is_reserved then
(* 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
-> actuals:HilExp.t list
-> PulseDomain.t
-> PulseDomain.t PulseDomain.access_result
-> PulseDomain.t PulseOperations.access_result
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