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
(* SPDX-License-Identifier: AGPL-3.0-or-later *)
(* Copyright © 2021-2024 OCamlPro *)
(* Written by the Owi programmers *)
open Syntax
open Types
module Stack = Stack.Make (Concrete_value)
module M = Map.Make (Int)
type mode =
| Complete
| Sound
let find_functions_with_func_type func_type (acc, i)
(f : (binary func, binary block_type) Runtime.t) =
let (Bt_raw (_, ft)) =
match f with
| Runtime.Local x -> x.type_f
| Runtime.Imported imp -> imp.desc
in
if func_type_eq func_type ft then (i :: acc, i + 1) else (acc, i + 1)
let rec find_children mode tables funcs acc (l : binary instr Annotated.t list)
=
match (l, mode) with
| [], _ -> acc
| { raw = Call (Raw i) | Return_call (Raw i) } :: l, _ ->
find_children mode tables funcs (i :: acc) l
| ( { raw =
( Call_indirect (_, Bt_raw (_, ft))
| Return_call_indirect (_, Bt_raw (_, ft)) )
}
:: l
, Complete ) ->
let acc, _ =
Array.fold_left (find_functions_with_func_type ft) (acc, 0) funcs
in
find_children mode tables funcs acc l
| ( { raw = I32_const x }
:: { raw = Call_indirect (Raw i, _) | Return_call_indirect (Raw i, _) }
:: l
, Sound ) -> (
let t_opt = M.find_opt i tables in
let f =
Option.bind t_opt (fun t ->
match Array.get t (Int32.to_int x) with
| Ok n -> Some (Int32.to_int n)
| _ -> None )
in
match f with
| Some f -> find_children mode tables funcs (f :: acc) l
| None -> find_children mode tables funcs acc l )
| { raw = Block (_, _, exp) | Loop (_, _, exp) } :: l, _ ->
let x = find_children mode tables funcs acc exp.raw in
find_children mode tables funcs x l
| { raw = If_else (_, _, e1, e2) } :: l, _ ->
let x = find_children mode tables funcs acc e1.raw in
let x = find_children mode tables funcs x e2.raw in
find_children mode tables funcs x l
| _ :: l, _ -> find_children mode tables funcs acc l
let build_graph mode tables funcs (g, i) (f : (binary func, 'a) Runtime.t) =
match f with
| Runtime.Local x ->
let l =
List.sort_uniq compare (find_children mode tables funcs [] x.body.raw)
in
((i, x.id, l) :: g, i + 1)
| Runtime.Imported x -> ((i, x.assigned_name, []) :: g, i + 1)
let eval_ibinop stack nn (op : ibinop) =
match nn with
| S32 ->
let (n1, n2), stack = Stack.pop2_i32 stack in
Stack.push_i32 stack
(let open Int32 in
match op with
| Add -> add n1 n2
| Sub -> sub n1 n2
| Mul -> mul n1 n2
| _ -> assert false )
| S64 ->
let (n1, n2), stack = Stack.pop2_i64 stack in
Stack.push_i64 stack
(let open Int64 in
match op with
| Add -> add n1 n2
| Sub -> sub n1 n2
| Mul -> mul n1 n2
| _ -> assert false )
let get_const_global env id =
match M.find_opt id env with Some n -> n | None -> assert false
let eval_const_instr env stack instr =
match instr.Annotated.raw with
| I32_const n -> ok @@ Stack.push_i32 stack n
| I64_const n -> ok @@ Stack.push_i64 stack n
| F32_const f -> ok @@ Stack.push_f32 stack f
| F64_const f -> ok @@ Stack.push_f64 stack f
| V128_const f -> ok @@ Stack.push_v128 stack f
| I_binop (nn, op) -> ok @@ eval_ibinop stack nn op
| Ref_null t -> ok @@ Stack.push stack (Concrete_value.ref_null t)
| Global_get (Raw id) ->
let* g = get_const_global env id in
ok @@ Stack.push_i32 stack g
| Ref_func (Raw id) -> ok @@ Stack.push_i32_of_int stack id
| _ -> assert false
let eval_const env exp =
let* stack =
list_fold_left (eval_const_instr env) Stack.empty exp.Annotated.raw
in
match stack with
| [] -> Error (`Type_mismatch "const expr returning zero values")
| _ :: _ :: _ ->
Error (`Type_mismatch "const expr returning more than one value")
| [ Concrete_value.I32 i ] -> Ok i
| [ _ ] -> Error (`Type_mismatch "expected int32")
let eval_tables tables env =
let t =
List.map
(fun (n, elem) ->
(n, Array.of_list (List.map (eval_const env) elem.Binary.init)) )
tables
in
M.of_list t
let build_env (env, n) (global : (Binary.global, 'a) Runtime.t) =
match global with
| Runtime.Local x -> (
match fst x.Binary.typ with
| Const -> (M.add n (eval_const env x.Binary.init) env, n + 1)
| _ -> (env, n + 1) )
| _ -> (env, n + 1)
let rec find_tables acc (e : binary instr Annotated.t) =
match e.raw with
| Table_set (Raw i) | Table_fill (Raw i) | Table_copy (Raw i, _) -> i :: acc
| Block (_, _, exp) | Loop (_, _, exp) ->
List.fold_left find_tables acc exp.raw
| If_else (_, _, e1, e2) ->
let acc = List.fold_left find_tables acc e1.raw in
List.fold_left find_tables acc e2.raw
| _ -> acc
let find_tables_to_remove export_tables funcs =
List.map (fun (x : Binary.export) -> x.id) export_tables
@ Array.fold_left
(fun acc f ->
match f with
| Runtime.Local x -> List.fold_left find_tables acc x.body.raw
| _ -> acc )
[] funcs
let rec remove_tables (l1 : (int * Binary.elem) list) l2 acc =
match (l1, l2) with
| [], [] -> acc
| [], _ -> acc
| h :: t, [] -> remove_tables t [] (h :: acc)
| (n, e) :: t1, h2 :: t2 ->
if n = h2 then remove_tables t1 t2 acc
else if n < h2 then remove_tables t1 l2 ((n, e) :: acc)
else remove_tables l1 t2 acc
let find_entry_points (m : Binary.Module.t) =
let l = Option.to_list m.start in
List.fold_left (fun acc (x : Binary.export) -> x.id :: acc) l m.exports.func
let find_entries entry_point (m : Binary.Module.t) =
let entries =
Option.bind
(Option.bind entry_point (fun x ->
Array.find_index
(fun f ->
match f with
| Runtime.Local y ->
Option.compare String.compare (Some x) y.id = 0
| _ -> false )
m.func ) )
(fun x -> Some [ x ])
in
Option.value entries ~default:(find_entry_points m)
let build_call_graph call_graph_mode (m : Binary.Module.t) entry_point =
let funcs = m.func in
let tables =
let elems =
List.filter_map
(fun e ->
match e.Binary.mode with
| Elem_active (Some n, _) -> Some (n, e)
| _ -> None )
(Array.to_list m.elem)
in
let t = find_tables_to_remove m.exports.table funcs in
remove_tables
(List.sort_uniq (fun x y -> compare (fst x) (fst y)) elems)
(List.sort_uniq compare t) []
in
let env, _ = Array.fold_left build_env (M.empty, 0) m.global in
let tables = eval_tables tables env in
let l, _ =
Array.fold_left (build_graph call_graph_mode tables funcs) ([], 0) funcs
in
let entries = find_entries entry_point m in
Graph.init l entries
let build_call_graph_from_text_module call_graph_mode modul entry_point =
let m =
Compile.Text.until_validate ~unsafe:false ~rac:false ~srac:false modul
in
match m with
| Ok m -> build_call_graph call_graph_mode m entry_point
| _ -> assert false
let cmd ~call_graph_mode ~source_file ~entry_point =
let* m =
Compile.File.until_validate ~unsafe:false ~rac:false ~srac:false source_file
in
let call_graph = build_call_graph call_graph_mode m entry_point in
let* () =
Bos.OS.File.writef
(Fpath.set_ext ".dot" source_file)
"%a" Graph.pp_dot call_graph
in
Ok ()