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/wto.ml.html

Source file wto.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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
(**************************************************************************)
(*  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).                      *)
(*                                                                        *)
(**************************************************************************)

(** Each component of the graph is either an individual node of the graph
    (without) self loop, or a strongly connected component where a node is
    designed as the head of the component and the remaining nodes are given
    by a list of components topologically ordered. *)
type 'n component =
  | Component of 'n * 'n partition
    (** A strongly connected component, described by its head node and the
        remaining sub-components topologically ordered *)
  | Node of 'n
    (** A single node without self loop *)

(** A list of strongly connected components, sorted topologically *)
and 'n partition = 'n component list

let fold_heads f acc l =
  let rec partition acc l =
    List.fold_left component acc l
  and component acc = function
    | Node _ -> acc
    | Component (h,l) ->
      partition (f acc h) l
  in
  partition acc l


let flatten wto =
  let rec f acc = function
    | [] -> acc
    | Node v :: l -> f (v :: acc) l
    | Component (v,w) :: l -> f (f (v :: acc) w) l
  in
  List.rev (f [] wto)

(* Bourdoncle's WTO algorithm builds on Tarjan's SCC algorithm. In Tarjan:

   - We visit every node once, starting from root, by following the
     successors; this creates a spanning tree of the graph. SCCs are
     subtrees of this spanning tree, whose root is the head of the SCC
     (although in non-natural SCCs, it is possible to enter into a SCC
     without going through the head).

   - This spanning tree is obtained using DFS. What DFS guarantees is
     that there is no path from a child c of a node n to other
     children of n, provided that there is no path from c to an
     ancestor of n. Thus when we visit other children of n, we know
     that they are no path to them from the descendants of c.

   - We assign consecutive numbers to each node in the order in which
     they have been visited. As the iteration is depth-first search,
     this gives a depth-first numbering (DFN).

   - Each time we visit a node n, we push it on a stack. After the
     visit, n is popped, unless a path exists from n to an element
     earlier on the stack. So the stack contains elements currently
     visited or that belongs to a non-trivial scc. Moreover, they 
     are in topological order.

   About the proof of Tarjan:
   http://ls2-www.cs.uni-dortmund.de/~wegener/papers/connected.pdf
*)

module Make(N:sig
    type t (* = int *)
    val equal: t -> t -> bool
    val hash: t -> int
    val pretty: Format.formatter -> t -> unit
    (* val succ: t -> t list *)
  end) = struct

  let rec equal_component (x:N.t component) (y:N.t component) =
    match x,y with
    | Node x, Node y -> N.equal x y
    | Component (x,cx), Component (y,cy) -> N.equal x y && equal_partition cx cy
    | _ -> false

  and equal_partition x y =
    (try List.for_all2 equal_component x y with Invalid_argument _ -> false)


  let rec pretty_partition fmt part =
    List.iter (fun x -> Format.fprintf fmt "@ %a" pretty_component x) part
  and pretty_component fmt : N.t component -> unit = function
    | Node n -> N.pretty fmt n
    | Component(head,part) ->
      Format.fprintf fmt "@[<hov 1>(%a%a)@]"
        N.pretty head pretty_partition part

  module DFN = Hashtbl.Make(N);;

  module Bourdoncle_Original = struct
    type state =
      { dfn: int DFN.t; (* Mapping from nodes to its dfn, depth-first
                           numbering.  Note that we replaced the DFN=0
                           test by presence in the Hashtable. *)
        mutable num: int;            (* Number of visited nodes. *)
        succs: N.t -> (N.t list);    (* Successors transition. *)
        stack: N.t Stack.t
      }

    (* Visit [vertex], and all the vertices reachable from [vertex]
       which have not been explored yet (this is a depth-first search).
       Also gives [partition], which is the partition built so far

       Returns a pair (lowlink,partition) where
       - [lowlink] is the deepest node in the stack, that is the
         successor of an explored node (i.e. a node which is below than
         [vertex] in the spanning tree built by the DFS);
       - [partition] is returned completed.

       Except for the call to component, and changing the DFN of vertex
       to max_int, this function exactly performs Tarjan's SCC
       algorithm. *)
    let rec visit state vertex partition =
      Stack.push vertex state.stack;
      let n = state.num + 1 in
      state.num <- n;
      DFN.replace state.dfn vertex n;
      let succs = state.succs vertex in
      let (head,loop,partition) = List.fold_left (fun (head,loop,partition) succ ->
          let (min,partition) =
            (* If already visited. *)
            if DFN.mem state.dfn succ
            (* Is another branch, which can have been visited before or after [vertex]. *)
            then (succ,partition)
            else visit state succ partition in
          (* OPTIM: On doit pouvoir sauvegarder le dfn de head et min pour gagner des lookups. *)
          assert (DFN.mem state.dfn min);
          assert (DFN.mem state.dfn head);
          (* If an element below in the spanning tree links to deepest
             in the stack than [vertex]. *)
          if (DFN.find state.dfn min) <= (DFN.find state.dfn head)
          then (min,true,partition)
          else (head,loop,partition)
        ) (vertex,false,partition) succs in
      (* If no element below in the spanning tree link to deepest than [vertex]. *)    
      if N.equal head vertex then begin
        (* Makes edges to [vertex] invisible in the second visit. *)
        DFN.replace state.dfn vertex max_int;
        let element = Stack.pop state.stack in
        (* If an element below in the spanning tree links to [vertex],
             which is the head of the SCC.  *)
        if loop then begin
          let rec loop element =
            if not @@ N.equal element vertex then begin
              (* Remove from DFN for a second visit. *)
              DFN.remove state.dfn element;
              loop (Stack.pop state.stack)
            end
            else ()
          in loop element;
          (head,component state vertex::partition)
        end
        else (head,(Node vertex)::partition)
      end
      (* [vertex] is part of a strongly connected component, and is not
         the root.  Do not update partition; the vertex will be added in
         the next call to [component]. *)
      else (head,partition)

    (* We found a SCC; revisit it with the [edge] to its head made
       invisible. *)
    and component state vertex =
      let succs = state.succs vertex in
      let partition = List.fold_left (fun partition succ ->
          (* Select only the branches of the spanning tree which are in a SCC with [vertex];
             and which have been removed from the DFN by [visit]. *)
          if DFN.mem state.dfn succ
          then partition
          else let (_,part) =  visit state succ partition in part
        ) [] succs
      in
      Component(vertex,partition)
    ;;


    let _partition ~init ~succs =
      let state = {dfn = DFN.create 17; num = 0; succs;
                   stack = Stack.create () } in
      snd @@ visit state init []
    ;;
  end
  
  module With_Preference = struct

    (* This implements Francois Bobot's improvements (preference on
       the choice of a head, maybe others, but possibly the WTO
       construction is slower. *)

    type level = int

    (** Status of a visited vertex during the algorithm. *)
    type status =
      | Invisible (** The vertex have already been added into the partition and
                      is hidden until the end of the search. *)
      | Parent of level (** The vertex have been visited and given a [level]. For
                            the algorithm, this implies that there is a path
                            between this vertex and the current vertex. *)

    (** Result of one [visit]  *)
    type loop =
      | NoLoop (** The vertex is not in a loop *)
      | Loop of N.t * level (** The vertex is inside at least one loop, and
                                level is the smallest level of all these loops *)

    let min_loop x y =
      match x, y with
      | NoLoop, z | z, NoLoop -> z
      | Loop(_,xi), Loop(_,yi) ->
        if xi <= yi then x else y

    type state =
      { dfn: status DFN.t; (* Mapping from nodes to its dfn, depth-first
                              numbering.  Note that we replaced the DFN=0
                              test by presence in the Hashtable. *)
        mutable num: level;          (* Number of visited nodes. *)
        succs: N.t -> (N.t list);    (* Successors transition. *)
        stack: N.t Stack.t
      }

    (** Visit [vertex], and all the vertices reachable from [vertex]
        which have not been explored yet (this is a depth-first search).
        Also gives [partition], which is the partition built so far

        Returns a pair (loop,partition) where
        - [loop] tells whether we are in a loop or not and gives the vertex of
          this loop with the lowest level. This vertex is also the deepest in the
          stack and the neareast vertex from the root that is below [vertex] in
          the spanning tree built by the DFS);
        - [partition] is returned completed. *)
    let rec visit ~pref state vertex partition =
      match DFN.find state.dfn vertex with
      (* The vertex is already in the partition *)
      | Invisible -> NoLoop, partition (* skip it *)
      (* The vertex have been visited but is not yet in the partition *)
      | Parent i -> Loop (vertex,i), partition (* we are in a loop *)
      (* The vertex have not been visited yet *)
      | exception Not_found ->
        (* Put the current vertex into the stack *)
        Stack.push vertex state.stack;
        (* Number it and mark it as visited *)
        let n = state.num + 1 in
        state.num <- n;
        DFN.replace state.dfn vertex (Parent n);
        (* Visit all its successors *)
        let succs = state.succs vertex in
        let (loop,partition) = List.fold_left (fun (loop,partition) succ ->
            let (loop',partition) = visit ~pref state succ partition in
            let loop = min_loop loop loop' in
            (loop,partition)
          ) (NoLoop,partition) succs
        in
        match loop with
        (* We are not in a loop. Add the vertex to the partition *)
        | NoLoop ->
          let _ = Stack.pop state.stack in
          DFN.replace state.dfn vertex Invisible;
          (NoLoop,Node(vertex)::partition)
        (* We are in a loop and the current vertex is the head of this loop *)
        | Loop(head,_) when N.equal head vertex ->
          (* Unmark all vertices in the loop, and, if pref is given, try to
             return a better head *)
          let rec reset_SCC best_head =
            (* pop until vertex *)
            let element = Stack.pop state.stack in
            DFN.remove state.dfn element;
            if not (N.equal element vertex) then begin
              let best_head = match pref with
                (* the strict is important because we are conservative *)
                | Some cmp when cmp best_head element < 0 -> element
                | _ -> best_head
              in
              reset_SCC best_head
            end
            else
              best_head
          in
          let best_head = reset_SCC vertex in
          let vertex, succs =
            if N.equal best_head vertex
            then vertex,succs
            else best_head, state.succs best_head
          in
          (* Makes [vertex] invisible in the subsequents visits. *)
          DFN.replace state.dfn vertex Invisible;
          (* Restart the component analysis *)
          let component = List.fold_left
              (fun component succ ->
                 let (loop,component) = visit ~pref state succ component in
                 (* Since we reset the component we should have no loop *)
                 assert (loop = NoLoop);
                 component
              ) [] succs
          in
          (NoLoop,Component(vertex,component)::partition)
        | _ ->
          (* [vertex] is part of a strongly connected component but is not the
             head. Do not update partition; the vertex will
             be added during the second visit of the SCC. *)
          (loop,partition)

    type pref = N.t -> N.t -> int

    let partition ~pref ~init ~succs =
      let state = {dfn = DFN.create 17; num = 0; succs;
                   stack = Stack.create () } in
      let loop,component = visit ~pref:(Some pref) state init [] in
      assert (loop = NoLoop);
      component

  end

  include With_Preference
  
end