From 4edeb4e23600d196dac5a2b7c5b93b35c65ee938 Mon Sep 17 00:00:00 2001 From: nabeel a Date: Mon, 19 Oct 2026 14:19:31 +0530 Subject: [PATCH] propagate toJSON lookup exceptions in simd JSON.stringify --- crates/javy/CHANGELOG.md | 4 +++ crates/javy/src/apis/json.rs | 26 ++++++--------- crates/javy/tests/misc.rs | 19 +++++++++++ crates/javy/tests/stringify_tojson_throws.js | 33 ++++++++++++++++++++ 4 files changed, 65 insertions(+), 17 deletions(-) create mode 100644 crates/javy/tests/stringify_tojson_throws.js diff --git a/crates/javy/CHANGELOG.md b/crates/javy/CHANGELOG.md index 19a22974..376035cd 100644 --- a/crates/javy/CHANGELOG.md +++ b/crates/javy/CHANGELOG.md @@ -23,6 +23,10 @@ Versioning](https://semver.org/spec/v2.0.0.html). - `to_string_lossy` no longer leaks the engine-allocated copy of the string it converts, and no longer constructs a slice from a null pointer when that allocation fails. +- `JSON.stringify` with `simd_json_builtins` enabled now propagates an + exception thrown while looking up the top-level value's `toJSON` property + (an accessor or proxy trap that throws) instead of dropping it and + continuing. ## [8.1.0] - 2026-07-30 diff --git a/crates/javy/src/apis/json.rs b/crates/javy/src/apis/json.rs index e1d4c7e1..c7e6c50b 100644 --- a/crates/javy/src/apis/json.rs +++ b/crates/javy/src/apis/json.rs @@ -24,7 +24,6 @@ use crate::{ atom::PredefinedAtom, function::This, prelude::{MutFn, Rest}, - qjs::JS_GetProperty, }, to_js_error, val_to_string, }; @@ -137,7 +136,7 @@ fn call_json_stringify(args: Args<'_>) -> Result> { 1 => { let arg = args[0].clone(); let val: Value = if arg.is_object() { - if let Some(f) = get_to_json(&arg) { + if let Some(f) = get_to_json(&arg)? { f.call(( This(arg.clone()), JSString::from_str(arg.ctx().clone(), "")?.into_value(), @@ -172,20 +171,13 @@ fn call_json_stringify(args: Args<'_>) -> Result> { } } -fn get_to_json<'a>(value: &Value<'a>) -> Option> { - let f = unsafe { - JS_GetProperty( - value.ctx().as_raw().as_ptr(), - value.as_raw(), - PredefinedAtom::ToJSON as u32, - ) +/// Looks up the `toJSON` property of an object, propagating any exception +/// thrown by a getter or proxy trap during the lookup. +fn get_to_json<'a>(value: &Value<'a>) -> Result>> { + let Some(object) = value.as_object() else { + return Ok(None); }; - let f = unsafe { Value::from_raw(value.ctx().clone(), f) }; - if f.is_function() - && let Some(f) = f.into_function() - { - Some(f) - } else { - None - } + Ok(object + .get::<_, Value<'a>>(PredefinedAtom::ToJSON)? + .into_function()) } diff --git a/crates/javy/tests/misc.rs b/crates/javy/tests/misc.rs index 3274bc55..253e3858 100644 --- a/crates/javy/tests/misc.rs +++ b/crates/javy/tests/misc.rs @@ -76,3 +76,22 @@ fn to_string_lossy_frees_engine_cstring() -> anyhow::Result<()> { ); Ok(()) } + +#[cfg(feature = "json")] +#[test] +fn json_stringify_propagates_to_json_lookup_errors() -> Result<()> { + let mut config = Config::default(); + config.simd_json_builtins(true); + + let source = include_bytes!("stringify_tojson_throws.js"); + let rt = Runtime::new(config)?; + + rt.context().with(|this| { + let _: () = this + .eval_with_options(*source, EvalOptions::default()) + .inspect_err(|e| println!("{e}")) + .expect("source evaluation to succeed"); + }); + + Ok(()) +} diff --git a/crates/javy/tests/stringify_tojson_throws.js b/crates/javy/tests/stringify_tojson_throws.js new file mode 100644 index 00000000..f9f01062 --- /dev/null +++ b/crates/javy/tests/stringify_tojson_throws.js @@ -0,0 +1,33 @@ +// An exception thrown while looking up `toJSON` must propagate out of +// `JSON.stringify` instead of being dropped. +const boom = new Error("boom"); +const values = [ + { + get toJSON() { + throw boom; + }, + }, + new Proxy( + { a: 1 }, + { + get(target, key) { + if (key === "toJSON") { + throw boom; + } + return Reflect.get(target, key); + }, + } + ), +]; + +for (const value of values) { + let thrown; + try { + JSON.stringify(value); + } catch (e) { + thrown = e; + } + if (thrown !== boom) { + throw new Error(`expected JSON.stringify to throw, got ${thrown}`); + } +}