You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
614 lines
23 KiB
614 lines
23 KiB
(*
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*)
|
|
|
|
open! IStd
|
|
module F = Format
|
|
module L = Logging
|
|
open PulseBasicInterface
|
|
module BaseDomain = PulseBaseDomain
|
|
module BaseStack = PulseBaseStack
|
|
module BaseMemory = PulseBaseMemory
|
|
module BaseAddressAttributes = PulseBaseAddressAttributes
|
|
|
|
(** signature common to the "normal" [Domain], representing the post at the current program point,
|
|
and the inverted [PreDomain], representing the inferred pre-condition*)
|
|
module type BaseDomainSig = sig
|
|
(* private because the lattice is not the same for preconditions and postconditions so we don't
|
|
want to confuse them *)
|
|
type t = private BaseDomain.t [@@deriving yojson_of]
|
|
|
|
val empty : t
|
|
|
|
val update : ?stack:BaseStack.t -> ?heap:BaseMemory.t -> ?attrs:BaseAddressAttributes.t -> t -> t
|
|
|
|
val filter_addr : f:(AbstractValue.t -> bool) -> t -> t
|
|
(** filter both heap and attrs *)
|
|
|
|
val filter_addr_with_discarded_addrs :
|
|
f:(AbstractValue.t -> bool) -> t -> t * AbstractValue.t list
|
|
(** compute new state containing only reachable addresses in its heap and attributes, as well as
|
|
the list of discarded unreachable addresses *)
|
|
|
|
val pp : F.formatter -> t -> unit
|
|
end
|
|
|
|
(* just to expose record field names without having to type
|
|
[BaseDomain.heap] *)
|
|
type base_domain = BaseDomain.t =
|
|
{heap: BaseMemory.t; stack: BaseStack.t; attrs: BaseAddressAttributes.t}
|
|
|
|
(** represents the post abstract state at each program point *)
|
|
module PostDomain : sig
|
|
include BaseDomainSig
|
|
|
|
val initialize : AbstractValue.t -> t -> t
|
|
end = struct
|
|
include BaseDomain
|
|
|
|
let update ?stack ?heap ?attrs foot =
|
|
let new_stack, new_heap, new_attrs =
|
|
( Option.value ~default:foot.stack stack
|
|
, Option.value ~default:foot.heap heap
|
|
, Option.value ~default:foot.attrs attrs )
|
|
in
|
|
if
|
|
phys_equal new_stack foot.stack && phys_equal new_heap foot.heap
|
|
&& phys_equal new_attrs foot.attrs
|
|
then foot
|
|
else {stack= new_stack; heap= new_heap; attrs= new_attrs}
|
|
|
|
|
|
let filter_addr ~f foot =
|
|
let heap' = BaseMemory.filter (fun address _ -> f address) foot.heap in
|
|
let attrs' = BaseAddressAttributes.filter (fun address _ -> f address) foot.attrs in
|
|
update ~heap:heap' ~attrs:attrs' foot
|
|
|
|
|
|
let filter_addr_with_discarded_addrs ~f foot =
|
|
let heap' = BaseMemory.filter (fun address _ -> f address) foot.heap in
|
|
let attrs', discarded_addresses =
|
|
BaseAddressAttributes.filter_with_discarded_addrs (fun address _ -> f address) foot.attrs
|
|
in
|
|
(update ~heap:heap' ~attrs:attrs' foot, discarded_addresses)
|
|
|
|
|
|
let initialize address x =
|
|
let attrs = BaseAddressAttributes.initialize address x.attrs in
|
|
update ~attrs x
|
|
end
|
|
|
|
(* NOTE: [PreDomain] and [Domain] theoretically differ in that [PreDomain] should be the inverted lattice of [Domain], but since we never actually join states or check implication the two collapse into one. *)
|
|
|
|
(** represents the inferred pre-condition at each program point, biabduction style *)
|
|
module PreDomain : BaseDomainSig = PostDomain
|
|
|
|
(** biabduction-style pre/post state + skipped calls *)
|
|
type t =
|
|
{ post: PostDomain.t (** state at the current program point*)
|
|
; pre: PreDomain.t (** inferred pre at the current program point *)
|
|
; topl: (PulseTopl.state[@yojson.opaque])
|
|
; skipped_calls: SkippedCalls.t (** set of skipped calls *)
|
|
; path_condition: PathCondition.t }
|
|
[@@deriving yojson_of]
|
|
|
|
let pp f {post; pre; topl; path_condition; skipped_calls} =
|
|
F.fprintf f "@[<v>%a@;%a@;PRE=[%a]@;skipped_calls=%a@;TOPL=%a@]" PathCondition.pp path_condition
|
|
PostDomain.pp post PreDomain.pp pre SkippedCalls.pp skipped_calls PulseTopl.pp_state topl
|
|
|
|
|
|
let set_path_condition path_condition astate = {astate with path_condition}
|
|
|
|
let leq ~lhs ~rhs =
|
|
SkippedCalls.leq ~lhs:lhs.skipped_calls ~rhs:rhs.skipped_calls
|
|
&&
|
|
match
|
|
BaseDomain.isograph_map BaseDomain.empty_mapping
|
|
~lhs:(rhs.pre :> BaseDomain.t)
|
|
~rhs:(lhs.pre :> BaseDomain.t)
|
|
with
|
|
| NotIsomorphic ->
|
|
false
|
|
| IsomorphicUpTo foot_mapping ->
|
|
BaseDomain.is_isograph foot_mapping
|
|
~lhs:(lhs.post :> BaseDomain.t)
|
|
~rhs:(rhs.post :> BaseDomain.t)
|
|
|
|
|
|
let initialize address astate = {astate with post= PostDomain.initialize address astate.post}
|
|
|
|
module Stack = struct
|
|
let is_abducible astate var =
|
|
(* HACK: formals are pre-registered in the initial state *)
|
|
BaseStack.mem var (astate.pre :> base_domain).stack || Var.is_global var
|
|
|
|
|
|
(** [astate] with [astate.post.stack = f astate.post.stack] *)
|
|
let map_post_stack ~f astate =
|
|
let new_post = PostDomain.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 eval origin var astate =
|
|
match BaseStack.find_opt var (astate.post :> base_domain).stack with
|
|
| Some addr_hist ->
|
|
(astate, addr_hist)
|
|
| None ->
|
|
let addr = AbstractValue.mk_fresh () in
|
|
let addr_hist = (addr, origin) in
|
|
let post_stack = BaseStack.add var addr_hist (astate.post :> base_domain).stack in
|
|
let pre =
|
|
(* do not overwrite values of variables already in the pre *)
|
|
if (not (BaseStack.mem var (astate.pre :> base_domain).stack)) && is_abducible astate var
|
|
then
|
|
(* HACK: do not record the history of values in the pre as they are unused *)
|
|
let foot_stack = BaseStack.add var (addr, []) (astate.pre :> base_domain).stack in
|
|
let foot_heap = BaseMemory.register_address addr (astate.pre :> base_domain).heap in
|
|
PreDomain.update ~stack:foot_stack ~heap:foot_heap astate.pre
|
|
else astate.pre
|
|
in
|
|
( { post= PostDomain.update astate.post ~stack:post_stack
|
|
; pre
|
|
; topl= astate.topl
|
|
; skipped_calls= astate.skipped_calls
|
|
; path_condition= astate.path_condition }
|
|
, addr_hist )
|
|
|
|
|
|
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 =
|
|
let vars_to_remove =
|
|
let is_in_pre var astate = BaseStack.mem var (astate.pre :> base_domain).stack in
|
|
List.filter vars ~f:(fun var -> not (is_in_pre var astate))
|
|
in
|
|
map_post_stack astate ~f:(fun stack ->
|
|
BaseStack.filter (fun var _ -> not (List.mem ~equal:Var.equal vars_to_remove 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
|
|
|
|
let mem var astate = BaseStack.mem var (astate.post :> base_domain).stack
|
|
|
|
let exists f astate = BaseStack.exists f (astate.post :> base_domain).stack
|
|
|
|
let keys astate =
|
|
BaseStack.fold (fun key _ keys -> key :: keys) (astate.post :> base_domain).stack []
|
|
end
|
|
|
|
module AddressAttributes = struct
|
|
open IResult.Let_syntax
|
|
|
|
(** if [address] is in [pre] then add the attribute [attr] *)
|
|
let abduce_attribute address attribute astate =
|
|
L.d_printfln "Abducing %a:%a" AbstractValue.pp address Attribute.pp attribute ;
|
|
let new_pre =
|
|
if BaseMemory.mem address (astate.pre :> base_domain).heap then
|
|
PreDomain.update astate.pre
|
|
~attrs:(BaseAddressAttributes.add_one address attribute (astate.pre :> base_domain).attrs)
|
|
else astate.pre
|
|
in
|
|
if phys_equal new_pre astate.pre then astate else {astate with pre= new_pre}
|
|
|
|
|
|
let check_valid access_trace addr astate =
|
|
let+ () = BaseAddressAttributes.check_valid addr (astate.post :> base_domain).attrs in
|
|
(* if [address] is in [pre] and it should be valid then that fact goes in the precondition *)
|
|
abduce_attribute addr (MustBeValid access_trace) astate
|
|
|
|
|
|
let check_initialized access_trace addr astate =
|
|
let attrs = (astate.post :> base_domain).attrs in
|
|
let+ () = BaseAddressAttributes.check_initialized addr attrs in
|
|
let is_written_to =
|
|
Option.exists (BaseAddressAttributes.find_opt addr attrs) ~f:(fun attrs ->
|
|
Attribute.Attributes.get_written_to attrs |> Option.is_some )
|
|
in
|
|
if is_written_to then astate else abduce_attribute addr (MustBeInitialized access_trace) astate
|
|
|
|
|
|
(** [astate] with [astate.post.attrs = f astate.post.attrs] *)
|
|
let map_post_attrs ~f astate =
|
|
let new_post = PostDomain.update astate.post ~attrs:(f (astate.post :> base_domain).attrs) in
|
|
if phys_equal new_post astate.post then astate else {astate with post= new_post}
|
|
|
|
|
|
let invalidate address invalidation location astate =
|
|
map_post_attrs astate ~f:(BaseAddressAttributes.invalidate address invalidation location)
|
|
|
|
|
|
let allocate procname address location astate =
|
|
map_post_attrs astate ~f:(BaseAddressAttributes.allocate procname address location)
|
|
|
|
|
|
let add_dynamic_type typ address astate =
|
|
map_post_attrs astate ~f:(BaseAddressAttributes.add_dynamic_type typ address)
|
|
|
|
|
|
let remove_allocation_attr address astate =
|
|
map_post_attrs astate ~f:(BaseAddressAttributes.remove_allocation_attr address)
|
|
|
|
|
|
let add_one address attributes astate =
|
|
map_post_attrs astate ~f:(BaseAddressAttributes.add_one address attributes)
|
|
|
|
|
|
let get_closure_proc_name addr astate =
|
|
BaseAddressAttributes.get_closure_proc_name addr (astate.post :> base_domain).attrs
|
|
|
|
|
|
let std_vector_reserve addr astate =
|
|
map_post_attrs astate ~f:(BaseAddressAttributes.std_vector_reserve addr)
|
|
|
|
|
|
let is_std_vector_reserved addr astate =
|
|
BaseAddressAttributes.is_std_vector_reserved addr (astate.post :> base_domain).attrs
|
|
|
|
|
|
let mark_as_end_of_collection addr astate =
|
|
map_post_attrs astate ~f:(BaseAddressAttributes.mark_as_end_of_collection addr)
|
|
|
|
|
|
let is_end_of_collection addr astate =
|
|
BaseAddressAttributes.is_end_of_collection addr (astate.post :> base_domain).attrs
|
|
|
|
|
|
let abduce_and_add value attrs astate =
|
|
Attributes.fold attrs ~init:astate ~f:(fun astate attr ->
|
|
let astate =
|
|
if Attribute.is_suitable_for_pre attr then abduce_attribute value attr astate else astate
|
|
in
|
|
let astate = add_one value attr astate in
|
|
match attr with Attribute.WrittenTo _ -> initialize value astate | _ -> astate )
|
|
|
|
|
|
let find_opt address astate =
|
|
BaseAddressAttributes.find_opt address (astate.post :> base_domain).attrs
|
|
end
|
|
|
|
module Memory = struct
|
|
module Access = BaseMemory.Access
|
|
module Edges = BaseMemory.Edges
|
|
|
|
(** [astate] with [astate.post.heap = f astate.post.heap] *)
|
|
let map_post_heap ~f astate =
|
|
let new_post = PostDomain.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}
|
|
|
|
|
|
let add_edge (addr, history) access new_addr_hist location astate =
|
|
map_post_heap astate ~f:(BaseMemory.add_edge addr access new_addr_hist)
|
|
|> AddressAttributes.map_post_attrs
|
|
~f:(BaseAddressAttributes.add_one addr (WrittenTo (Trace.Immediate {location; history})))
|
|
|
|
|
|
let find_edge_opt address access astate =
|
|
BaseMemory.find_edge_opt address access (astate.post :> base_domain).heap
|
|
|
|
|
|
let eval_edge (addr_src, hist_src) access astate =
|
|
match find_edge_opt addr_src access astate with
|
|
| Some addr_hist_dst ->
|
|
(astate, addr_hist_dst)
|
|
| None ->
|
|
let addr_dst = AbstractValue.mk_fresh () in
|
|
let addr_hist_dst = (addr_dst, hist_src) in
|
|
let post_heap =
|
|
BaseMemory.add_edge addr_src access addr_hist_dst (astate.post :> base_domain).heap
|
|
in
|
|
let foot_heap =
|
|
if BaseMemory.mem addr_src (astate.pre :> base_domain).heap then
|
|
(* HACK: do not record the history of values in the pre as they are unused *)
|
|
BaseMemory.add_edge addr_src access (addr_dst, []) (astate.pre :> base_domain).heap
|
|
|> BaseMemory.register_address addr_dst
|
|
else (astate.pre :> base_domain).heap
|
|
in
|
|
( { post= PostDomain.update astate.post ~heap:post_heap
|
|
; pre= PreDomain.update astate.pre ~heap:foot_heap
|
|
; topl= astate.topl
|
|
; skipped_calls= astate.skipped_calls
|
|
; path_condition= astate.path_condition }
|
|
, addr_hist_dst )
|
|
|
|
|
|
let find_opt address astate = BaseMemory.find_opt address (astate.post :> base_domain).heap
|
|
end
|
|
|
|
let set_uninitialized_post src typ location (post : PostDomain.t) =
|
|
match typ.Typ.desc with
|
|
| Tint _ | Tfloat _ | Tptr _ ->
|
|
let {stack; attrs} = (post :> base_domain) in
|
|
let stack, addr, history =
|
|
match src with
|
|
| `LocalDecl (pvar, addr_opt) ->
|
|
let history = [ValueHistory.VariableDeclared (pvar, location)] in
|
|
let stack, addr =
|
|
match addr_opt with
|
|
| None ->
|
|
let addr = AbstractValue.mk_fresh () in
|
|
(BaseStack.add (Var.of_pvar pvar) (addr, history) stack, addr)
|
|
| Some addr ->
|
|
(stack, addr)
|
|
in
|
|
(stack, addr, history)
|
|
| `Malloc (addr, history) ->
|
|
(stack, addr, history)
|
|
in
|
|
let attrs =
|
|
BaseAddressAttributes.add_one addr (Uninitialized (Immediate {location; history})) attrs
|
|
in
|
|
PostDomain.update ~stack ~attrs post
|
|
| Tstruct _ ->
|
|
(* TODO: set uninitialized attributes for fields *)
|
|
post
|
|
| Tarray _ ->
|
|
(* TODO: set uninitialized attributes for elements *)
|
|
post
|
|
| Tvoid | Tfun | TVar _ ->
|
|
(* We ignore tricky types to mark uninitialized addresses. *)
|
|
post
|
|
|
|
|
|
let set_uninitialized src typ location x =
|
|
{x with post= set_uninitialized_post src typ location x.post}
|
|
|
|
|
|
let mk_initial proc_desc =
|
|
(* HACK: save the formals in the stacks of the pre and the post to remember which local variables
|
|
correspond to formals *)
|
|
let proc_name = Procdesc.get_proc_name proc_desc in
|
|
let location = Procdesc.get_loc proc_desc in
|
|
let formals_and_captured =
|
|
let init_var mangled =
|
|
let pvar = Pvar.mk mangled proc_name in
|
|
(Var.of_pvar pvar, (AbstractValue.mk_fresh (), [ValueHistory.FormalDeclared (pvar, location)]))
|
|
in
|
|
let formals =
|
|
Procdesc.get_formals proc_desc |> List.map ~f:(fun (mangled, _) -> init_var mangled)
|
|
in
|
|
let captured =
|
|
Procdesc.get_captured proc_desc |> List.map ~f:(fun {CapturedVar.name} -> init_var name)
|
|
in
|
|
captured @ formals
|
|
in
|
|
let initial_stack =
|
|
List.fold formals_and_captured ~init:(PreDomain.empty :> BaseDomain.t).stack
|
|
~f:(fun stack (formal, addr_loc) -> BaseStack.add formal addr_loc stack)
|
|
in
|
|
let pre =
|
|
let initial_heap =
|
|
List.fold formals_and_captured ~init:(PreDomain.empty :> base_domain).heap
|
|
~f:(fun heap (_, (addr, _)) -> BaseMemory.register_address addr heap)
|
|
in
|
|
PreDomain.update ~stack:initial_stack ~heap:initial_heap PreDomain.empty
|
|
in
|
|
let locals = Procdesc.get_locals proc_desc in
|
|
let post = PostDomain.update ~stack:initial_stack PostDomain.empty in
|
|
let post =
|
|
List.fold locals ~init:post ~f:(fun (acc : PostDomain.t) {ProcAttributes.name; typ} ->
|
|
set_uninitialized_post (`LocalDecl (Pvar.mk name proc_name, None)) typ location acc )
|
|
in
|
|
{ pre
|
|
; post
|
|
; topl= PulseTopl.start ()
|
|
; skipped_calls= SkippedCalls.empty
|
|
; path_condition= PathCondition.true_ }
|
|
|
|
|
|
let add_skipped_call pname trace astate =
|
|
let new_skipped_calls = SkippedCalls.add pname trace astate.skipped_calls in
|
|
if phys_equal new_skipped_calls astate.skipped_calls then astate
|
|
else {astate with skipped_calls= new_skipped_calls}
|
|
|
|
|
|
let add_skipped_calls new_skipped_calls astate =
|
|
(* favor calls we already knew about somewhat arbitrarily *)
|
|
let skipped_calls =
|
|
SkippedCalls.union
|
|
(fun _ orig_call _new_call -> Some orig_call)
|
|
astate.skipped_calls new_skipped_calls
|
|
in
|
|
if phys_equal skipped_calls astate.skipped_calls then astate else {astate with skipped_calls}
|
|
|
|
|
|
let discard_unreachable ({pre; post} as astate) =
|
|
let pre_addresses = BaseDomain.reachable_addresses (pre :> BaseDomain.t) in
|
|
let pre_new =
|
|
PreDomain.filter_addr ~f:(fun address -> AbstractValue.Set.mem address pre_addresses) pre
|
|
in
|
|
let post_addresses = BaseDomain.reachable_addresses (post :> BaseDomain.t) in
|
|
let live_addresses = AbstractValue.Set.union pre_addresses post_addresses in
|
|
let post_new, discard_addresses =
|
|
PostDomain.filter_addr_with_discarded_addrs
|
|
~f:(fun address -> AbstractValue.Set.mem address live_addresses)
|
|
post
|
|
in
|
|
(* note: we don't call {!PulsePathCondition.simplify} *)
|
|
let astate =
|
|
if phys_equal pre_new pre && phys_equal post_new post then astate
|
|
else {astate with pre= pre_new; post= post_new}
|
|
in
|
|
(astate, live_addresses, discard_addresses)
|
|
|
|
|
|
let is_local var astate = not (Var.is_return var || Stack.is_abducible astate var)
|
|
|
|
let set_post_edges addr edges astate = Memory.map_post_heap astate ~f:(BaseMemory.add addr edges)
|
|
|
|
(* {3 Helper functions to traverse the two maps at once } *)
|
|
|
|
let find_post_cell_opt addr {post} = BaseDomain.find_cell_opt addr (post :> BaseDomain.t)
|
|
|
|
let set_post_cell (addr, history) (edges, attr_set) location astate =
|
|
set_post_edges addr edges astate
|
|
|> AddressAttributes.map_post_attrs ~f:(fun attrs ->
|
|
BaseAddressAttributes.add_one addr (WrittenTo (Trace.Immediate {location; history})) attrs
|
|
|> BaseAddressAttributes.add addr attr_set )
|
|
|
|
|
|
let filter_stack_for_summary astate =
|
|
let post_stack =
|
|
BaseStack.filter
|
|
(fun var _ -> Var.appears_in_source_code var && not (is_local var astate))
|
|
(astate.post :> BaseDomain.t).stack
|
|
in
|
|
{astate with post= PostDomain.update ~stack:post_stack astate.post}
|
|
|
|
|
|
let add_out_of_scope_attribute addr pvar location history heap typ =
|
|
BaseAddressAttributes.add_one addr
|
|
(Invalid (GoneOutOfScope (pvar, typ), Immediate {location; history}))
|
|
heap
|
|
|
|
|
|
(** invalidate local variables going out of scope *)
|
|
let invalidate_locals pdesc astate : t =
|
|
let attrs : BaseAddressAttributes.t = (astate.post :> BaseDomain.t).attrs in
|
|
let attrs' =
|
|
BaseAddressAttributes.fold
|
|
(fun addr attrs acc ->
|
|
Attributes.get_address_of_stack_variable attrs
|
|
|> Option.value_map ~default:acc ~f:(fun (var, location, history) ->
|
|
let get_local_typ_opt pvar =
|
|
Procdesc.get_locals pdesc
|
|
|> List.find_map ~f:(fun ProcAttributes.{name; typ} ->
|
|
if Mangled.equal name (Pvar.get_name pvar) then Some typ else None )
|
|
in
|
|
match var with
|
|
| Var.ProgramVar pvar ->
|
|
get_local_typ_opt pvar
|
|
|> Option.value_map ~default:acc
|
|
~f:(add_out_of_scope_attribute addr pvar location history acc)
|
|
| _ ->
|
|
acc ) )
|
|
attrs attrs
|
|
in
|
|
if phys_equal attrs attrs' then astate
|
|
else {astate with post= PostDomain.update astate.post ~attrs:attrs'}
|
|
|
|
|
|
type summary = t [@@deriving yojson_of]
|
|
|
|
let is_allocated {post; pre} v =
|
|
let is_pvar = function Var.ProgramVar _ -> true | Var.LogicalVar _ -> false in
|
|
let is_stack_allocated base_mem v =
|
|
BaseStack.exists
|
|
(fun var (address, _) -> is_pvar var && AbstractValue.equal address v)
|
|
base_mem.stack
|
|
in
|
|
(* OPTIM: the post stack contains at least the pre stack so no need to check both *)
|
|
BaseMemory.is_allocated (post :> BaseDomain.t).heap v
|
|
|| BaseMemory.is_allocated (pre :> BaseDomain.t).heap v
|
|
|| is_stack_allocated (post :> BaseDomain.t) v
|
|
|
|
|
|
let incorporate_new_eqs astate new_eqs =
|
|
List.fold_until new_eqs ~init:()
|
|
~finish:(fun () -> Sat ())
|
|
~f:(fun () (new_eq : PulseFormula.new_eq) ->
|
|
match new_eq with
|
|
| EqZero v when is_allocated astate v ->
|
|
L.d_printfln "CONTRADICTION: %a = 0 but is allocated" AbstractValue.pp v ;
|
|
Stop Unsat
|
|
| Equal (v1, v2)
|
|
when (not (AbstractValue.equal v1 v2)) && is_allocated astate v1 && is_allocated astate v2
|
|
->
|
|
L.d_printfln "CONTRADICTION: %a = %a but both are separately allocated" AbstractValue.pp
|
|
v1 AbstractValue.pp v2 ;
|
|
Stop Unsat
|
|
| _ ->
|
|
Continue () )
|
|
|
|
|
|
(** it's a good idea to normalize the path condition before calling this function *)
|
|
let canonicalize astate =
|
|
let open SatUnsat.Import in
|
|
let get_var_repr v = PathCondition.get_var_repr astate.path_condition v in
|
|
let canonicalize_base stack heap attrs =
|
|
let stack' = BaseStack.canonicalize ~get_var_repr stack in
|
|
(* note: this step also de-registers addresses pointing to empty edges *)
|
|
let+ heap' = BaseMemory.canonicalize ~get_var_repr heap in
|
|
let attrs' = BaseAddressAttributes.canonicalize ~get_var_repr attrs in
|
|
(stack', heap', attrs')
|
|
in
|
|
(* need different functions for pre and post to appease the type system *)
|
|
let canonicalize_pre (pre : PreDomain.t) =
|
|
let+ stack', heap', attrs' =
|
|
canonicalize_base (pre :> BaseDomain.t).stack (pre :> BaseDomain.t).heap
|
|
(pre :> BaseDomain.t).attrs
|
|
in
|
|
PreDomain.update ~stack:stack' ~heap:heap' ~attrs:attrs' pre
|
|
in
|
|
let canonicalize_post (post : PostDomain.t) =
|
|
let+ stack', heap', attrs' =
|
|
canonicalize_base (post :> BaseDomain.t).stack (post :> BaseDomain.t).heap
|
|
(post :> BaseDomain.t).attrs
|
|
in
|
|
PostDomain.update ~stack:stack' ~heap:heap' ~attrs:attrs' post
|
|
in
|
|
let* pre = canonicalize_pre astate.pre in
|
|
let+ post = canonicalize_post astate.post in
|
|
{astate with pre; post}
|
|
|
|
|
|
let summary_of_post pdesc astate =
|
|
let open SatUnsat.Import in
|
|
(* NOTE: we normalize (to strengthen the equality relation used by canonicalization) then
|
|
canonicalize *before* garbage collecting unused addresses in case we detect any last-minute
|
|
contradictions about addresses we are about to garbage collect *)
|
|
let path_condition, is_unsat, new_eqs = PathCondition.is_unsat_expensive astate.path_condition in
|
|
if is_unsat then Unsat
|
|
else
|
|
let astate = {astate with path_condition} in
|
|
let* () = incorporate_new_eqs astate new_eqs in
|
|
L.d_printfln "Canonicalizing...@\n" ;
|
|
let* astate = canonicalize astate in
|
|
L.d_printfln "Canonicalized state: %a@\n" pp astate ;
|
|
let astate = filter_stack_for_summary astate in
|
|
let astate =
|
|
{astate with topl= PulseTopl.filter_for_summary astate.path_condition astate.topl}
|
|
in
|
|
let astate, live_addresses, _ = discard_unreachable astate in
|
|
let* path_condition, new_eqs =
|
|
PathCondition.simplify ~keep:live_addresses astate.path_condition
|
|
in
|
|
let+ () = incorporate_new_eqs astate new_eqs in
|
|
let astate =
|
|
{astate with path_condition; topl= PulseTopl.simplify ~keep:live_addresses astate.topl}
|
|
in
|
|
invalidate_locals pdesc astate
|
|
|
|
|
|
let get_pre {pre} = (pre :> BaseDomain.t)
|
|
|
|
let get_post {post} = (post :> BaseDomain.t)
|
|
|
|
(* re-exported for mli *)
|
|
let incorporate_new_eqs astate (phi, new_eqs) =
|
|
if PathCondition.is_unsat_cheap phi then phi
|
|
else match incorporate_new_eqs astate new_eqs with Unsat -> PathCondition.false_ | Sat () -> phi
|
|
|
|
|
|
module Topl = struct
|
|
let small_step loc event astate =
|
|
{astate with topl= PulseTopl.small_step loc astate.path_condition event astate.topl}
|
|
|
|
|
|
let large_step ~call_location ~callee_proc_name ~substitution ?(condition = PathCondition.true_)
|
|
~callee_prepost astate =
|
|
{ astate with
|
|
topl=
|
|
PulseTopl.large_step ~call_location ~callee_proc_name ~substitution ~condition
|
|
~callee_prepost astate.topl }
|
|
|
|
|
|
let get {topl} = topl
|
|
end
|