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.

168 lines
5.9 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
open PulseBasicInterface
module BaseAddressAttributes = PulseBaseAddressAttributes
module BaseDomain = PulseBaseDomain
module BaseMemory = PulseBaseMemory
module BaseStack = PulseBaseStack
(** Layer on top of {!BaseDomain} 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. *)
(** 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
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 partition_addr :
f:(AbstractValue.t -> bool)
-> t
-> (BaseMemory.t * BaseAddressAttributes.t) * (BaseMemory.t * BaseAddressAttributes.t)
(**partition both heap and attrs *)
val pp : F.formatter -> t -> unit
end
module PostDomain : BaseDomainSig
(** The post abstract state at each program point, or current state. *)
module PreDomain : BaseDomainSig
(** The inferred pre-condition at each program point, biabduction style.
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. * *)
(** biabduction-style pre/post state + skipped calls *)
type t = private
{ post: PostDomain.t (** state at the current program point*)
; pre: PreDomain.t (** inferred pre at the current program point *)
; skipped_calls: SkippedCalls.t (** set of skipped calls *)
; path_condition: PathCondition.t (** arithmetic facts *) }
val leq : lhs:t -> rhs:t -> bool
val pp : Format.formatter -> t -> unit
val mk_initial : Procdesc.t -> t
val get_pre : t -> BaseDomain.t
val get_post : t -> BaseDomain.t
(** stack operations like {!BaseStack} but that also take care of propagating facts to the
precondition *)
module Stack : sig
val add : Var.t -> BaseStack.value -> t -> t
val remove_vars : Var.t list -> t -> t
val fold : (Var.t -> BaseStack.value -> 'a -> 'a) -> t -> 'a -> 'a
val find_opt : Var.t -> t -> BaseStack.value option
val eval : ValueHistory.t -> Var.t -> t -> t * (AbstractValue.t * ValueHistory.t)
(** return the value of the variable in the stack or create a fresh one if needed *)
val mem : Var.t -> t -> bool
val exists : (Var.t -> BaseStack.value -> bool) -> t -> bool
end
(** memory operations like {!BaseMemory} but that also take care of propagating facts to the
precondition *)
module Memory : sig
module Access = BaseMemory.Access
module Edges = BaseMemory.Edges
val add_edge :
AbstractValue.t * ValueHistory.t
-> Access.t
-> AbstractValue.t * ValueHistory.t
-> Location.t
-> t
-> t
val eval_edge :
AbstractValue.t * ValueHistory.t -> Access.t -> t -> t * (AbstractValue.t * ValueHistory.t)
(** [eval_edge (addr,hist) access astate] follows the edge [addr --access--> .] in memory and
returns what it points to or creates a fresh value if that edge didn't exist. *)
val find_opt : AbstractValue.t -> t -> BaseMemory.Edges.t option
val find_edge_opt : AbstractValue.t -> Access.t -> t -> (AbstractValue.t * ValueHistory.t) option
end
(** attribute operations like {!BaseAddressAttributes} but that also take care of propagating facts
to the precondition *)
module AddressAttributes : sig
val abduce_attribute : AbstractValue.t -> Attribute.t -> t -> t
(** add the attribute to the pre, if meaningful (does not modify the post) *)
val abduce_and_add : AbstractValue.t -> Attributes.t -> t -> t
(** add the attributes to both the current state and, if meaningful, the pre *)
val add_one : AbstractValue.t -> Attribute.t -> t -> t
(** add the attribute only to the post *)
val check_valid : Trace.t -> AbstractValue.t -> t -> (t, Invalidation.t * Trace.t) result
val invalidate : AbstractValue.t * ValueHistory.t -> Invalidation.t -> Location.t -> t -> t
val allocate : Procname.t -> AbstractValue.t * ValueHistory.t -> Location.t -> t -> t
val get_closure_proc_name : AbstractValue.t -> t -> Procname.t option
val is_std_vector_reserved : AbstractValue.t -> t -> bool
val std_vector_reserve : AbstractValue.t -> t -> t
val get_citv : AbstractValue.t -> t -> (CItv.t * Trace.t) option
val get_bo_itv : AbstractValue.t -> t -> Itv.ItvPure.t
val find_opt : AbstractValue.t -> t -> Attributes.t option
end
val is_local : Var.t -> t -> bool
val find_post_cell_opt : AbstractValue.t -> t -> BaseDomain.cell option
val discard_unreachable : t -> t * AbstractValue.Set.t * BaseAddressAttributes.t
(** [discard_unreachable astate] garbage collects unreachable addresses in the state to make it
smaller, and retuns the new state, the live addresses, and the attributes of discarded addresses *)
val add_skipped_call : Procname.t -> Trace.t -> t -> t
val add_skipped_calls : SkippedCalls.t -> t -> t
val get_path_condition : t -> PathCondition.t
val set_path_condition : PathCondition.t -> t -> t
[pulse] Distinguish exit state at top level Summary: This diff lifts the `PulseAbductiveDomain.t` in `PulseExecutionState` by tracking whether the program continues the analysis normally or exits unusually (e.g. by calling `exit` or `throw`): ``` type exec_state = | ContinueProgram of PulseAbductiveDomain.t (** represents the state at the program point *) | ExitProgram of PulseAbductiveDomain.t (** represents the state originating at exit/divergence. *) ``` Now, Pulse's actual domain is tracked by `PulseExecutionState` and as soon as we try to analyze an instruction at `ExitProgram`, we simply return its state. The aim is to recover the state at the time of the exit, rather than simply ignoring them (i.e. returning empty disjuncts). This allows us to get rid of some FNs that we were not able to detect before. Moreover, it also allows the impurity analysis to be more precise since we will know how the state changed up to exit. TODO: - Impurity analysis needs to be improved to consider functions that simply exit as impure. - The next goal is to handle error state similarly so that when pulse finds an error, we recover the state at the error location (and potentially continue to analyze?). Disclaimer: currently, we handle throw statements like exit (as was the case before). However, this is not correct. Ideally, control flow from throw nodes follows catch nodes rather than exiting the program entirely. Reviewed By: jvillard Differential Revision: D20791747 fbshipit-source-id: df9e5445a
5 years ago
val of_post : Procdesc.t -> t -> t
val set_post_edges : AbstractValue.t -> BaseMemory.Edges.t -> t -> t
(** directly set the edges for the given address, bypassing abduction altogether *)
val set_post_cell : AbstractValue.t * ValueHistory.t -> BaseDomain.cell -> Location.t -> t -> t
(** directly set the edges and attributes for the given address, bypassing abduction altogether *)