Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@
whole-program builds emit reversed `Filename.concat` operands and
silently broke `Filename.temp_file`
* Runtime/wasm: fix string conversion from JS to OCaml (#2230)
* Runtime: only install the process-wide "uncaughtException" handler when
the program is run directly (`node a.js`), not when it is loaded as a
library (`require("./a.js")`) or in the Node REPL, where it used to
override the host's error handling and abort the process on unrelated
errors (#1277)
* Lib: fix several `Dom_html` bindings (#2221)
* Lib: defer `Intl.{Collator,DateTimeFormat,...}` member lookups so the
`Intl` module no longer throws at load time on hosts where
Expand Down
26 changes: 26 additions & 0 deletions compiler/tests-compiler/error.ml
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,29 @@ let _ = null 1 2
(try ignore (Str.search_forward (Str.regexp "TypeError: Cannot read") s 0)
with Not_found -> print_endline s);
[%expect {||}]

let%expect_test "uncaught handler is not installed when loaded as a library (#1277)" =
(* When jsoo output is loaded with [require] (e.g. in the Node REPL or as a
library) rather than run directly, it must not register a process-wide
"uncaughtException" handler: doing so used to override the host's error
handling and abort the whole process on unrelated errors. *)
with_temp_dir ~f:(fun () ->
let _js_file =
{| let () = ignore (Sys.opaque_identity 0) |}
|> Filetype.ocaml_text_of_string
|> Filetype.write_ocaml ~name:"test.ml"
|> compile_ocaml_to_bc
|> compile_bc_to_javascript
in
let driver =
{|
require("./test.js");
console.log(
"uncaughtException listeners: " +
process.listenerCount("uncaughtException"));
|}
|> Filetype.js_text_of_string
|> Filetype.write_js ~name:"driver.js"
in
print_string (run_javascript driver));
[%expect {| uncaughtException listeners: 0 |}]
17 changes: 17 additions & 0 deletions runtime/js/sys.js
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,18 @@ function caml_sys_const_standard_library_default(_unit) {
function caml_setup_uncaught_exception_handler() {
var process = globalThis.process;
if (process?.on) {
// Only emulate OCaml's fatal-error behaviour (report the exception and
// exit with code 2) when the program is run directly, e.g. `node a.js`.
// When the generated code is loaded as a library (`require("./a.js")`) or
// in the Node REPL, registering a process-wide "uncaughtException" handler
// that calls process.exit would override the host's error handling and
// make unrelated errors abort the whole process. See ocsigen/js_of_ocaml#1277.
if (
typeof module !== "undefined" &&
typeof require !== "undefined" &&
require.main !== module
)
return;
process.on("uncaughtException", function (err, origin) {
caml_fatal_uncaught_exception(err);
process.exit(2);
Expand All @@ -525,5 +537,10 @@ function caml_setup_uncaught_exception_handler() {
}
});
}
// On QuickJS there is nothing to do: it exposes neither `process.on` nor
// `addEventListener`, so we install no global handler. The engine prints
// uncaught exceptions and exits itself, and loading the output as a library
// never overrides the host's error handling -- so no standalone guard like
// the one above (or `import.meta.main`) is needed here.
}
caml_setup_uncaught_exception_handler();
17 changes: 14 additions & 3 deletions runtime/wasm/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -796,9 +796,20 @@
start_fiber = make_promising(caml_start_fiber);
var _initialize = make_promising(_initialize);
if (globalThis.process?.on) {
globalThis.process.on("uncaughtException", (err, _origin) =>
caml_handle_uncaught_exception(err),
);
// Only install the handler when run directly (`node a.js`), not when
// loaded as a library or in the REPL, where hijacking "uncaughtException"
// would override the host's error handling. See ocsigen/js_of_ocaml#1277.
if (
!(
typeof module !== "undefined" &&
typeof require !== "undefined" &&
require.main !== module
)
) {
globalThis.process.on("uncaughtException", (err, _origin) =>
caml_handle_uncaught_exception(err),
);
}
} else if (globalThis.addEventListener) {
globalThis.addEventListener(
"error",
Expand Down
Loading