HS tests: VM step limit fix, callFn error propagation, compiler emit-set fixes

- sx_vm.ml: VM timeout now compares vm_insn_count > step_limit instead of
  unconditionally throwing after 65536 instructions when limit > 0
- sx_browser.ml: Expose setStepLimit/resetStepCount APIs on SxKernel;
  callFn now returns {__sx_error, message} on Eval_error instead of null
- compiler.sx: emit-set handles array-index targets (host-set! instead of
  nth) and 'of' property chains (dom-set-prop with chain navigation)
- hs-run-fast.js: New Node.js test runner with step-limit timeouts,
  SX-level guard for error detection, insertAdjacentHTML mock,
  range selection (HS_START/HS_END), wall-clock timeout in driveAsync
- hs-debug-test.js: Single-test debugger with DOM state inspection
- hs-verify.js: Assertion verification (proves pass/fail detection works)

Test results: 415/831 (50%), up from 408/831 (49%) baseline.
Fixes: set my style["color"], set X of Y, put at end of (insertAdjacentHTML).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-14 19:27:03 +00:00
parent b81c26c45b
commit b1666a5fe2
8 changed files with 3132 additions and 2293 deletions

View File

@@ -544,15 +544,14 @@ let api_call_fn fn_js args_js =
sync_vm_to_env ();
Js.Unsafe.inject (make_js_callFn_suspension request vm)
| Eval_error msg ->
ignore (Js.Unsafe.meth_call
(Js.Unsafe.get Js.Unsafe.global (Js.string "console"))
"error" [| Js.Unsafe.inject (Js.string ("[sx] callFn: " ^ msg)) |]);
Js.Unsafe.inject Js.null
(* Store the error message so callers can detect it *)
let err_obj = Js.Unsafe.obj [| ("__sx_error", Js.Unsafe.inject Js._true);
("message", Js.Unsafe.inject (Js.string msg)) |] in
Js.Unsafe.inject err_obj
| exn ->
ignore (Js.Unsafe.meth_call
(Js.Unsafe.get Js.Unsafe.global (Js.string "console"))
"error" [| Js.Unsafe.inject (Js.string ("[sx] callFn: " ^ Printexc.to_string exn)) |]);
Js.Unsafe.inject Js.null
let err_obj = Js.Unsafe.obj [| ("__sx_error", Js.Unsafe.inject Js._true);
("message", Js.Unsafe.inject (Js.string (Printexc.to_string exn))) |] in
Js.Unsafe.inject err_obj
let api_is_callable fn_js =
if Js.Unsafe.equals fn_js Js.null || Js.Unsafe.equals fn_js Js.undefined then
@@ -1049,4 +1048,16 @@ let () =
let log = Sx_primitives.scope_trace_drain () in
Js.Unsafe.inject (Js.array (Array.of_list (List.map (fun s -> Js.Unsafe.inject (Js.string s)) log)))));
(* Step limit for timeout protection *)
Js.Unsafe.set sx (Js.string "setStepLimit") (Js.wrap_callback (fun n ->
let limit = Js.float_of_number (Js.Unsafe.coerce n) |> int_of_float in
Sx_ref.step_limit := limit;
Sx_ref.step_count := 0;
Sx_vm.vm_reset_counters ();
Js.Unsafe.inject Js.null));
Js.Unsafe.set sx (Js.string "resetStepCount") (Js.wrap_callback (fun () ->
Sx_ref.step_count := 0;
Sx_vm.vm_reset_counters ();
Js.Unsafe.inject Js.null));
Js.Unsafe.set Js.Unsafe.global (Js.string "SxKernel") sx

View File

@@ -448,8 +448,9 @@ and run vm =
let op = bc.(frame.ip) in
frame.ip <- frame.ip + 1;
incr _vm_insn_count;
(* Check timeout flag set by SIGALRM *)
if !_vm_insn_count land 0xFFFF = 0 && !Sx_ref.step_limit > 0 then
(* Check timeout — compare VM instruction count against step limit *)
if !_vm_insn_count land 0xFFFF = 0 && !Sx_ref.step_limit > 0
&& !_vm_insn_count > !Sx_ref.step_limit then
raise (Eval_error "TIMEOUT: step limit exceeded");
(try match op with
(* ---- Constants ---- *)