[pulse][interproc 2/3] abductive domain

Summary:
For each operation on the domain, try to record what it requires of the
precondition of the function. This is akin to what happens in the
biabduction backend, hence the terminology used.

Reviewed By: jberdine

Differential Revision: D14387148

fbshipit-source-id: a61fe30c8
master
Jules Villard 6 years ago committed by Facebook Github Bot
parent f0f66daa4c
commit 0300d5374c

@ -8,6 +8,18 @@ open! IStd
module F = Format module F = Format
module L = Logging module L = Logging
open Result.Monad_infix open Result.Monad_infix
module AbstractAddress = PulseDomain.AbstractAddress
include (* ocaml ignores the warning suppression at toplevel, hence the [include struct ... end] trick *)
struct
[@@@warning "-60"]
(** Do not use {!PulseDomain} directly as it could result in bypassing abduction mechanisms in
{!PulseOperations} and {!PulseAbductiveDomain} that take care of propagating facts to the
precondition. *)
module PulseDomain = struct end
[@@deprecated "Use PulseAbductiveDomain or PulseOperations instead."]
end
let report summary diagnostic = let report summary diagnostic =
let open PulseDiagnostic in let open PulseDiagnostic in
@ -27,7 +39,7 @@ let check_error summary = function
module PulseTransferFunctions = struct module PulseTransferFunctions = struct
module CFG = ProcCfg.Exceptional module CFG = ProcCfg.Exceptional
module Domain = PulseDomain module Domain = PulseAbductiveDomain
type extras = Summary.t type extras = Summary.t
@ -137,8 +149,7 @@ module PulseTransferFunctions = struct
model call_loc ~ret ~actuals astate model call_loc ~ret ~actuals astate
let exec_instr (astate : PulseDomain.t) {ProcData.extras= summary} _cfg_node (instr : HilInstr.t) let exec_instr (astate : Domain.t) {ProcData.extras= summary} _cfg_node (instr : HilInstr.t) =
=
match instr with match instr with
| Assign (lhs_access, rhs_exp, loc) -> | Assign (lhs_access, rhs_exp, loc) ->
let post = exec_assign summary lhs_access rhs_exp loc astate |> check_error summary in let post = exec_assign summary lhs_access rhs_exp loc astate |> check_error summary in
@ -190,10 +201,10 @@ module DisjunctiveAnalyzer =
let checker {Callbacks.proc_desc; tenv; summary} = let checker {Callbacks.proc_desc; tenv; summary} =
let proc_data = ProcData.make proc_desc tenv summary in let proc_data = ProcData.make proc_desc tenv summary in
PulseDomain.AbstractAddress.init () ; AbstractAddress.init () ;
( try ( try
ignore ignore
(DisjunctiveAnalyzer.compute_post proc_data (DisjunctiveAnalyzer.compute_post proc_data
~initial:(DisjunctiveTransferFunctions.Disjuncts.singleton PulseDomain.initial)) ~initial:(DisjunctiveTransferFunctions.Disjuncts.singleton PulseAbductiveDomain.empty))
with AbstractDomain.Stop_analysis -> () ) ; with AbstractDomain.Stop_analysis -> () ) ;
summary summary

@ -0,0 +1,237 @@
(*
* Copyright (c) 2019-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
module Attributes = PulseDomain.Attributes
module BaseStack = PulseDomain.Stack
module BaseMemory = PulseDomain.Memory
(** signature common to the "normal" [Domain], representing the post at the current program point,
and the inverted [InvertedDomain], representing the inferred pre-condition*)
module type BaseDomain = sig
(* private because the lattice is not the same for preconditions and postconditions so we don't
want to confuse them *)
type t = private PulseDomain.t [@@deriving compare]
val empty : t
val make : BaseStack.t -> BaseMemory.t -> t
val update : ?stack:BaseStack.t -> ?heap:BaseMemory.t -> t -> t
include AbstractDomain.NoJoin with type t := t
end
(* just to expose the [heap] and [stack] record field names without having to type
[PulseDomain.heap] *)
type base_domain = PulseDomain.t = {heap: BaseMemory.t; stack: BaseStack.t}
(** operations common to [Domain] and [InvertedDomain], see also the [BaseDomain] signature *)
module BaseDomainCommon = struct
let make stack heap = {stack; heap}
let update ?stack ?heap foot =
let new_stack, new_heap =
(Option.value ~default:foot.stack stack, Option.value ~default:foot.heap heap)
in
if phys_equal new_stack foot.stack && phys_equal new_heap foot.heap then foot
else {stack= new_stack; heap= new_heap}
end
(** represents the post abstract state at each program point *)
module Domain : BaseDomain = struct
include BaseDomainCommon
include PulseDomain
end
(** represents the inferred pre-condition at each program point, biabduction style *)
module InvertedDomain : BaseDomain = struct
include BaseDomainCommon
type t = PulseDomain.t [@@deriving compare]
let empty = PulseDomain.empty
let pp = PulseDomain.pp
(** inverted lattice *)
let ( <= ) ~lhs ~rhs = PulseDomain.( <= ) ~rhs:lhs ~lhs:rhs
end
(** biabduction-style pre/post state *)
type t =
{ post: Domain.t (** state at the current program point*)
; pre: InvertedDomain.t (** inferred pre at the current program point *) }
[@@deriving compare]
let pp f {post; pre} = F.fprintf f "@[<v>%a@;[%a]@]" Domain.pp post InvertedDomain.pp pre
let ( <= ) ~lhs ~rhs =
match
PulseDomain.isograph_map PulseDomain.empty_mapping
~lhs:(rhs.pre :> PulseDomain.t)
~rhs:(lhs.pre :> PulseDomain.t)
with
| NotIsomorphic ->
false
| IsomorphicUpTo foot_mapping ->
PulseDomain.is_isograph foot_mapping
~lhs:(lhs.post :> PulseDomain.t)
~rhs:(rhs.post :> PulseDomain.t)
module Stack = struct
let is_abducible _var =
(* TODO: need to keep only formals + return variable + globals in the pre *) true
(** [astate] with [astate.post.stack = f astate.post.stack] *)
let map_post_stack ~f astate =
let new_post = Domain.update astate.post ~stack:(f (astate.post :> base_domain).stack) in
if phys_equal new_post astate.post then astate else {astate with post= new_post}
let materialize var astate =
match BaseStack.find_opt var (astate.post :> base_domain).stack with
| Some addr_loc_opt ->
(astate, addr_loc_opt)
| None ->
let addr_loc_opt' = (AbstractAddress.mk_fresh (), None) in
let post_stack = BaseStack.add var addr_loc_opt' (astate.post :> base_domain).stack in
let pre =
if is_abducible var then
let foot_stack = BaseStack.add var addr_loc_opt' (astate.pre :> base_domain).stack in
let foot_heap =
BaseMemory.register_address (fst addr_loc_opt') (astate.pre :> base_domain).heap
in
InvertedDomain.make foot_stack foot_heap
else astate.pre
in
({post= Domain.update astate.post ~stack:post_stack; pre}, addr_loc_opt')
let add var addr_loc_opt astate =
map_post_stack astate ~f:(fun stack -> BaseStack.add var addr_loc_opt stack)
let remove_vars vars astate =
map_post_stack astate ~f:(fun stack ->
BaseStack.filter (fun var _ -> not (List.mem ~equal:Var.equal vars var)) stack )
let fold f astate accum = BaseStack.fold f (astate.post :> base_domain).stack accum
let find_opt var astate = BaseStack.find_opt var (astate.post :> base_domain).stack
end
module Memory = struct
open Result.Monad_infix
module Access = BaseMemory.Access
(** [astate] with [astate.post.heap = f astate.post.heap] *)
let map_post_heap ~f astate =
let new_post = Domain.update astate.post ~heap:(f (astate.post :> base_domain).heap) in
if phys_equal new_post astate.post then astate else {astate with post= new_post}
(** if [address] is in [pre] and it should be valid then that fact goes in the precondition *)
let record_must_be_valid actor address (pre : InvertedDomain.t) =
if BaseMemory.mem_edges address (pre :> base_domain).heap then
InvertedDomain.update pre
~heap:
(BaseMemory.add_attributes address
(Attributes.singleton (MustBeValid actor))
(pre :> base_domain).heap)
else pre
let check_valid actor addr ({post; pre} as astate) =
BaseMemory.check_valid addr (post :> base_domain).heap
>>| fun () ->
let new_pre = record_must_be_valid actor addr pre in
if phys_equal new_pre pre then astate else {astate with pre= new_pre}
let add_edge addr access new_addr_trace astate =
map_post_heap astate ~f:(fun heap -> BaseMemory.add_edge addr access new_addr_trace heap)
let add_edge_and_back_edge addr access new_addr_trace astate =
map_post_heap astate ~f:(fun heap ->
BaseMemory.add_edge_and_back_edge addr access new_addr_trace heap )
let materialize_edge addr access astate =
match BaseMemory.find_edge_opt addr access (astate.post :> base_domain).heap with
| Some addr_trace' ->
(astate, addr_trace')
| None ->
let addr_trace' = (AbstractAddress.mk_fresh (), []) in
let post_heap =
BaseMemory.add_edge_and_back_edge addr access addr_trace'
(astate.post :> base_domain).heap
in
let foot_heap =
if BaseMemory.mem_edges addr (astate.pre :> base_domain).heap then
BaseMemory.add_edge_and_back_edge addr access addr_trace'
(astate.pre :> base_domain).heap
|> BaseMemory.register_address (fst addr_trace')
else (astate.pre :> base_domain).heap
in
( { post= Domain.update astate.post ~heap:post_heap
; pre= InvertedDomain.update astate.pre ~heap:foot_heap }
, addr_trace' )
let invalidate address actor astate =
map_post_heap astate ~f:(fun heap -> BaseMemory.invalidate address actor heap)
let add_attributes address attributes astate =
map_post_heap astate ~f:(fun heap -> BaseMemory.add_attributes address attributes heap)
let std_vector_reserve addr astate =
map_post_heap astate ~f:(fun heap -> BaseMemory.std_vector_reserve addr heap)
let is_std_vector_reserved addr astate =
BaseMemory.is_std_vector_reserved addr (astate.post :> base_domain).heap
let find_opt address astate = BaseMemory.find_opt address (astate.post :> base_domain).heap
let set_cell addr cell astate =
map_post_heap astate ~f:(fun heap -> BaseMemory.set_cell addr cell heap)
module Edges = BaseMemory.Edges
end
let empty = {post= Domain.empty; pre= InvertedDomain.empty}
let discard_unreachable ({pre; post} as astate) =
let pre_addresses = PulseDomain.visit (pre :> PulseDomain.t) in
let pre_old_heap = (pre :> PulseDomain.t).heap in
let pre_new_heap =
PulseDomain.Memory.filter
(fun address -> PulseDomain.AbstractAddressSet.mem address pre_addresses)
pre_old_heap
in
let post_addresses = PulseDomain.visit (post :> PulseDomain.t) in
let all_addresses = PulseDomain.AbstractAddressSet.union pre_addresses post_addresses in
let post_old_heap = (post :> PulseDomain.t).heap in
let post_new_heap =
PulseDomain.Memory.filter
(fun address -> PulseDomain.AbstractAddressSet.mem address all_addresses)
post_old_heap
in
if phys_equal pre_new_heap pre_old_heap && phys_equal post_new_heap post_old_heap then astate
else
{ pre= InvertedDomain.make (pre :> PulseDomain.t).stack pre_new_heap
; post= Domain.make (post :> PulseDomain.t).stack post_new_heap }

@ -0,0 +1,66 @@
(*
* Copyright (c) 2019-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 AbstractAddress = PulseDomain.AbstractAddress
module Attributes = PulseDomain.Attributes
(* layer on top of {!PulseDomain} to propagate operations on the current state to the pre-condition
when necessary
The abstract type [t] is a pre/post pair in the style of biabduction.
*)
include AbstractDomain.NoJoin
val empty : t
(** stack operations like {!PulseDomain.Stack} but that also take care of propagating facts to the
precondition *)
module Stack : sig
val add : Var.t -> PulseDomain.Stack.value -> t -> t
val remove_vars : Var.t list -> t -> t
val fold : (Var.t -> PulseDomain.Stack.value -> 'a -> 'a) -> t -> 'a -> 'a
val find_opt : Var.t -> t -> PulseDomain.Stack.value option
val materialize : Var.t -> t -> t * PulseDomain.Stack.value
end
(** stack operations like {!PulseDomain.Heap} but that also take care of propagating facts to the
precondition *)
module Memory : sig
module Access = PulseDomain.Memory.Access
module Edges = PulseDomain.Memory.Edges
val add_attributes : AbstractAddress.t -> Attributes.t -> t -> t
val add_edge : AbstractAddress.t -> Access.t -> PulseDomain.AddrTracePair.t -> t -> t
val add_edge_and_back_edge :
AbstractAddress.t -> Access.t -> PulseDomain.AddrTracePair.t -> t -> t
val check_valid :
PulseDiagnostic.actor -> AbstractAddress.t -> t -> (t, PulseInvalidation.t) result
val find_opt : AbstractAddress.t -> t -> PulseDomain.Memory.cell option
val set_cell : AbstractAddress.t -> PulseDomain.Memory.cell -> t -> t
val invalidate : AbstractAddress.t -> PulseInvalidation.t -> t -> t
val is_std_vector_reserved : AbstractAddress.t -> t -> bool
val std_vector_reserve : AbstractAddress.t -> t -> t
val materialize_edge : AbstractAddress.t -> Access.t -> t -> t * PulseDomain.AddrTracePair.t
end
val discard_unreachable : t -> t
(** garbage collect unreachable addresses in the state to make it smaller, just for convenience and
keep its size down *)

@ -20,6 +20,7 @@ module Attribute = struct
type t = type t =
(* DO NOT MOVE, see toplevel comment *) (* DO NOT MOVE, see toplevel comment *)
| Invalid of Invalidation.t | Invalid of Invalidation.t
| MustBeValid of PulseDiagnostic.actor
| AddressOfCppTemporary of Var.t * Location.t option | AddressOfCppTemporary of Var.t * Location.t option
| Closure of Typ.Procname.t | Closure of Typ.Procname.t
| StdVectorReserve | StdVectorReserve
@ -28,6 +29,9 @@ module Attribute = struct
let pp f = function let pp f = function
| Invalid invalidation -> | Invalid invalidation ->
Invalidation.pp f invalidation Invalidation.pp f invalidation
| MustBeValid actor ->
F.fprintf f "MustBeValid (read by %a @ %a)" HilExp.AccessExpression.pp actor.access_expr
Location.pp actor.location
| AddressOfCppTemporary (var, location_opt) -> | AddressOfCppTemporary (var, location_opt) ->
F.fprintf f "&%a (%a)" Var.pp var (Pp.option Location.pp) location_opt F.fprintf f "&%a (%a)" Var.pp var (Pp.option Location.pp) location_opt
| Closure pname -> | Closure pname ->
@ -86,6 +90,8 @@ end = struct
let set_state counter = next_fresh := counter let set_state counter = next_fresh := counter
end end
module AbstractAddressSet = PrettyPrintable.MakePPSet (AbstractAddress)
(* {3 Heap domain } *) (* {3 Heap domain } *)
module AddrTracePair = struct module AddrTracePair = struct
@ -120,8 +126,12 @@ module Memory : sig
val set_cell : AbstractAddress.t -> cell -> t -> t val set_cell : AbstractAddress.t -> cell -> t -> t
val mem_edges : AbstractAddress.t -> t -> bool
val pp : F.formatter -> t -> unit val pp : F.formatter -> t -> unit
val register_address : AbstractAddress.t -> t -> t
val add_edge : AbstractAddress.t -> Access.t -> AddrTracePair.t -> t -> t val add_edge : AbstractAddress.t -> Access.t -> AddrTracePair.t -> t -> t
val add_edge_and_back_edge : AbstractAddress.t -> Access.t -> AddrTracePair.t -> t -> t val add_edge_and_back_edge : AbstractAddress.t -> Access.t -> AddrTracePair.t -> t -> t
@ -162,6 +172,11 @@ end = struct
~snd:(Graph.pp ~pp_value:Attributes.pp) ~snd:(Graph.pp ~pp_value:Attributes.pp)
let register_address addr memory =
if Graph.mem addr (fst memory) then memory
else (Graph.add addr Edges.empty (fst memory), snd memory)
(* {3 Helper functions to traverse the two maps at once } *) (* {3 Helper functions to traverse the two maps at once } *)
let add_edge addr_src access value memory = let add_edge addr_src access value memory =
@ -248,6 +263,9 @@ end = struct
let heap = Graph.filter (fun address _ -> f address) (fst memory) in let heap = Graph.filter (fun address _ -> f address) (fst memory) in
let attrs = Graph.filter (fun address _ -> f address) (snd memory) in let attrs = Graph.filter (fun address _ -> f address) (snd memory) in
if phys_equal heap (fst memory) && phys_equal attrs (snd memory) then memory else (heap, attrs) if phys_equal heap (fst memory) && phys_equal attrs (snd memory) then memory else (heap, attrs)
let mem_edges addr memory = Graph.mem addr (fst memory)
end end
(** Stacks: map addresses of variables to values and initialisation location. *) (** Stacks: map addresses of variables to values and initialisation location. *)
@ -285,7 +303,7 @@ end
type t = {heap: Memory.t; stack: Stack.t} [@@deriving compare] type t = {heap: Memory.t; stack: Stack.t} [@@deriving compare]
let initial = let empty =
{ heap= { heap=
Memory.empty Memory.empty
(* TODO: we could record that 0 is an invalid address at this point but this makes the (* TODO: we could record that 0 is an invalid address at this point but this makes the
@ -447,16 +465,14 @@ let pp fmt {heap; stack} =
module GraphGC : sig module GraphGC : sig
val minimize : t -> t val visit : t -> AbstractAddressSet.t
(** compute the set of abstract addresses that are "used" in the abstract state, i.e. reachable (** compute the set of abstract addresses that are "used" in the abstract state, i.e. reachable
from the stack variables, then removes all the unused addresses from the heap *) from the stack variables *)
end = struct end = struct
module AddressSet = PrettyPrintable.MakePPSet (AbstractAddress)
let visit address visited = let visit address visited =
if AddressSet.mem address visited then `AlreadyVisited if AbstractAddressSet.mem address visited then `AlreadyVisited
else else
let visited = AddressSet.add address visited in let visited = AbstractAddressSet.add address visited in
`NotAlreadyVisited visited `NotAlreadyVisited visited
@ -478,17 +494,11 @@ end = struct
edges visited edges visited
let visit_stack astate visited = let visit astate =
Stack.fold Stack.fold
(fun _var (address, _loc) visited -> visit_address astate address visited) (fun _var (address, _loc) visited -> visit_address astate address visited)
astate.stack visited astate.stack AbstractAddressSet.empty
let minimize astate =
let visited = visit_stack astate AddressSet.empty in
let heap = Memory.filter (fun address -> AddressSet.mem address visited) astate.heap in
if phys_equal heap astate.heap then astate else {astate with heap}
end end
include GraphGC
include GraphComparison include GraphComparison
include GraphGC

@ -11,12 +11,15 @@ module F = Format
module Attribute : sig module Attribute : sig
type t = type t =
| Invalid of PulseInvalidation.t | Invalid of PulseInvalidation.t
| MustBeValid of PulseDiagnostic.actor
| AddressOfCppTemporary of Var.t * Location.t option | AddressOfCppTemporary of Var.t * Location.t option
| Closure of Typ.Procname.t | Closure of Typ.Procname.t
| StdVectorReserve | StdVectorReserve
[@@deriving compare] [@@deriving compare]
end end
module Attributes : PrettyPrintable.PPSet with type elt = Attribute.t
module AbstractAddress : sig module AbstractAddress : sig
type t = private int [@@deriving compare] type t = private int [@@deriving compare]
@ -35,6 +38,8 @@ module AbstractAddress : sig
val set_state : state -> unit val set_state : state -> unit
end end
module AbstractAddressSet : PrettyPrintable.PPSet with type elt = AbstractAddress.t
module Stack : sig module Stack : sig
include include
PrettyPrintable.MonoMap PrettyPrintable.MonoMap
@ -50,8 +55,6 @@ module AddrTracePair : sig
type t = AbstractAddress.t * PulseTrace.t [@@deriving compare] type t = AbstractAddress.t * PulseTrace.t [@@deriving compare]
end end
module Attributes : PrettyPrintable.PPSet with type elt = Attribute.t
module Memory : sig module Memory : sig
module Access : module Access :
PrettyPrintable.PrintableOrderedType with type t = AbstractAddress.t HilExp.Access.t PrettyPrintable.PrintableOrderedType with type t = AbstractAddress.t HilExp.Access.t
@ -64,10 +67,16 @@ module Memory : sig
type t [@@deriving compare] type t [@@deriving compare]
val filter : (AbstractAddress.t -> bool) -> t -> t
val find_opt : AbstractAddress.t -> t -> cell option val find_opt : AbstractAddress.t -> t -> cell option
val set_cell : AbstractAddress.t -> cell -> t -> t val set_cell : AbstractAddress.t -> cell -> t -> t
val mem_edges : AbstractAddress.t -> t -> bool
val register_address : AbstractAddress.t -> t -> t
val add_edge : AbstractAddress.t -> Access.t -> AddrTracePair.t -> t -> t val add_edge : AbstractAddress.t -> Access.t -> AddrTracePair.t -> t -> t
val add_edge_and_back_edge : AbstractAddress.t -> Access.t -> AddrTracePair.t -> t -> t val add_edge_and_back_edge : AbstractAddress.t -> Access.t -> AddrTracePair.t -> t -> t
@ -87,8 +96,22 @@ end
type t = {heap: Memory.t; stack: Stack.t} [@@deriving compare] type t = {heap: Memory.t; stack: Stack.t} [@@deriving compare]
val initial : t val empty : t
include AbstractDomain.NoJoin with type t := t include AbstractDomain.NoJoin with type t := t
val minimize : t -> t val visit : t -> AbstractAddressSet.t
(** compute the set of abstract addresses that are "used" in the abstract state, i.e. reachable
from the stack variables *)
type mapping
val empty_mapping : mapping
type isograph_relation =
| NotIsomorphic (** no mapping was found that can make LHS the same as the RHS *)
| IsomorphicUpTo of mapping (** [mapping(lhs)] is isomorphic to [rhs] *)
val isograph_map : lhs:t -> rhs:t -> mapping -> isograph_relation
val is_isograph : lhs:t -> rhs:t -> mapping -> bool

@ -11,13 +11,13 @@ type exec_fun =
Location.t Location.t
-> ret:Var.t * Typ.t -> ret:Var.t * Typ.t
-> actuals:HilExp.t list -> actuals:HilExp.t list
-> PulseDomain.t -> PulseAbductiveDomain.t
-> PulseDomain.t PulseOperations.access_result -> PulseAbductiveDomain.t PulseOperations.access_result
type model = exec_fun type model = exec_fun
module Misc = struct module Misc = struct
let early_exit : model = fun _ ~ret:_ ~actuals:_ _ -> Ok PulseDomain.initial let early_exit : model = fun _ ~ret:_ ~actuals:_ _ -> Ok PulseAbductiveDomain.empty
end end
module C = struct module C = struct

@ -10,8 +10,8 @@ type exec_fun =
Location.t Location.t
-> ret:Var.t * Typ.t -> ret:Var.t * Typ.t
-> actuals:HilExp.t list -> actuals:HilExp.t list
-> PulseDomain.t -> PulseAbductiveDomain.t
-> PulseDomain.t PulseOperations.access_result -> PulseAbductiveDomain.t PulseOperations.access_result
type model = exec_fun type model = exec_fun

@ -9,21 +9,27 @@ module L = Logging
module AbstractAddress = PulseDomain.AbstractAddress module AbstractAddress = PulseDomain.AbstractAddress
module Attribute = PulseDomain.Attribute module Attribute = PulseDomain.Attribute
module Attributes = PulseDomain.Attributes module Attributes = PulseDomain.Attributes
module Memory = PulseDomain.Memory module Memory = PulseAbductiveDomain.Memory
module Stack = PulseDomain.Stack module Stack = PulseAbductiveDomain.Stack
open Result.Monad_infix open Result.Monad_infix
type t = PulseDomain.t = {heap: Memory.t; stack: Stack.t} include (* ocaml ignores the warning suppression at toplevel, hence the [include struct ... end] trick *)
struct
[@@@warning "-60"]
(** Do not use {!PulseDomain} directly, go through {!PulseAbductiveDomain} instead *)
module PulseDomain = struct end [@@deprecated "Use PulseAbductiveDomain instead."]
end
type t = PulseAbductiveDomain.t
type 'a access_result = ('a, PulseDiagnostic.t) result type 'a access_result = ('a, PulseDiagnostic.t) result
(** Check that the address is not known to be invalid *) (** Check that the address is not known to be invalid *)
let check_addr_access actor (address, trace) astate = let check_addr_access actor (address, trace) astate =
match Memory.check_valid address astate.heap with Memory.check_valid actor address astate
| Ok () -> |> Result.map_error ~f:(fun invalidated_by ->
Ok astate PulseDiagnostic.AccessToInvalidAddress {invalidated_by; accessed_by= actor; trace} )
| Error invalidated_by ->
Error (PulseDiagnostic.AccessToInvalidAddress {invalidated_by; accessed_by= actor; trace})
(** Walk the heap starting from [addr] and following [path]. Stop either at the element before last (** Walk the heap starting from [addr] and following [path]. Stop either at the element before last
@ -46,38 +52,22 @@ let rec walk ~dereference_to_ignore actor ~on_last addr_trace path astate =
| [a], `Overwrite new_addr_trace -> | [a], `Overwrite new_addr_trace ->
check_addr_access_optional actor addr_trace astate check_addr_access_optional actor addr_trace astate
>>| fun astate -> >>| fun astate ->
let heap = Memory.add_edge_and_back_edge (fst addr_trace) a new_addr_trace astate.heap in let astate = Memory.add_edge_and_back_edge (fst addr_trace) a new_addr_trace astate in
({astate with heap}, new_addr_trace) (astate, new_addr_trace)
| a :: path, _ -> ( | a :: path, _ ->
check_addr_access_optional actor addr_trace astate check_addr_access_optional actor addr_trace astate
>>= fun astate -> >>= fun astate ->
let dereference_to_ignore = let dereference_to_ignore =
Option.map ~f:(fun index -> max 0 (index - 1)) dereference_to_ignore Option.map ~f:(fun index -> max 0 (index - 1)) dereference_to_ignore
in in
let addr = fst addr_trace in let astate, addr_trace' = Memory.materialize_edge (fst addr_trace) a astate in
match Memory.find_edge_opt addr a astate.heap with walk ~dereference_to_ignore actor ~on_last addr_trace' path astate
| 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 write_var var new_addr_trace astate =
let astate, var_address_of = let astate, (var_address_of, _) = Stack.materialize var astate in
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 *) (* 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 Memory.add_edge var_address_of HilExp.Access.Dereference new_addr_trace astate
{astate with heap}
let ends_with_addressof = function HilExp.AccessExpression.AddressOf _ -> true | _ -> false let ends_with_addressof = function HilExp.AccessExpression.AddressOf _ -> true | _ -> false
@ -129,17 +119,12 @@ and walk_access_expr ~on_last astate access_expr location =
Ok (write_var access_var new_addr_trace astate, new_addr_trace) Ok (write_var access_var new_addr_trace astate, new_addr_trace)
| `Access, _ | `Overwrite _, _ :: _ -> ( | `Access, _ | `Overwrite _, _ :: _ -> (
let astate, base_addr_trace = let astate, base_addr_trace =
match Stack.find_opt access_var astate.stack with let astate, (addr, init_loc_opt) = Stack.materialize access_var astate in
| Some (addr, init_loc_opt) -> let trace =
let trace = Option.value_map init_loc_opt ~default:[] ~f:(fun init_loc ->
Option.value_map init_loc_opt ~default:[] ~f:(fun init_loc -> [PulseTrace.VariableDeclaration init_loc] )
[PulseTrace.VariableDeclaration init_loc] ) in
in (astate, (addr, trace))
(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 in
match access_list with match access_list with
| [HilExp.Access.TakeAddress] -> | [HilExp.Access.TakeAddress] ->
@ -189,9 +174,7 @@ let overwrite_address astate access_expr new_addr_trace =
(** Add the given address to the set of know invalid addresses. *) (** Add the given address to the set of know invalid addresses. *)
let mark_invalid actor address astate = let mark_invalid actor address astate = Memory.invalidate address actor 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_var trace var astate = write_var var (AbstractAddress.mk_fresh (), trace) astate
@ -215,7 +198,7 @@ let invalidate_array_elements cause location access_expr astate =
>>= fun (astate, addr_trace) -> >>= fun (astate, addr_trace) ->
check_addr_access {access_expr; location} addr_trace astate check_addr_access {access_expr; location} addr_trace astate
>>| fun astate -> >>| fun astate ->
match Memory.find_opt (fst addr_trace) astate.heap with match Memory.find_opt (fst addr_trace) astate with
| None -> | None ->
astate astate
| Some (edges, _) -> | Some (edges, _) ->
@ -233,10 +216,10 @@ let check_address_of_local_variable proc_desc address astate =
let proc_location = Procdesc.get_loc proc_desc in let proc_location = Procdesc.get_loc proc_desc in
let proc_name = Procdesc.get_proc_name proc_desc in let proc_name = Procdesc.get_proc_name proc_desc in
let check_address_of_cpp_temporary () = let check_address_of_cpp_temporary () =
Memory.find_opt address astate.heap Memory.find_opt address astate
|> Option.fold_result ~init:() ~f:(fun () (_, attrs) -> |> Option.fold_result ~init:() ~f:(fun () (_, attrs) ->
Container.fold_result ~fold:(IContainer.fold_of_pervasives_fold ~fold:Attributes.fold) IContainer.iter_result ~fold:(IContainer.fold_of_pervasives_fold ~fold:Attributes.fold)
attrs ~init:() ~f:(fun () attr -> attrs ~f:(fun attr ->
match attr with match attr with
| Attribute.AddressOfCppTemporary (variable, location_opt) -> | Attribute.AddressOfCppTemporary (variable, location_opt) ->
let location = Option.value ~default:proc_location location_opt in let location = Option.value ~default:proc_location location_opt in
@ -245,57 +228,58 @@ let check_address_of_local_variable proc_desc address astate =
Ok () ) ) Ok () ) )
in in
let check_address_of_stack_variable () = let check_address_of_stack_variable () =
Container.fold_result ~fold:(IContainer.fold_of_pervasives_map_fold ~fold:Stack.fold) IContainer.iter_result ~fold:(IContainer.fold_of_pervasives_map_fold ~fold:Stack.fold) astate
astate.stack ~init:() ~f:(fun () (variable, (var_address, init_location)) -> ~f:(fun (variable, (var_address, init_location)) ->
if if
AbstractAddress.equal var_address address AbstractAddress.equal var_address address
&& ( Var.is_cpp_temporary variable && ( Var.is_cpp_temporary variable
|| Var.is_local_to_procedure proc_name variable || Var.is_local_to_procedure proc_name variable
&& not (Procdesc.is_captured_var proc_desc variable) ) && not (Procdesc.is_captured_var proc_desc variable) )
then then (
let location = Option.value ~default:proc_location init_location in let location = Option.value ~default:proc_location init_location in
Error (PulseDiagnostic.StackVariableAddressEscape {variable; location}) L.d_printfln_escaped "Stack Variable &%a detected at address %a" Var.pp variable
AbstractAddress.pp address ;
Error (PulseDiagnostic.StackVariableAddressEscape {variable; location}) )
else Ok () ) else Ok () )
in in
check_address_of_cpp_temporary () >>= check_address_of_stack_variable >>| fun () -> astate check_address_of_cpp_temporary () >>= check_address_of_stack_variable >>| fun () -> astate
let mark_address_of_cpp_temporary location variable address heap = let mark_address_of_cpp_temporary location variable address astate =
Memory.add_attributes address Memory.add_attributes address
(Attributes.singleton (AddressOfCppTemporary (variable, location))) (Attributes.singleton (AddressOfCppTemporary (variable, location)))
heap astate
let remove_vars vars astate = let remove_vars vars astate =
let heap = let astate =
List.fold vars ~init:astate.heap ~f:(fun heap var -> List.fold vars ~init:astate ~f:(fun heap var ->
match Stack.find_opt var astate.stack with match Stack.find_opt var astate with
| Some (address, location) when Var.is_cpp_temporary var -> | Some (address, location) when Var.is_cpp_temporary var ->
(* TODO: it would be good to record the location of the temporary creation in the (* 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 *) stack and save it here in the attribute for reporting *)
mark_address_of_cpp_temporary location var address heap mark_address_of_cpp_temporary location var address astate
| _ -> | _ ->
heap ) heap )
in in
let stack = Stack.filter (fun var _ -> not (List.mem ~equal:Var.equal vars var)) astate.stack in let astate' = Stack.remove_vars vars astate in
if phys_equal stack astate.stack && phys_equal heap astate.heap then astate if phys_equal astate' astate then astate else PulseAbductiveDomain.discard_unreachable astate'
else PulseDomain.minimize {stack; heap}
let record_var_decl_location location var astate = let record_var_decl_location location var astate =
let addr = let addr =
match Stack.find_opt var astate.stack with match Stack.find_opt var astate with
| Some (addr, _) -> | Some (addr, _) ->
addr addr
| None -> | None ->
AbstractAddress.mk_fresh () AbstractAddress.mk_fresh ()
in in
let stack = Stack.add var (addr, Some location) astate.stack in Stack.add var (addr, Some location) astate
{astate with stack}
module Closures = struct module Closures = struct
open Result.Monad_infix open Result.Monad_infix
module Memory = PulseAbductiveDomain.Memory
let fake_capture_field_prefix = "__capture_" let fake_capture_field_prefix = "__capture_"
@ -324,7 +308,7 @@ module Closures = struct
let check_captured_addresses location lambda addr astate = let check_captured_addresses location lambda addr astate =
match Memory.find_opt addr astate.heap with match Memory.find_opt addr astate with
| None -> | None ->
Ok astate Ok astate
| Some (edges, attributes) -> | Some (edges, attributes) ->
@ -350,12 +334,7 @@ module Closures = struct
astate astate
>>| fun astate -> >>| fun astate ->
let fake_capture_edges = mk_capture_edges captured in let fake_capture_edges = mk_capture_edges captured in
let heap = Memory.set_cell closure_addr (fake_capture_edges, Attributes.singleton (Closure pname)) astate
Memory.set_cell closure_addr
(fake_capture_edges, Attributes.singleton (Closure pname))
astate.heap
in
{astate with heap}
let record location access_expr pname captured astate = let record location access_expr pname captured astate =
@ -377,13 +356,14 @@ end
module StdVector = struct module StdVector = struct
open Result.Monad_infix open Result.Monad_infix
module Memory = PulseAbductiveDomain.Memory
let is_reserved location vector_access_expr astate = let is_reserved location vector_access_expr astate =
read location vector_access_expr astate read location vector_access_expr astate
>>| fun (astate, (addr, _)) -> (astate, Memory.is_std_vector_reserved addr astate.heap) >>| fun (astate, (addr, _)) -> (astate, Memory.is_std_vector_reserved addr astate)
let mark_reserved location vector_access_expr astate = let mark_reserved location vector_access_expr astate =
read location vector_access_expr astate read location vector_access_expr astate
>>| fun (astate, (addr, _)) -> {astate with heap= Memory.std_vector_reserve addr astate.heap} >>| fun (astate, (addr, _)) -> Memory.std_vector_reserve addr astate
end end

@ -6,8 +6,9 @@
*) *)
open! IStd open! IStd
module AbstractAddress = PulseDomain.AbstractAddress
type t = PulseDomain.t = {heap: PulseDomain.Memory.t; stack: PulseDomain.Stack.t} type t = PulseAbductiveDomain.t
type 'a access_result = ('a, PulseDiagnostic.t) result type 'a access_result = ('a, PulseDiagnostic.t) result
@ -40,7 +41,7 @@ val read :
Location.t Location.t
-> HilExp.AccessExpression.t -> HilExp.AccessExpression.t
-> t -> t
-> (t * (PulseDomain.AbstractAddress.t * PulseTrace.t)) access_result -> (t * (AbstractAddress.t * PulseTrace.t)) access_result
val read_all : Location.t -> HilExp.AccessExpression.t list -> t -> t access_result val read_all : Location.t -> HilExp.AccessExpression.t list -> t -> t access_result
@ -48,12 +49,12 @@ val havoc_var : PulseTrace.t -> Var.t -> t -> t
val havoc : PulseTrace.t -> Location.t -> HilExp.AccessExpression.t -> t -> t access_result 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_var : Var.t -> AbstractAddress.t * PulseTrace.t -> t -> t
val write : val write :
Location.t Location.t
-> HilExp.AccessExpression.t -> HilExp.AccessExpression.t
-> PulseDomain.AbstractAddress.t * PulseTrace.t -> AbstractAddress.t * PulseTrace.t
-> t -> t
-> t access_result -> t access_result
@ -68,5 +69,4 @@ val record_var_decl_location : Location.t -> Var.t -> t -> t
val remove_vars : Var.t list -> t -> t val remove_vars : Var.t list -> t -> t
(* TODO: better name and pass location to report where we returned *) (* TODO: better name and pass location to report where we returned *)
val check_address_of_local_variable : val check_address_of_local_variable : Procdesc.t -> AbstractAddress.t -> t -> t access_result
Procdesc.t -> PulseDomain.AbstractAddress.t -> t -> t access_result

Loading…
Cancel
Save