package codex

  1. Overview
  2. Docs
The Codex library for building static analysers based on abstract interpretation

Install

dune-project
 Dependency

Authors

Maintainers

Sources

1.0-rc4.tar.gz
md5=bc7266a140c6886add673ede90e335d3
sha512=8da42c0ff2c1098c5f9cb2b5b43b306faf7ac93b8f5ae00c176918cee761f249ff45b29309f31a05bbcf6312304f86a0d5a000eb3f1094d3d3c2b9b4c7f5c386

doc/src/codex.fixpoint/reduce.ml.html

Source file reduce.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
(**************************************************************************)
(*  This file is part of the Codex semantics library.                     *)
(*                                                                        *)
(*  Copyright (C) 2013-2025                                               *)
(*    CEA (Commissariat à l'énergie atomique et aux énergies              *)
(*         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 LICENSE).                      *)
(*                                                                        *)
(**************************************************************************)

(** Interface for directed graphs with labeled vertices and edges.  The
 * reduction algorithm reduces graphs into a regex over the alphabet of edge
 * labels. The input graph must be without self-loops, i.e. edges from one node
 * to itself.
 * NOTE: This algorithm's complexity and correctness has only been established
 * if there is at most one edge between two nodes (as should be the case in a
 * CFG)! *)
module type GRAPHI = sig
  type t

  module V : sig
    type t
      (** Vertex labels *)

    val equal : t -> t -> bool
    val hash : t -> int
    val pretty : Format.formatter -> t -> unit
  end

  module E : sig
    type t
    val src : t -> V.t
    val dst : t -> V.t
  end

  (** Fold over the incoming edges of a node *)
  val fold_pred_e : (E.t -> 'a -> 'a) -> t -> V.t -> 'a -> 'a
end

module Make (G : GRAPHI) (R : Regex.S with type letter = G.E.t) = struct

  type state = (G.V.t, R.t) Hashtbl.t

  module Wto_utils = Wto_utils.Make(G.V)
  open Wto

  module NodeMap = Hashtbl.Make(G.V)

  (* To the w.t.o., we add a map from every node to the set of its heads, to
   * look up efficiently whether a node is in a given component. The set of
   * heads is represented as a list, because in practice we expect to be very
   * small so lists will probably be more efficient. *)
  type decorated_wto =
    { partition : G.V.t partition; heads : (G.V.t list) NodeMap.t }

  let decorate wto =
    let table = NodeMap.create 17 in
    let rec loop heads = function
      | [] -> ()
      | Node n :: rest -> NodeMap.add table n heads; loop heads rest
      | Component (h,body) :: rest ->
          let heads' = h :: heads in
          NodeMap.add table h heads';
          loop heads' body;
          loop heads rest
    in
    loop [] wto;
    { partition = wto; heads = table }

  let in_component {heads;_} head node =
    try List.mem head @@ NodeMap.find heads node
    with Not_found -> raise @@ Invalid_argument "in_component"

  (** [udpate state loops is_head add_epsilon graph predicate node] computes
      the new path expression leading to [node] in [graph] and stores it in
      [state] (replacing whatever value was there before). If [add_epsilon] is
      true, then [R.epsilon] is joined to the result. To that end, it needs the
      loop map [loops], and [is_head] that tells whether [node] is a head. The
      path expression is computed by considering the edges to all predecessors
      of [node] matching [predicate]. If [node] has no such predecessors, the
      expression will be [R.empty], unless [add_epsilon] is true (useful if
      [node] is the entry node).
   *)
  let update state loops is_head add_espilon graph predicate node =
    (* Codex_log.feedback "update %a" G.V.pretty node; *)
    let initial = if add_espilon then R.epsilon else R.empty in
    let new_state = G.fold_pred_e
        (fun e acc ->
           (* Note: faults on nodes that are not reachable. *)
           (* Codex_log.feedback "pred is %a" G.V.pretty (G.E.src e); *)
        if predicate (G.E.src e)
        then R.join acc @@ R.append (Hashtbl.find state (G.E.src e)) e
        else acc
      )
      graph node initial
    in
    Hashtbl.replace state node new_state;
    if is_head then
      try
        let loop = Hashtbl.find loops node in
        if not (R.equal loop R.epsilon) then
          Hashtbl.replace state node @@
            R.append_star (Hashtbl.find state node) loop
      with Not_found -> assert false

  (** [stabilize state loops graph add_epsilon head body] propagates inside the
      component ([head] [body]) the paths that enter the component, but not
      through its head. Once [stabilize] terminates, the state of every node in
      the component is guaranteed to express all possible paths from the entry
      node to that node, provided that the arguments verify the following
      pre-conditions:
        - all predecessors of the component have their states matching the
          possible paths to them;
        - [loops] contains a correct self-loop for [head].
    *)
  let rec stabilize state loops graph predicate add_espilon head body =
    Hashtbl.replace state head R.empty;
    let rec aux = function
      | [] -> ()
      | Node n :: rest ->
          update state loops false false graph predicate n;
          aux rest
      | Component (h, l) :: rest ->
          stabilize state loops graph predicate add_espilon h l;
          aux rest
    in
    aux body;
    (*Format.printf "value in %a before update: %a\n" G.V.pretty head R.pretty (Hashtbl.find state head);*)
    update state loops true add_espilon graph predicate head;
    (*Format.printf "value in %a after update: %a\n" G.V.pretty head R.pretty (Hashtbl.find state head);*)
    Wto_utils.iter
      (fun n is_head -> update state loops is_head false graph predicate n)
      body

  (** [propagate state loops graph predicate wto] takes the first node in the
      w.t.o. as the entry point and propagates and stabilizes path expressions.
      [w] should be a w.t.o. of [g]. [state] and [loops] should be empty.
      [propagate] guarantees, if [predicate] is [fun _ -> true] that the state
      of each node expresses all paths from the first node of [wto] to that
      node. [predicate] is passed to [update]. *)
  let rec propagate loops graph predicate wto =
    let state = Hashtbl.create 17 in
    (* Loop function *)
    let rec aux = function
    | [] -> ()
    | Node n :: rest ->
        update state loops false false graph predicate n;
        aux rest
    | Component (h, l) :: rest ->
        compute_self_loop loops graph wto h l;
        stabilize state loops graph predicate false h l;
        aux rest
    in
    (match wto.partition with
    | [] -> raise (Invalid_argument "propagate")
    | Node n :: rest ->
        (* [n] is the entry point, set its state to "empty regex" *)
        Hashtbl.replace state n R.epsilon;
        aux rest
    | Component (h, l) :: rest ->
        compute_self_loop loops graph wto h l;
        stabilize state loops graph predicate true h l;
        aux rest
    );
    state

  (** Computes the self-loop of a component and stores it. *)
  and compute_self_loop loops graph wto head body =
    (* Propagate on the nodes in the component *)
    let sub_state = propagate loops graph
      (in_component wto head) {wto with partition = Node head :: body} in
    (* Updating the head inside the subgraph gives us all paths from [h] to
     * [h] in that subgraph. Tell [update] that [h] is not a head so that it
     * does not try to access the self-loop (since it is not computed yet). *)
    update sub_state loops false false graph (in_component wto head) head;
    let loop = Hashtbl.find sub_state head in
    if not (R.equal loop R.empty) then
      Hashtbl.replace loops head loop

  let compute_exprs graph wto =
    let loops = Hashtbl.create 17 in
    let wto = decorate wto in
    propagate loops graph (fun _ -> true) wto

end