Skip to content

Commit 97cc1a3

Browse files
authored
extends the knowledge monad interface (#1360)
This is a quality of life PR (an extract from another PR that I am currently working on) that adds a few useful primitives to the knowledge base monad interface. The most important addition is the new operator `require` that works like `collect` but raises the empty value conflict if the returned value is empty. This function is inteded to be used in the context of promises and proposals that now catch this exception and evaluate the whole scoped expression to the empty value of the computed domain. This vastly simplify the implementation logic of promises for domains that do not use `None` as the empty value. To make things work even more smoothly we extend the KB.Syntax module with a few new operators. First of all, we include the `Monad.Let` directly into the `KB.Syntax` so that you don't need to open `KB.Let` anymore. Next, we introduce `let*?` and `let+?`, which let-binding couterparts of the already existing `>>=?` and `>>|?` infix operators. Next, we add `-->?` to complement `-->`, it collects an optional domain value and fails if it is `None`. Finally, we add indexing operators for accessing fields of KB values. The also come with the empty-checking variants. The PR also applies the new operators to the existing code for the demonstration purposes (and to show that it works). I also believe that the rewritten code is more reable, even despite that I still prefer the infix syntax to the let-binding operators.
1 parent ea2f03d commit 97cc1a3

File tree

7 files changed

+205
-43
lines changed

7 files changed

+205
-43
lines changed

lib/bap_primus/bap_primus_lisp_semantics.ml

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -685,17 +685,14 @@ let provide_semantics ?(stdout=Format.std_formatter) () =
685685
provide Theory.Semantics.slot |>
686686
comment "reifies Primus Lisp definitions"
687687
end);
688-
let (>>=?) x f = x >>= function
689-
| None -> !!Insn.empty
690-
| Some x -> f x in
691688
let require p k = if p then k () else !!Insn.empty in
692689
KB.promise Theory.Semantics.slot @@ fun obj ->
693-
KB.collect Theory.Label.unit obj >>=? fun unit ->
694-
KB.collect Property.name obj >>=? fun name ->
695-
obtain_typed_program unit >>= fun prog ->
690+
let* unit = obj-->?Theory.Label.unit in
691+
let* name = obj-->?Property.name in
692+
let* prog = obtain_typed_program unit in
696693
require (Set.mem prog.names name) @@ fun () ->
697-
KB.collect Property.args obj >>= fun args ->
698-
KB.collect Theory.Unit.target unit >>= fun target ->
694+
let* args = obj-->Property.args in
695+
let* target = KB.collect Theory.Unit.target unit in
699696
let bits = Theory.Target.bits target in
700697
KB.Context.set State.var State.{
701698
binds = Map.empty (module Theory.Var.Top);
@@ -706,7 +703,7 @@ let provide_semantics ?(stdout=Format.std_formatter) () =
706703
} >>= fun () ->
707704
let* (module Core) = Theory.current in
708705
let open Prelude(Core) in
709-
reify stdout prog obj target name args >>= fun res ->
706+
let* res = reify stdout prog obj target name args in
710707
KB.collect Disasm_expert.Basic.Insn.slot obj >>| function
711708
| Some basic when Insn.(res <> empty) ->
712709
Insn.with_basic res basic

lib/knowledge/bap_knowledge.ml

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2442,7 +2442,14 @@ module Knowledge = struct
24422442

24432443
let pids = ref Pid.zero
24442444

2445-
let register_promise (s : _ slot) run =
2445+
type conflict += Empty : ('a,'b) slot -> conflict
2446+
2447+
let with_empty ~missing scope =
2448+
Knowledge.catch (scope ())
2449+
(function Empty _ -> Knowledge.return missing
2450+
| other -> Knowledge.fail other)
2451+
2452+
let register_promise (type a b)(s : (a,b) slot) run =
24462453
Pid.incr pids;
24472454
let pid = !pids in
24482455
Hashtbl.add_exn s.promises pid {run; pid};
@@ -2451,9 +2458,14 @@ module Knowledge = struct
24512458
let remove_promise (s : _ slot) pid =
24522459
Hashtbl.remove s.promises pid
24532460

2461+
let wrap (s : _ slot) get obj =
2462+
let missing = Domain.empty s.dom in
2463+
with_empty ~missing @@ fun () ->
2464+
get obj
2465+
24542466
let promising s ~promise:get scoped =
24552467
let pid = register_promise s @@ fun obj ->
2456-
get obj >>= fun x ->
2468+
wrap s get obj >>= fun x ->
24572469
if Domain.is_empty s.dom x
24582470
then Knowledge.return ()
24592471
else provide s obj x in
@@ -2464,7 +2476,7 @@ module Knowledge = struct
24642476
let promise s get =
24652477
ignore @@
24662478
register_promise s @@ fun obj ->
2467-
get obj >>= fun x ->
2479+
wrap s get obj >>= fun x ->
24682480
if Domain.is_empty s.dom x
24692481
then Knowledge.return ()
24702482
else provide s obj x
@@ -2576,6 +2588,7 @@ module Knowledge = struct
25762588
| GT | NC -> collect_inner slot obj promises
25772589

25782590

2591+
25792592
let collect : type a p. (a,p) slot -> a obj -> p Knowledge.t =
25802593
fun slot id ->
25812594
if Object.is_null id
@@ -2592,6 +2605,13 @@ module Knowledge = struct
25922605
leave_slot slot id >>= fun () ->
25932606
current slot id
25942607

2608+
2609+
let require (slot : _ slot) obj =
2610+
collect slot obj >>= fun x ->
2611+
if (Domain.is_empty slot.dom x)
2612+
then Knowledge.fail (Empty slot)
2613+
else !!x
2614+
25952615
let resolve slot obj =
25962616
collect slot obj >>| Opinions.choice
25972617

@@ -2792,10 +2812,17 @@ module Knowledge = struct
27922812

27932813
module Syntax = struct
27942814
include Knowledge.Syntax
2815+
include Knowledge.Let
2816+
27952817
let (-->) x p = collect p x
27962818
let (<--) p f = promise p f
27972819
let (//) c s = Object.read c s
27982820

2821+
let (-->?) x p =
2822+
collect p x >>= function
2823+
| None -> Knowledge.fail (Empty p)
2824+
| Some x -> !!x
2825+
27992826
let (>>=?) x f =
28002827
x >>= function
28012828
| None -> Knowledge.return None
@@ -2805,6 +2832,23 @@ module Knowledge = struct
28052832
x >>| function
28062833
| None -> None
28072834
| Some x -> f x
2835+
2836+
let (let*?) = (>>=?)
2837+
let (let+?) = (>>|?)
2838+
2839+
2840+
let (.$[]) v s = Value.get s v
2841+
let (.$[]<-) v s x = Value.put s v x
2842+
2843+
let (.?[]) v s = match v.$[s] with
2844+
| Some v -> !!v
2845+
| None -> Knowledge.fail (Empty s)
2846+
2847+
let (.![]) v s =
2848+
let r = v.$[s] in
2849+
if Domain.is_empty (Slot.domain s) r
2850+
then Knowledge.fail (Empty s)
2851+
else !!r
28082852
end
28092853

28102854
module type S = sig

lib/knowledge/bap_knowledge.mli

Lines changed: 134 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,16 @@ module Knowledge : sig
164164
value of the property domain is returned as the result. *)
165165
val collect : ('a,'p) slot -> 'a obj -> 'p t
166166

167+
(** [require p x] collects the property [p] and fails if it is empty.
168+
169+
When [require p x] fails in the scope of a {!promise},
170+
{!proposal}, or in the scope of [with_empty], then the scoped
171+
computation immediately returns the empty value.
172+
173+
@since 2.4.0
174+
*)
175+
val require : ('a,'p) slot -> 'a obj -> 'p t
176+
167177

168178
(** [resolve p x] resolves the multi-opinion property [p]
169179
@@ -186,6 +196,7 @@ module Knowledge : sig
186196
then [provide p x v] diverges into a conflict.
187197
*)
188198
val provide : ('a,'p) slot -> 'a obj -> 'p -> unit t
199+
189200
(** [suggest a p x v] suggests [v] as the value for the property [p].
190201
191202
The same as [provide] except the provided value is predicated by
@@ -195,26 +206,35 @@ module Knowledge : sig
195206

196207
(** [promise p f] promises to compute the property [p].
197208
198-
If no knowledge exists about the property [p] of
199-
an object [x], then [f x] is invoked to provide an
200-
initial value.
209+
If the property [p] of [x] is not provided, then [f x] is
210+
invoked to provide the initial value, when [p] is collected.
201211
202212
If there are more than one promises, then they all must
203213
provide a consistent answer. The function [f] may refer
204214
to the property [p] directly or indirectly. In that case
205215
the least fixed point solution of all functions [g] involved
206216
in the property computation is computed.
217+
218+
@since 2.4.0 if [require] is called in the scope of the promise
219+
and fails, the the whole promise immediately returns the empty
220+
value of the property domain, i.e., [f] is wrapped into
221+
[with_missing].
207222
*)
208223
val promise : ('a,'p) slot -> ('a obj -> 'p t) -> unit
209224

210-
211225
(** [promising p ~promise f] evaluates [f ()] under [promise] and
212226
retracts it after [f] is evaluated.
213227
214228
The information provided by [promise] is only available during
215229
evaluation of [f ()].
216230
217231
@since 2.2.0
232+
233+
234+
@since 2.4.0 if [require] is called in the scope of the promise
235+
and fails, the the whole promise immediately returns the empty
236+
value of the property domain, i.e., [promise] (not [f]) wrapped
237+
into [with_missing].
218238
*)
219239
val promising : ('a,'p) slot -> promise:('a obj -> 'p t) ->
220240
(unit -> 's t) -> 's t
@@ -223,6 +243,12 @@ module Knowledge : sig
223243
224244
The same as [promise] except that it promises a value for
225245
an opinion-based property.
246+
247+
@since 2.4.0 if [require] is called in the scope of the promise
248+
and fails, the the whole promise immediately returns the empty
249+
value of the property domain, i.e., [f] is wrapped into
250+
[with_missing].
251+
226252
*)
227253
val propose : agent -> ('a, 'p opinions) slot -> ('a obj -> 'p t) -> unit
228254

@@ -233,10 +259,25 @@ module Knowledge : sig
233259
value for an opinion-based property.
234260
235261
@since 2.2.0
262+
263+
@since 2.4.0 if [require] are called in the scope of the proposal
264+
and fails, the the whole proposal immediately returns the empty
265+
value of the property domain, i.e., [propose] (not [f]) wrapped
266+
into [with_missing].
267+
236268
*)
237269
val proposing : agent -> ('a, 'p opinions) slot ->
238270
propose:('a obj -> 'p t) -> (unit -> 's t) -> 's t
239271

272+
273+
(** [with_empty ~missing f x] evaluates [f ()] and if it fails on an empty
274+
immediately evaluates to [return missing].
275+
276+
@since 2.4.0
277+
*)
278+
val with_empty : missing:'r -> (unit -> 'r knowledge) -> 'r knowledge
279+
280+
240281
(** state with no knowledge *)
241282
val empty : state
242283

@@ -333,10 +374,62 @@ module Knowledge : sig
333374
include Monad.Syntax.S with type 'a t := 'a t
334375

335376

336-
(** [x-->p] is [collect p x] *)
377+
(** the monadic let-binding operators.
378+
379+
Brings [let*] for [bind] and [let+] for [map] to the scope
380+
of the [Syntax] module.
381+
382+
@since 2.4.0 (before that an explicit [open Knowledge.Let] was
383+
required. *)
384+
include Monad.Syntax.Let.S with type 'a t := 'a t
385+
386+
387+
(** [let*? v = x in f] evaluates to [f y] if [v] is [Some r].
388+
389+
Otherwise evaluates to [return None].
390+
391+
This let-binding operator is synonumous to [>>=?]
392+
393+
@since 2.4.0
394+
*)
395+
val (let*?) : 'a option t -> ('a -> 'b option t) -> 'b option t
396+
397+
(** [let+? v = x in f] evaluates to [!!(f y)] if [v] is [Some r].
398+
399+
Otherwise evaluates to [return None].
400+
401+
This let-binding operator is synonumous to [>>|?]
402+
403+
@since 2.4.0
404+
*)
405+
val (let+?) : 'a option t -> ('a -> 'b option) -> 'b option t
406+
407+
(** [x-->p] is [collect p x].
408+
409+
Example,
410+
{[
411+
let* addr = label-->address in
412+
...
413+
]}
414+
*)
337415
val (-->) : 'a obj -> ('a,'p) slot -> 'p t
338416

339417

418+
(** [x-->?p] returns property [p] if it is not empty.
419+
420+
Otherwise, if [x-->p] evaluates to empty or to [None]
421+
fails with the empty value conflict.
422+
423+
Example,
424+
{[
425+
let* addr = label-->?address in
426+
...
427+
]}
428+
429+
See also {!with_empty}. *)
430+
val (-->?) : 'a obj -> ('a, 'p option) slot -> 'p t
431+
432+
340433
(** [p <-- f] is [promise p f] *)
341434
val (<--) : ('a,'p) slot -> ('a obj -> 'p t) -> unit
342435

@@ -351,6 +444,42 @@ module Knowledge : sig
351444

352445
(** [x >>|? f] evaluates to [f y] if [x] evaluates to [Some y]. *)
353446
val (>>|?) : 'a option t -> ('a -> 'b option) -> 'b option t
447+
448+
449+
(** [x.$[p]] is the propery [p] of [x].
450+
451+
@since 2.4.0
452+
*)
453+
val (.$[]) : ('a,_) cls value -> ('a,'p) slot -> 'p
454+
455+
456+
(** [x.$[p] <- r] updates the property [p] of [x] to [r].
457+
458+
Returns the value [x] with the new property. The previous
459+
value is ignored so there is no merging or monotonicity check
460+
involved.
461+
462+
@since 2.4.0 *)
463+
val (.$[]<-) : ('a,'b) cls value -> ('a,'p) slot -> 'p -> ('a,'b) cls value
464+
465+
466+
(** [x.?[p]] returns the non-[None] property [p] or fails.
467+
468+
The result is [return r] when [x.$[p]] is [Some r] or a
469+
knowledge base conflict otherwise.
470+
471+
@since 2.4.0
472+
*)
473+
val (.?[]) : ('a,_) cls value -> ('a,'p option) slot -> 'p knowledge
474+
475+
(** [x.![p]] returns the property [p] or fails if it is empty.
476+
477+
The result is [return r] if not [Domain.is_empty dom r],
478+
where [dom] is [Slot.domain p].
479+
480+
@since 2.4.0
481+
*)
482+
val (.![]) : ('a,_) cls value -> ('a,'p) slot -> 'p knowledge
354483
end
355484

356485

@@ -1388,7 +1517,6 @@ module Knowledge : sig
13881517
module Conflict : sig
13891518
type t = conflict = ..
13901519

1391-
13921520
(** [to_string err] is the textual representation of the conflict [err]
13931521
13941522
@since 2.2.0 *)

plugins/bil/bil_lifter.ml

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -289,21 +289,17 @@ let provide_bil ~enable_intrinsics () =
289289
require Disasm_expert.Basic.Insn.slot |>
290290
provide Bil.code |>
291291
comment "uses legacy lifters to provide BIL code.");
292-
let unknown = KB.Domain.empty Bil.domain in
293-
let (>>?) x f = x >>= function
294-
| None -> KB.return unknown
295-
| Some x -> f x in
296292
let enable_intrinsics = split_specs enable_intrinsics in
297293
KB.promise Bil.code @@ fun obj ->
298-
Knowledge.collect Arch.slot obj >>= fun arch ->
299-
Theory.Label.target obj >>= fun target ->
300-
Knowledge.collect Memory.slot obj >>? fun mem ->
301-
Knowledge.collect Disasm_expert.Basic.Insn.slot obj >>? fun insn ->
294+
let* arch = KB.require Arch.slot obj in
295+
let* target = Theory.Label.target obj in
296+
let* mem = obj-->?Memory.slot in
297+
let* insn = obj-->?Disasm_expert.Basic.Insn.slot in
302298
match lift ~enable_intrinsics target arch mem insn with
303299
| Error err ->
304300
info "BIL: the BIL lifter failed with %a" Error.pp err;
305-
!!unknown
306-
| Ok [] -> !!unknown
301+
KB.return []
302+
| Ok [] -> KB.return []
307303
| Ok bil ->
308304
Optimizer.run Bil.Theory.parser bil >>= fun sema ->
309305
let bil = Insn.bil sema in

0 commit comments

Comments
 (0)