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
(* SPDX-License-Identifier: AGPL-3.0-or-later *)
(* Copyright © 2021-2024 OCamlPro *)
(* Written by the Owi programmers *)
(* Multicore is based on several layers of monad transformers. The module as a whole is made to provide a monad to explore in parallel different possibilites, with a notion of priority. *)
(* Add a notion of faillibility to the evaluation. "Transformer without module functor" style. *)
include Symex.Monad
type 'a t = ('a, Bug.t, Prio.metrics, Thread.t) Symex.Monad.t
let state : Thread.t t = state ()
let add_already_checked_condition_to_pc (condition : Symbolic_boolean.t) =
let* state in
let state = Thread.add_already_checked_condition_to_pc state condition in
set_state state
[@@inline]
let get_pc () =
let+ state in
let pc = Symex.Path_condition.to_list state.pc in
List.fold_left Smtml.Expr.Set.union Smtml.Expr.Set.empty pc
let add_breadcrumb crumb = modify_state (fun t -> Thread.add_breadcrumb t crumb)
let add_label label = modify_state (fun t -> Thread.add_label t label)
let open_scope scope = modify_state (fun t -> Thread.open_scope t scope)
let close_scope () = modify_state (fun t -> Thread.close_scope t)
let with_new_invisible_symbol ty f =
let* state in
let n = state.num_symbols in
let+ () = modify_state Thread.incr_num_symbols in
let sym = Fmt.kstr (Smtml.Symbol.make ty) "symbol_invisible_%i" n in
f sym
let with_new_symbol ty f =
let* state in
let n = state.num_symbols in
let sym = Fmt.kstr (Smtml.Symbol.make ty) "symbol_%d" n in
let+ () =
modify_state (fun state ->
let state = Thread.add_symbol state sym in
Thread.incr_num_symbols state )
in
f sym
let check_reachability condition =
let* state in
let pc = Symex.Path_condition.slice_on_new_condition condition state.pc in
let stats = state.bench_stats in
let reachability =
Benchmark.handle_time_span stats.solver_sat_time @@ fun () ->
Solver.check pc condition
in
return reachability
let assume condition =
let* satisfiability = check_reachability condition in
match satisfiability with
| `Sat ->
let* () = add_already_checked_condition_to_pc condition in
return ()
| `Unsat -> prune ()
| `Unknown -> if Solver.was_interrupted () then prune () else assert false
let select_inner ~with_breadcrumbs (condition : Symbolic_boolean.t)
~instr_counter_true ~instr_counter_false =
let condition = Smtml.Typed.simplify condition in
match Smtml.Typed.view condition with
| Val True -> return true
| Val False -> return false
| _ ->
let branch condition final_value priority =
let* () =
if with_breadcrumbs then add_breadcrumb (if final_value then 1 else 0)
else return ()
in
let* () = yield priority in
let* () = assume condition in
let* () = modify_state (Thread.set_priority priority) in
return final_value
in
let* state in
let true_branch =
let prio =
let instr_counter =
match instr_counter_true with
| None -> state.priority.instr_counter
| Some instr_counter -> instr_counter
in
Prio.v ~instr_counter ~distance_to_unreachable:None ~depth:state.depth
in
branch condition true prio
in
let false_branch =
let prio =
let instr_counter =
match instr_counter_false with
| None -> state.priority.instr_counter
| Some instr_counter -> instr_counter
in
Prio.v ~instr_counter ~distance_to_unreachable:None ~depth:state.depth
in
branch (Symbolic_boolean.not condition) false prio
in
Thread.incr_path_count state;
choose true_branch false_branch
[@@inline]
let select (cond : Symbolic_boolean.t) ~instr_counter_true ~instr_counter_false
=
select_inner cond ~instr_counter_true ~instr_counter_false
~with_breadcrumbs:true
[@@inline]
let get_model_or_prune symbol =
let* state in
let sat_model =
let set = state.pc |> Symex.Path_condition.slice_on_symbol symbol in
let stats = state.bench_stats in
let symbol_scopes = Symbol_scope.of_symbol symbol in
Benchmark.handle_time_span stats.solver_intermediate_model_time (fun () ->
Solver.model_of_set ~symbol_scopes ~set )
in
match sat_model with
| `Unsat -> prune ()
| `Model model -> begin
match Smtml.Model.evaluate model symbol with
| Some v -> return v
| None ->
(* the model exists so the symbol should evaluate *)
assert false
end
| `Unknown -> if Solver.was_interrupted () then prune () else assert false
let select_i32 (e : Symbolic_i32.t) : int32 t =
let e = Smtml.Typed.simplify e in
match Smtml.Typed.view e with
| Val (Bitv bv) when Smtml.Bitvector.numbits bv <= 32 ->
return (Smtml.Bitvector.to_int32 bv)
| _ ->
let* state in
let* assign, symbol =
match Smtml.Typed.view e with
| Symbol symbol -> return (None, symbol)
| _ ->
let num_symbols = state.num_symbols in
let+ () = modify_state Thread.incr_num_symbols in
let name = Fmt.str "choice_i32_%i" num_symbols in
let sym = Smtml.Symbol.make Smtml.Typed.Types.(to_ty bitv32) name in
let assign = Smtml.Typed.Bitv32.(eq (symbol sym) e) in
(Some assign, sym)
in
let* () =
match assign with
| Some assign ->
(* it must be SAT because we only introduced a constraint: new_symbol = e *)
add_already_checked_condition_to_pc assign
| None -> return ()
in
let rec generator () =
let* possible_value = get_model_or_prune symbol in
let i =
match possible_value with
| Smtml.Value.Bitv bv ->
assert (Smtml.Bitvector.numbits bv <= 32);
Smtml.Bitvector.to_int32 bv
| _ ->
(* it should be a value! *)
assert false
in
(* TODO: everything which follows look like select_inner and could probably be simplified by calling it directly! *)
let this_value_cond =
Symbolic_i32.eq_concrete (Smtml.Typed.Bitv32.symbol symbol) i
in
let this_val_branch =
let* () = add_breadcrumb (Int32.to_int i) in
let* () = add_already_checked_condition_to_pc this_value_cond in
return i
in
let not_this_val_branch =
let not_this_value_cond = Symbolic_boolean.not this_value_cond in
(* TODO: this is annoying as the next call to get_model_or_prune is also going to check for satisfiability which is useless! it can probably be simplified.
I'm leaving it for now to be sure this is correct. It may actually not be required but better safe than sorry! *)
let* () = assume not_this_value_cond in
generator ()
in
Thread.incr_path_count state;
(* TODO: better prio here? *)
let prio =
Prio.v ~instr_counter:state.priority.instr_counter
~distance_to_unreachable:None ~depth:state.depth
in
fork ~parent:this_val_branch ~child:(prio, not_this_val_branch)
in
generator ()
let bug kind =
let* state in
let* model =
let stats = state.bench_stats in
Benchmark.handle_time_span stats.solver_final_model_time @@ fun () ->
let path_condition = state.pc in
match Solver.model_of_path_condition ~path_condition with
| Some model -> return model
| None -> if Solver.was_interrupted () then prune () else assert false
in
fail { Bug.kind; model; state }
let trap t = bug (`Trap t)
let assertion (c : Symbolic_boolean.t) =
(* TODO: better prio here ? *)
let* assertion_true =
select_inner c ~with_breadcrumbs:false ~instr_counter_true:None
~instr_counter_false:None
in
if assertion_true then return () else bug (`Assertion c)
let ite (c : Symbolic_boolean.t) ~(if_true : Symbolic_value.t)
~(if_false : Symbolic_value.t) : Symbolic_value.t t =
match (if_true, if_false) with
| I32 if_true, I32 if_false ->
let res = Symbolic_boolean.ite c if_true if_false in
return (Symbolic_value.I32 res)
| I64 if_true, I64 if_false ->
let res = Symbolic_boolean.ite c if_true if_false in
return (Symbolic_value.I64 res)
| F32 if_true, F32 if_false ->
return (Symbolic_value.F32 (Symbolic_boolean.ite c if_true if_false))
| F64 if_true, F64 if_false ->
return (Symbolic_value.F64 (Symbolic_boolean.ite c if_true if_false))
| Ref _, Ref _ ->
(* TODO: better prio here *)
let+ b = select c ~instr_counter_true:None ~instr_counter_false:None in
if b then if_true else if_false
| _, _ -> assert false