Skip to content
Snippets Groups Projects
CfgCompiler.mli 10.07 KiB
(**************************************************************************)
(*                                                                        *)
(*  This file is part of WP plug-in of Frama-C.                           *)
(*                                                                        *)
(*  Copyright (C) 2007-2022                                               *)
(*    CEA (Commissariat a l'energie atomique et aux energies              *)
(*         alternatives)                                                  *)
(*                                                                        *)
(*  you can redistribute it and/or modify it under the terms of the GNU   *)
(*  Lesser General Public License as published by the Free Software       *)
(*  Foundation, version 2.1.                                              *)
(*                                                                        *)
(*  It is distributed in the hope that it will be useful,                 *)
(*  but WITHOUT ANY WARRANTY; without even the implied warranty of        *)
(*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *)
(*  GNU Lesser General Public License for more details.                   *)
(*                                                                        *)
(*  See the GNU Lesser General Public License version 2.1                 *)
(*  for more details (enclosed in the file licenses/LGPLv2.1).            *)
(*                                                                        *)
(**************************************************************************)

open Sigs
open Cil_types
open Lang

(** {2 Control Flow Graphs}

    The semantics of a {i cfg} is a collection of execution traces.  We
    introduce the notion of {i node} which represent a program point.
    In case of loop unrolling of function inlining, a node
    generalize the notion of [stmt] : two distinct nodes may refer to the same
    instruction at different memory states.

    We introduce an interpretation I as a partial mapping from nodes [n:node] to
    memory states [s:M.sigma], denoted I(n). The notation I(n) seen as a predicate
    indicates if `n` is in the partial mapping.

    Given a cfg, a node can be associated to {i assumptions} to filter
    interpretation against the memory state at this point.

    Effects and predicates are defined {i wrt} some fresh memory states, and can
    be duplicated at different nodes, each instance being mapped to different
    memory states.

*)

type mode = [
  | `Tree
  | `Bool_Backward
  | `Bool_Forward
]

module type Cfg =
sig

  (** The memory model used. *)
  module S : Sigma

  (** Program point along a trace. *)
  module Node : sig
    type t
    module Map : Qed.Idxmap.S with type key = t
    module Set : Qed.Idxset.S with type elt = t
    module Hashtbl : Hashtbl.S with type key = t
    val pp: Format.formatter -> t -> unit
    val create: unit -> t
    val equal: t -> t -> bool
  end
  type node = Node.t

  (** fresh node *)
  val node : unit -> node

  (** {2 Relocatable Formulae}
      Can be created once with fresh environment, and used several
      times on different memory states. *)

  (** Relocatable condition *)
  module C :
  sig
    type t

    val equal : t -> t -> bool

    (** Bundle an equation with the sigma sequence that created it. *)
    val create : S.t -> F.pred -> t
    val get : t -> F.pred
    val reads : t -> S.domain
    val relocate : S.t -> t -> t
  end

  (** Relocatable predicate *)
  module P :
  sig
    type t
    val pretty : Format.formatter -> t -> unit

    (** Bundle an equation with the sigma sequence that created it.
        [| create m p |] = [| p |]
    *)
    val create : S.t Node.Map.t -> F.pred -> t
    val get: t -> F.pred
    val reads : t -> S.domain Node.Map.t
    val nodes : t -> Node.Set.t
    val relocate : S.t Node.Map.t -> t -> t
    (** [| relocate m' (create m p) |] = [| p{ } |] *)

    val to_condition: t -> (C.t * Node.t option) option
  end

  (** Relocatable term *)
  module T :
  sig
    type t
    val pretty : Format.formatter -> t -> unit

    (** Bundle a term with the sigma sequence that created it. *)
    val create : S.t Node.Map.t -> F.term -> t
    val get: t -> F.term
    val reads : t -> S.domain Node.Map.t
    val relocate : S.t Node.Map.t -> t -> t
    val init  : Node.Set.t -> (S.t Node.Map.t -> F.term) -> t
    val init' : Node.t -> (S.t -> F.term) -> t
  end


  (** Relocatable effect (a predicate that depend on two states). *)
  module E : sig
    type t
    val pretty: Format.formatter -> t -> unit

    (** Bundle an equation with the sigma sequence that created it *)
    val create : S.t sequence -> F.pred -> t
    val get : t -> F.pred
    val reads : t -> S.domain
    val writes : t -> S.domain
    (** as defined by S.writes *)
    val relocate : S.t sequence -> t -> t
  end

  type cfg (** Structured collection of traces. *)

  val dump_env: name:string -> cfg -> unit
  val output_dot: out_channel -> ?checks:P.t Bag.t -> cfg -> unit

  val nop : cfg
  (** Structurally, [nop] is an empty execution trace.
      Hence, [nop] actually denotes all possible execution traces.
      This is the neutral element of [concat].

      Formally: all interpretations I verify nop: [| nop |]_I
  *)

  val add_tmpnode: node -> cfg
  (** Set a node as temporary. Information about its path predicate or
      sigma can be discarded during compilation *)

  val concat : cfg -> cfg -> cfg
  (** The concatenation is the intersection of all
      possible collection of traces from each cfg.

      [concat] is associative, commutative,
      has [nop] as neutral element.

      Formally: [| concat g1 g2 |]_I iff [| g1 |]_I and [| g2 |]_I
  *)

  val meta : ?stmt:stmt -> ?descr:string -> node -> cfg
  (** Attach meta informations to a node.
      Formally, it is equivalent to [nop]. *)

  val goto : node -> node -> cfg
  (** Represents all execution traces [T] such that, if [T] contains node [a],
      [T] also contains node [b] and memory states at [a] and [b] are equal.

      Formally: [| goto a b |]_I iff (I(a) iff I(b))
  *)

  val branch : node -> C.t -> node -> node -> cfg
  (** Structurally corresponds to an if-then-else control-flow.
      The predicate [P] shall reads only memory state at label [Here].

      Formally: [| branch n P a b |]_I iff (   (I(n) iff (I(a) \/ I(b)))
                                            /\ (I(n) implies (if P(I(n)) then I(a) else I(b)))  )
  *)

  val guard : node -> C.t -> node -> cfg
  (** Structurally corresponds to an assume control-flow.
      The predicate [P] shall reads only memory state at label [Here].

      Formally: [| guard n P a |]_I iff (   (I(n) iff I(a))
                                            /\ (I(n) implies [| P |]_I  ) )
  *)

  val guard' : node -> C.t -> node -> cfg
  (** Same than guard but the condition is negated *)

  val either : node -> node list -> cfg
  (** Structurally corresponds to an arbitrary choice among the different
      possible executions.

      [either] is associative and commutative. [either a []] is
      very special, since it denotes a cfg with {i no} trace. Technically,
      it is equivalent to attaching an [assert \false] annotation to node [a].

      Formally: [| either n [a_1;...;a_n] } |]_I iff ( I(n) iff (I(a_1) \/ ... I(a_n)))
  *)


  val implies : node -> (C.t * node) list -> cfg
  (**
     implies is the dual of either. Instead of being a non-deterministic choice,
     it takes the choices that verify its predicate.

      Formally: [| either n [P_1,a_1;...;P_n,a_n] } |]_I iff ( I(n) iff (I(a_1) \/ ... I(a_n))
                                                              /\  I(n) implies [| P_k |]_I implies I(a_k)
  *)


  val effect : node -> E.t -> node -> cfg
  (** Represents all execution trace [T] such that, if [T] contains node [a],
      then [T] also contains [b] with the given effect on corresponding
      memory states.

      Formally: [| effect a e b |]_I iff (( I(a) iff I(b) ) /\ [| e |]_I )
  *)

  val assume : P.t -> cfg
  (** Represents execution traces [T] such that, if [T] contains
      every node points in the label-map, then the condition holds over the
      corresponding memory states. If the node-map is empty,
      the condition must hold over all possible execution path.

      Formally: [| assume P |]_I iff [| P |]_I
  *)

  val havoc : node -> effects:node sequence -> node -> cfg
  (** Inserts an assigns effect between nodes [a] and [b], correspondings
      to all the written memory chunks accessible in execution paths delimited
      by the [effects] sequence of nodes.

      Formally: [| havoc a s b |]_I is verified if there is no path between s.pre and s.path,
      otherwise if (I(a) iff I(b) and if I(a) is defined then I(a) and I(b) are equal
      for all the chunks that are not in the written domain of an effect that can be found
      between [s.pre] to [s.post].

      Note: the effects are collected in the {i final} control-flow,
      when {!compile} is invoked. The portion of the sub-graph in the sequence
      shall be concatenated to the [cfg] before compiling-it, otherwize it would be
      considered empty and [havoc] would be a nop (no connection between a and b).
  *)

  (** {2 Path-Predicates}

      The compilation of cfg control-flow into path predicate
      is performed by allocating fresh environments with optimized variable
      allocation. Only the relevant path between the nodes
      is extracted. Other paths in the cfg are pruned out.
  *)

  (** Extract the nodes that are between the start node and the final
      nodes and returns how to observe a collection of states indexed
      by nodes. The returned maps gives, for each reachable node, a
      predicate representing paths that reach the node and the memory
      state at this node.

      Nodes absent from the map are unreachable. Whenever possible,
      predicate [F.ptrue] is returned for inconditionally accessible
      nodes.

      ~name: identifier used for debugging

  *)

  val compile : ?name:string -> ?mode:mode -> node -> Node.Set.t -> S.domain Node.Map.t ->
    cfg -> F.pred Node.Map.t * S.t Node.Map.t * Conditions.sequence
end

module Cfg(S:Sigma) : Cfg with module S = S