Skip to content
This repository was archived by the owner on Apr 25, 2025. It is now read-only.

Commit ea7f2c0

Browse files
authored
[interpreter] Add support for validation and evaluation for exceptions (#175)
* Add support for exception validation * Add evaluation support for exceptions * Handle exceptions in the script runner * Improve core tests * Rename AssertUncaughtException -> AssertException
1 parent fc20c1f commit ea7f2c0

File tree

12 files changed

+263
-25
lines changed

12 files changed

+263
-25
lines changed

interpreter/exec/eval.ml

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ open Source
99

1010
module Link = Error.Make ()
1111
module Trap = Error.Make ()
12+
module Exception = Error.Make ()
1213
module Crash = Error.Make ()
1314
module Exhaustion = Error.Make ()
1415

1516
exception Link = Link.Error
1617
exception Trap = Trap.Error
18+
exception Exception = Exception.Error
1719
exception Crash = Crash.Error (* failure that cannot happen in valid code *)
1820
exception Exhaustion = Exhaustion.Error
1921

@@ -62,8 +64,14 @@ and admin_instr' =
6264
| Trapping of string
6365
| Returning of value stack
6466
| Breaking of int32 * value stack
67+
| Throwing of Tag.t * value stack
68+
| Rethrowing of int32 * (admin_instr -> admin_instr)
6569
| Label of int32 * instr list * code
6670
| Frame of int32 * frame * code
71+
| Catch of int32 * (Tag.t * instr list) list * instr list option * code
72+
| Caught of int32 * Tag.t * value stack * code
73+
| Delegate of int32 * code
74+
| Delegating of int32 * Tag.t * value stack
6775

6876
type config =
6977
{
@@ -205,6 +213,32 @@ let rec step (c : config) : config =
205213
else
206214
vs, [Invoke func @@ e.at]
207215

216+
| Throw x, vs ->
217+
let t = tag frame.inst x in
218+
let FuncType (ts, _) = Tag.type_of t in
219+
let n = Lib.List32.length ts in
220+
let args, vs' = take n vs e.at, drop n vs e.at in
221+
vs', [Throwing (t, args) @@ e.at]
222+
223+
| Rethrow x, vs ->
224+
vs, [Rethrowing (x.it, fun e -> e) @@ e.at]
225+
226+
| TryCatch (bt, es', cts, ca), vs ->
227+
let FuncType (ts1, ts2) = block_type frame.inst bt in
228+
let n1 = Lib.List32.length ts1 in
229+
let n2 = Lib.List32.length ts2 in
230+
let args, vs' = take n1 vs e.at, drop n1 vs e.at in
231+
let cts' = List.map (fun (x, es'') -> ((tag frame.inst x), es'')) cts in
232+
vs', [Label (n2, [], ([], [Catch (n2, cts', ca, (args, List.map plain es')) @@ e.at])) @@ e.at]
233+
234+
| TryDelegate (bt, es', x), vs ->
235+
let FuncType (ts1, ts2) = block_type frame.inst bt in
236+
let n1 = Lib.List32.length ts1 in
237+
let n2 = Lib.List32.length ts2 in
238+
let args, vs' = take n1 vs e.at, drop n1 vs e.at in
239+
let k = Int32.succ x.it in
240+
vs', [Label (n2, [], ([], [Delegate (k, (args, List.map plain es')) @@ e.at])) @@ e.at]
241+
208242
| Drop, v :: vs' ->
209243
vs', []
210244

@@ -482,6 +516,15 @@ let rec step (c : config) : config =
482516
| Breaking (k, vs'), vs ->
483517
Crash.error e.at "undefined label"
484518

519+
| Throwing _, _ ->
520+
assert false
521+
522+
| Rethrowing _, _ ->
523+
Crash.error e.at "undefined catch label"
524+
525+
| Delegating _, _ ->
526+
Crash.error e.at "undefined delegate label"
527+
485528
| Label (n, es0, (vs', [])), vs ->
486529
vs' @ vs, []
487530

@@ -497,6 +540,18 @@ let rec step (c : config) : config =
497540
| Label (n, es0, (vs', {it = Breaking (k, vs0); at} :: es')), vs ->
498541
vs, [Breaking (Int32.sub k 1l, vs0) @@ at]
499542

543+
| Label (n, es0, (vs', {it = Throwing (a, vs0); at} :: es')), vs ->
544+
vs, [Throwing (a, vs0) @@ at]
545+
546+
| Label (n, es0, (vs', {it = Delegating (0l, a, vs0); at} :: es')), vs ->
547+
vs, [Throwing (a, vs0) @@ at]
548+
549+
| Label (n, es0, (vs', {it = Delegating (k, a, vs0); at} :: es')), vs ->
550+
vs, [Delegating (Int32.sub k 1l, a, vs0) @@ at]
551+
552+
| Label (n, es0, (vs', {it = Rethrowing (k, cont); at} :: es')), vs ->
553+
vs, [Rethrowing (Int32.sub k 1l, (fun e -> Label (n, es0, (vs', cont e :: es')) @@ e.at)) @@ at]
554+
500555
| Label (n, es0, code'), vs ->
501556
let c' = step {c with code = code'} in
502557
vs, [Label (n, es0, c'.code) @@ e.at]
@@ -510,10 +565,70 @@ let rec step (c : config) : config =
510565
| Frame (n, frame', (vs', {it = Returning vs0; at} :: es')), vs ->
511566
take n vs0 e.at @ vs, []
512567

568+
| Frame (n, frame', (vs', {it = Throwing (a, vs0); at} :: es')), vs ->
569+
vs, [Throwing (a, vs0) @@ at]
570+
513571
| Frame (n, frame', code'), vs ->
514572
let c' = step {frame = frame'; code = code'; budget = c.budget - 1} in
515573
vs, [Frame (n, c'.frame, c'.code) @@ e.at]
516574

575+
| Catch (n, cts, ca, (vs', [])), vs ->
576+
vs' @ vs, []
577+
578+
| Catch (n, cts, ca, (vs', {it = Delegating (0l, a, vs0); at} :: es')), vs ->
579+
vs, [Catch (n, cts, ca, (vs', (Throwing (a, vs0) @@ at) :: es')) @@ e.at]
580+
581+
| Catch (n, cts, ca, (vs', ({it = Trapping _ | Breaking _ | Returning _ | Delegating _; at} as e) :: es')), vs ->
582+
vs, [e]
583+
584+
| Catch (n, cts, ca, (vs', {it = Rethrowing (k, cont); at} :: es')), vs ->
585+
vs, [Rethrowing (k, (fun e -> Catch (n, cts, ca, (vs', (cont e) :: es')) @@ e.at)) @@ at]
586+
587+
| Catch (n, (a', es'') :: cts, ca, (vs', {it = Throwing (a, vs0); at} :: es')), vs ->
588+
if a == a' then
589+
vs, [Caught (n, a, vs0, (vs0, List.map plain es'')) @@ at]
590+
else
591+
vs, [Catch (n, cts, ca, (vs', {it = Throwing (a, vs0); at} :: es')) @@ e.at]
592+
593+
| Catch (n, [], Some es'', (vs', {it = Throwing (a, vs0); at} :: es')), vs ->
594+
vs, [Caught (n, a, vs0, (vs0, List.map plain es'')) @@ at]
595+
596+
| Catch (n, [], None, (vs', {it = Throwing (a, vs0); at} :: es')), vs ->
597+
vs, [Throwing (a, vs0) @@ at]
598+
599+
| Catch (n, cts, ca, code'), vs ->
600+
let c' = step {c with code = code'} in
601+
vs, [Catch (n, cts, ca, c'.code) @@ e.at]
602+
603+
| Caught (n, a, vs0, (vs', [])), vs ->
604+
vs' @ vs, []
605+
606+
| Caught (n, a, vs0, (vs', ({it = Trapping _ | Breaking _ | Returning _ | Throwing _ | Delegating _; at} as e) :: es')), vs ->
607+
vs, [e]
608+
609+
| Caught (n, a, vs0, (vs', {it = Rethrowing (0l, cont); at} :: es')), vs ->
610+
vs, [Caught (n, a, vs0, (vs', (cont (Throwing (a, vs0) @@ at)) :: es')) @@ e.at]
611+
612+
| Caught (n, a, vs0, (vs', {it = Rethrowing (k, cont); at} :: es')), vs ->
613+
vs, [Rethrowing (k, (fun e -> Caught (n, a, vs0, (vs', (cont e) :: es')) @@ e.at)) @@ at]
614+
615+
| Caught (n, a, vs0, code'), vs ->
616+
let c' = step {c with code = code'} in
617+
vs, [Caught (n, a, vs0, c'.code) @@ e.at]
618+
619+
| Delegate (l, (vs', [])), vs ->
620+
vs' @ vs, []
621+
622+
| Delegate (l, (vs', ({it = Trapping _ | Breaking _ | Returning _ | Rethrowing _ | Delegating _; at} as e) :: es')), vs ->
623+
vs, [e]
624+
625+
| Delegate (l, (vs', {it = Throwing (a, vs0); at} :: es')), vs ->
626+
vs, [Delegating (l, a, vs0) @@ e.at]
627+
628+
| Delegate (l, code'), vs ->
629+
let c' = step {c with code = code'} in
630+
vs, [Delegate (l, c'.code) @@ e.at]
631+
517632
| Invoke func, vs when c.budget = 0 ->
518633
Exhaustion.error e.at "call stack exhausted"
519634

@@ -543,6 +658,10 @@ let rec eval (c : config) : value stack =
543658
| vs, {it = Trapping msg; at} :: _ ->
544659
Trap.error at msg
545660

661+
| vs, {it = Throwing (a, args); at} :: _ ->
662+
let msg = "uncaught exception with args (" ^ string_of_values args ^ ")" in
663+
Exception.error at msg
664+
546665
| vs, es ->
547666
eval (step c)
548667

interpreter/exec/eval.mli

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ open Instance
33

44
exception Link of Source.region * string
55
exception Trap of Source.region * string
6+
exception Exception of Source.region * string
67
exception Crash of Source.region * string
78
exception Exhaustion of Source.region * string
89

interpreter/script/js.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,7 @@ let of_assertion mods ass =
513513
of_assertion' mods act "assert_trap" [] None
514514
| AssertExhaustion (act, _) ->
515515
of_assertion' mods act "assert_exhaustion" [] None
516-
| AssertUncaughtException act ->
516+
| AssertException act ->
517517
of_assertion' mods act "assert_exception" [] None
518518

519519
let of_command mods cmd =

interpreter/script/run.ml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ let input_from get_script run =
112112
| Eval.Trap (at, msg) -> error at "runtime trap" msg
113113
| Eval.Exhaustion (at, msg) -> error at "resource exhaustion" msg
114114
| Eval.Crash (at, msg) -> error at "runtime crash" msg
115+
| Eval.Exception (at, msg) -> error at "uncaught exception" msg
115116
| Encode.Code (at, msg) -> error at "encoding error" msg
116117
| Script.Error (at, msg) -> error at "script error" msg
117118
| IO (at, msg) -> error at "i/o error" msg
@@ -458,7 +459,12 @@ let run_assertion ass =
458459
| _ -> Assert.error ass.at "expected runtime error"
459460
)
460461

461-
| AssertUncaughtException act -> () (* TODO *)
462+
| AssertException act ->
463+
trace ("Asserting exception...");
464+
(match run_action act with
465+
| exception Eval.Exception (_, msg) -> ()
466+
| _ -> Assert.error ass.at "expected exception"
467+
)
462468

463469
| AssertExhaustion (act, re) ->
464470
trace ("Asserting exhaustion...");

interpreter/script/script.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ and assertion' =
3232
| AssertUninstantiable of definition * string
3333
| AssertReturn of action * result list
3434
| AssertTrap of action * string
35-
| AssertUncaughtException of action
35+
| AssertException of action
3636
| AssertExhaustion of action * string
3737

3838
type command = command' Source.phrase

interpreter/text/arrange.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,7 @@ let assertion mode ass =
550550
[Node ("assert_return", action mode act :: List.map (result mode) results)]
551551
| AssertTrap (act, re) ->
552552
[Node ("assert_trap", [action mode act; Atom (string re)])]
553-
| AssertUncaughtException act ->
553+
| AssertException act ->
554554
[Node ("assert_exception", [action mode act])]
555555
| AssertExhaustion (act, re) ->
556556
[Node ("assert_exhaustion", [action mode act; Atom (string re)])]

interpreter/text/parser.mly

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1169,7 +1169,7 @@ assertion :
11691169
| LPAR ASSERT_RETURN action result_list RPAR { AssertReturn ($3, $4) @@ at () }
11701170
| LPAR ASSERT_TRAP action STRING RPAR { AssertTrap ($3, $4) @@ at () }
11711171
| LPAR ASSERT_EXCEPTION action RPAR
1172-
{ AssertUncaughtException $3 @@ at () }
1172+
{ AssertException $3 @@ at () }
11731173
| LPAR ASSERT_EXHAUSTION action STRING RPAR { AssertExhaustion ($3, $4) @@ at () }
11741174

11751175
cmd :

interpreter/valid/valid.ml

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ let require b at s = if not b then error at s
1414

1515
(* Context *)
1616

17+
type label_kind = BlockLabel | CatchLabel
18+
1719
type context =
1820
{
1921
types : func_type list;
@@ -26,7 +28,7 @@ type context =
2628
datas : unit list;
2729
locals : value_type list;
2830
results : value_type list;
29-
labels : stack_type list;
31+
labels : (label_kind * stack_type) list;
3032
refs : Free.t;
3133
}
3234

@@ -229,32 +231,35 @@ let rec check_instr (c : context) (e : instr) (s : infer_stack_type) : op_type =
229231

230232
| Block (bt, es) ->
231233
let FuncType (ts1, ts2) as ft = check_block_type c bt in
232-
check_block {c with labels = ts2 :: c.labels} es ft e.at;
234+
check_block {c with labels = (BlockLabel, ts2) :: c.labels} es ft e.at;
233235
ts1 --> ts2
234236

235237
| Loop (bt, es) ->
236238
let FuncType (ts1, ts2) as ft = check_block_type c bt in
237-
check_block {c with labels = ts1 :: c.labels} es ft e.at;
239+
check_block {c with labels = (BlockLabel, ts1) :: c.labels} es ft e.at;
238240
ts1 --> ts2
239241

240242
| If (bt, es1, es2) ->
241243
let FuncType (ts1, ts2) as ft = check_block_type c bt in
242-
check_block {c with labels = ts2 :: c.labels} es1 ft e.at;
243-
check_block {c with labels = ts2 :: c.labels} es2 ft e.at;
244+
check_block {c with labels = (BlockLabel, ts2) :: c.labels} es1 ft e.at;
245+
check_block {c with labels = (BlockLabel, ts2) :: c.labels} es2 ft e.at;
244246
(ts1 @ [NumType I32Type]) --> ts2
245247

246248
| Br x ->
247-
label c x -->... []
249+
let (_, ts) = label c x in
250+
ts -->... []
248251

249252
| BrIf x ->
250-
(label c x @ [NumType I32Type]) --> label c x
253+
let (_, ts) = label c x in
254+
(ts @ [NumType I32Type]) --> ts
251255

252256
| BrTable (xs, x) ->
253-
let n = List.length (label c x) in
254-
let ts = Lib.List.table n (fun i -> peek (n - i) s) in
255-
check_stack ts (known (label c x)) x.at;
256-
List.iter (fun x' -> check_stack ts (known (label c x')) x'.at) xs;
257-
(ts @ [Some (NumType I32Type)]) -~>... []
257+
let (_, ts) = label c x in
258+
let n = List.length ts in
259+
let ts' = Lib.List.table n (fun i -> peek (n - i) s) in
260+
check_stack ts' (known ts) x.at;
261+
List.iter (fun x' -> check_stack ts' (known (snd (label c x'))) x'.at) xs;
262+
(ts' @ [Some (NumType I32Type)]) -~>... []
258263

259264
| Return ->
260265
c.results -->... []
@@ -271,6 +276,31 @@ let rec check_instr (c : context) (e : instr) (s : infer_stack_type) : op_type =
271276
" but table has " ^ string_of_ref_type t);
272277
(ts1 @ [NumType I32Type]) --> ts2
273278

279+
| Throw x ->
280+
let TagType y = tag c x in
281+
let FuncType (ts1, _) = type_ c (y @@ e.at) in
282+
ts1 -->... []
283+
284+
| Rethrow x ->
285+
let (kind, _) = label c x in
286+
require (kind = CatchLabel) e.at "invalid rethrow label";
287+
[] -->... []
288+
289+
| TryCatch (bt, es, cts, ca) ->
290+
let FuncType (ts1, ts2) as ft = check_block_type c bt in
291+
let c_try = {c with labels = (BlockLabel, ts2) :: c.labels} in
292+
let c_catch = {c with labels = (CatchLabel, ts2) :: c.labels} in
293+
check_block c_try es ft e.at;
294+
List.iter (fun ct -> check_catch ct c_catch ft e.at) cts;
295+
Lib.Option.app (fun es -> check_block c_catch es ft e.at) ca;
296+
ts1 --> ts2
297+
298+
| TryDelegate (bt, es, x) ->
299+
let FuncType (ts1, ts2) as ft = check_block_type c bt in
300+
ignore (label c x);
301+
check_block {c with labels = (BlockLabel, ts2) :: c.labels} es ft e.at;
302+
ts1 --> ts2
303+
274304
| LocalGet x ->
275305
[] --> [local c x]
276306

@@ -402,11 +432,6 @@ let rec check_instr (c : context) (e : instr) (s : infer_stack_type) : op_type =
402432
let t1, t2 = type_cvtop e.at cvtop in
403433
[NumType t1] --> [NumType t2]
404434

405-
| TryCatch _ -> [] --> [] (* TODO *)
406-
| TryDelegate _ -> [] --> [] (* TODO *)
407-
| Throw _ -> [] --> [] (* TODO *)
408-
| Rethrow _ -> [] --> [] (* TODO *)
409-
410435
and check_seq (c : context) (s : infer_stack_type) (es : instr list)
411436
: infer_stack_type =
412437
match es with
@@ -427,6 +452,13 @@ and check_block (c : context) (es : instr list) (ft : func_type) at =
427452
("type mismatch: block requires " ^ string_of_stack_type ts2 ^
428453
" but stack has " ^ string_of_infer_types (snd s))
429454

455+
and check_catch (ct : var * instr list) (c : context) (ft : func_type) at =
456+
let (x, es) = ct in
457+
let TagType y = tag c x in
458+
let FuncType (ts1, _) = type_ c (y @@ at) in
459+
let FuncType (_, ts2) = ft in
460+
check_block c es (FuncType (ts1, ts2)) at
461+
430462

431463
(* Types *)
432464

@@ -491,7 +523,7 @@ let check_type (t : type_) =
491523
let check_func (c : context) (f : func) =
492524
let {ftype; locals; body} = f.it in
493525
let FuncType (ts1, ts2) = type_ c ftype in
494-
let c' = {c with locals = ts1 @ locals; results = ts2; labels = [ts2]} in
526+
let c' = {c with locals = ts1 @ locals; results = ts2; labels = [(BlockLabel, ts2)]} in
495527
check_block c' body (FuncType ([], ts2)) f.at
496528

497529
let check_tag (c : context) (t : tag) =

0 commit comments

Comments
 (0)