@@ -69,8 +69,6 @@ fn syntax_error_value(message: &str) -> f64 {
6969 f64:: from_bits ( JSValue :: pointer ( err as * const u8 ) . bits ( ) )
7070}
7171
72- /// A catchable `RangeError`, for the one JSON failure that is about size
73- /// rather than shape: input nested deeper than the parser can descend.
7472fn range_error_value ( message : & str ) -> f64 {
7573 let msg_ptr = js_string_from_bytes ( message. as_ptr ( ) , message. len ( ) as u32 ) ;
7674 let err = crate :: error:: js_rangeerror_new ( msg_ptr) ;
@@ -85,21 +83,32 @@ fn throw_range_error(message: &str) -> ! {
8583 crate :: exception:: js_throw ( range_error_value ( message) )
8684}
8785
88- /// The one depth check, called by every entry that is about to descend.
86+ /// Select the heap-stack parser before recursive validation or materialization
87+ /// gets close to the smallest worker-thread stack.
8988///
9089/// `js_json_parse` and `js_json_parse_result` are separate implementations of
9190/// the same flow, and the typed-array path is a third. Sharing the decision is
9291/// what keeps them from drifting — the first version of this fix guarded only
9392/// one of the three and appeared to do nothing at all, because the entry point
9493/// codegen actually calls was one of the other two.
95- fn nesting_is_too_deep ( bytes : & [ u8 ] ) -> bool {
96- crate :: json:: parser:: nesting_depth_exceeds ( bytes, crate :: json:: parser:: MAX_NESTING_DEPTH )
94+ fn requires_iterative_parse ( bytes : & [ u8 ] ) -> bool {
95+ crate :: json:: parser:: nesting_depth_exceeds (
96+ bytes,
97+ crate :: json:: parser:: MAX_RECURSIVE_NESTING_DEPTH ,
98+ )
9799}
98100
99- fn too_deep_message ( ) -> String {
101+ fn exceeds_iterative_budget ( bytes : & [ u8 ] ) -> bool {
102+ crate :: json:: parser:: nesting_depth_exceeds (
103+ bytes,
104+ crate :: json:: parser:: MAX_ITERATIVE_NESTING_DEPTH ,
105+ )
106+ }
107+
108+ fn iterative_budget_message ( ) -> String {
100109 format ! (
101- "JSON.parse: input nested deeper than {} levels " ,
102- crate :: json:: parser:: MAX_NESTING_DEPTH
110+ "JSON.parse: input exceeds the {}-level iterative nesting budget " ,
111+ crate :: json:: parser:: MAX_ITERATIVE_NESTING_DEPTH
103112 )
104113}
105114
@@ -118,6 +127,53 @@ fn is_json_null_literal(bytes: &[u8]) -> bool {
118127 & bytes[ start..end] == b"null"
119128}
120129
130+ /// Parse a deeply nested document through the flat tape representation. Tape
131+ /// construction validates syntax with an explicit heap stack; materialization
132+ /// likewise keeps pending containers on the heap. This path runs only beyond
133+ /// the recursive fast path's safe depth, so ordinary JSON keeps its existing
134+ /// allocation and shape-specialization behavior.
135+ unsafe fn try_parse_deep_iterative (
136+ text_ptr : * const StringHeader ,
137+ len : usize ,
138+ bytes : & [ u8 ] ,
139+ ) -> Option < JSValue > {
140+ let text_root = parse_root_push ( JSValue :: string_ptr ( text_ptr as * mut StringHeader ) ) ;
141+ let result = crate :: json_tape:: with_built_tape ( bytes, |tape_entries| {
142+ crate :: gc:: gc_collect_pending_suppressed_parse ( ) ;
143+ crate :: gc:: gc_check_trigger ( ) ;
144+ crate :: gc:: gc_suppress ( ) ;
145+
146+ let bytes = {
147+ let moved = parse_root_get ( text_root) ;
148+ let hdr = moved. as_string_ptr ( ) ;
149+ let data_ptr = ( hdr as * const u8 ) . add ( std:: mem:: size_of :: < StringHeader > ( ) ) ;
150+ std:: slice:: from_raw_parts ( data_ptr, len)
151+ } ;
152+ let result = crate :: json_tape:: materialize_iterative ( tape_entries, bytes) ;
153+ if let Some ( value) = result {
154+ parse_root_push ( value) ;
155+ }
156+
157+ crate :: gc:: gc_unsuppress ( ) ;
158+ crate :: gc:: gc_bump_malloc_trigger ( ) ;
159+ crate :: gc:: gc_schedule_parse_boundary_collection_if_pressure ( ) ;
160+ result
161+ } )
162+ . flatten ( ) ;
163+ parse_root_restore ( text_root) ;
164+
165+ PARSE_KEY_CACHE . with ( |cell| {
166+ let cache = cell. borrow ( ) ;
167+ if cache. len ( ) > 4096 {
168+ drop ( cache) ;
169+ cell. borrow_mut ( ) . clear ( ) ;
170+ clear_parse_key_ring ( ) ;
171+ }
172+ } ) ;
173+
174+ result
175+ }
176+
121177/// Non-throwing JSON parse entry for APIs that must reject a Promise rather than
122178/// synchronously throwing through `JSON.parse`'s FFI boundary.
123179///
@@ -136,12 +192,12 @@ pub unsafe fn js_json_parse_result(text_ptr: *const StringHeader) -> Result<JSVa
136192 return Err ( syntax_error_value ( "Unexpected end of JSON input" ) ) ;
137193 }
138194
139- // #7792: depth first, BEFORE the validation pass below. That pass recurses
140- // once per nesting level itself, so a check placed after it would run after
141- // the crash it exists to prevent. The scan is one linear pass over bytes we
142- // are about to read anyway.
143- if nesting_is_too_deep ( bytes) {
144- return Err ( range_error_value ( & too_deep_message ( ) ) ) ;
195+ if requires_iterative_parse ( bytes ) {
196+ if exceeds_iterative_budget ( bytes ) {
197+ return Err ( range_error_value ( & iterative_budget_message ( ) ) ) ;
198+ }
199+ return try_parse_deep_iterative ( text_ptr , len , bytes)
200+ . ok_or_else ( || syntax_error_value ( "JSON parse error: malformed deep document" ) ) ;
145201 }
146202
147203 // Validate without constructing a second full JSON tree. The Perry parser
@@ -236,11 +292,14 @@ pub unsafe extern "C" fn js_json_parse(text_ptr: *const StringHeader) -> JSValue
236292 if len == 0 {
237293 throw_syntax_error ( "Unexpected end of JSON input" ) ;
238294 }
239- // #7792: depth first, ahead of the validation pass, for the same reason as
240- // the `_result` twin above. This is the entry codegen emits, so a guard
241- // that covered only the twin covered nothing a compiled program can reach.
242- if nesting_is_too_deep ( bytes) {
243- throw_range_error ( & too_deep_message ( ) ) ;
295+ if requires_iterative_parse ( bytes) {
296+ if exceeds_iterative_budget ( bytes) {
297+ throw_range_error ( & iterative_budget_message ( ) ) ;
298+ }
299+ return match try_parse_deep_iterative ( text_ptr, len, bytes) {
300+ Some ( value) => value,
301+ None => throw_syntax_error ( "JSON parse error: malformed deep document" ) ,
302+ } ;
244303 }
245304 // Keep serde_json's strict syntax validation, but discard tokens as they
246305 // are read instead of allocating an intermediate `serde_json::Value`
@@ -559,10 +618,9 @@ pub unsafe extern "C" fn js_json_parse_typed_array(
559618 let data_ptr = ( text_ptr as * const u8 ) . add ( std:: mem:: size_of :: < StringHeader > ( ) ) ;
560619 let bytes = std:: slice:: from_raw_parts ( data_ptr, len) ;
561620
562- // #7792: this path builds its own parser, so it needs its own guard. Hand
563- // deep input to the generic entry rather than repeating the error here, so
564- // both report it identically.
565- if nesting_is_too_deep ( bytes) {
621+ // Deep input uses the generic entry's heap-stack fallback. The shape fast
622+ // path is deliberately retained for ordinary payloads only.
623+ if requires_iterative_parse ( bytes) {
566624 return js_json_parse ( text_ptr) ;
567625 }
568626
0 commit comments