@@ -58,6 +58,53 @@ const VARYING_TOKEN_KINDS: &[&str] = &[
5858 "unknown" ,
5959] ;
6060
61+ /// Maximum structural nesting accepted in the serialized JSON tree.
62+ ///
63+ /// This exceeds the deepest tree in the Swift corpus while bounding the
64+ /// recursive serde deserialization and AST construction that follow.
65+ const MAX_JSON_DEPTH : usize = 2048 ;
66+
67+ /// Check JSON structural depth without recursively parsing it.
68+ ///
69+ /// Brackets and braces inside strings are ignored. Full JSON validation is
70+ /// still performed by serde_json afterward.
71+ fn check_json_depth ( json : & str ) -> Result < ( ) , String > {
72+ let mut depth = 0 ;
73+ let mut in_string = false ;
74+ let mut escaped = false ;
75+
76+ for byte in json. bytes ( ) {
77+ if in_string {
78+ if escaped {
79+ escaped = false ;
80+ } else {
81+ match byte {
82+ b'\\' => escaped = true ,
83+ b'"' => in_string = false ,
84+ _ => { }
85+ }
86+ }
87+ } else {
88+ match byte {
89+ b'"' => in_string = true ,
90+ b'{' | b'[' => {
91+ depth += 1 ;
92+ if depth > MAX_JSON_DEPTH {
93+ return Err ( format ! (
94+ "invalid JSON: nesting depth exceeds supported maximum \
95+ ({MAX_JSON_DEPTH})"
96+ ) ) ;
97+ }
98+ }
99+ b'}' | b']' => depth = depth. saturating_sub ( 1 ) ,
100+ _ => { }
101+ }
102+ }
103+ }
104+
105+ Ok ( ( ) )
106+ }
107+
61108/// Keys of a node object that carry metadata rather than a structural child.
62109fn is_metadata_key ( key : & str ) -> bool {
63110 matches ! (
@@ -326,6 +373,7 @@ const SWIFT_NODE_TYPES: &str = include_str!("../../../swift_node_types.yml");
326373/// authoritative swift-syntax schema ([`SWIFT_NODE_TYPES`]); the adapter only
327374/// ever consumes swift-syntax input, so the schema is not a parameter.
328375pub fn json_to_ast ( json : & str ) -> Result < AdaptedTree , String > {
376+ check_json_depth ( json) ?;
329377 let mut deserializer = serde_json:: Deserializer :: from_str ( json) ;
330378 deserializer. disable_recursion_limit ( ) ;
331379 let root = Value :: deserialize ( & mut deserializer) . map_err ( |e| format ! ( "invalid JSON: {e}" ) ) ?;
@@ -349,6 +397,14 @@ pub fn json_to_ast(json: &str) -> Result<AdaptedTree, String> {
349397mod tests {
350398 use super :: * ;
351399
400+ fn deeply_nested_json ( depth : usize ) -> String {
401+ let mut child = r#"{"$pos":0,"$end":0,"kind":"sourceFile"}"# . to_string ( ) ;
402+ for _ in 0 ..depth {
403+ child = format ! ( r#"{{"$pos":0,"$end":0,"kind":"sourceFile","child":{child}}}"# ) ;
404+ }
405+ format ! ( r#"{{"$lineStarts":[0],"$pos":0,"$end":0,"kind":"sourceFile","child":{child}}}"# )
406+ }
407+
352408 /// A hand-written JSON tree exercising layout nodes, a named (varying)
353409 /// token, a fixed keyword token, and an elided collection field — so the
354410 /// adapter is tested without needing the Swift toolchain.
@@ -522,15 +578,27 @@ mod tests {
522578
523579 #[ test]
524580 fn accepts_deeply_nested_json ( ) {
525- let mut child = r#"{"$pos":0,"$end":0,"kind":"sourceFile"}"# . to_string ( ) ;
526- for _ in 0 ..256 {
527- child = format ! ( r#"{{"$pos":0,"$end":0,"kind":"sourceFile","child":{child}}}"# ) ;
528- }
529- let json = format ! (
530- r#"{{"$lineStarts":[0],"$pos":0,"$end":0,"kind":"sourceFile","child":{child}}}"#
581+ let json = deeply_nested_json ( 256 ) ;
582+ json_to_ast ( & json) . expect ( "adapter should accept JSON nested beyond serde_json's default" ) ;
583+ }
584+
585+ #[ test]
586+ fn rejects_json_beyond_supported_depth ( ) {
587+ let json = deeply_nested_json ( MAX_JSON_DEPTH ) ;
588+ let error = match json_to_ast ( & json) {
589+ Ok ( _) => panic ! ( "adapter should reject excessively nested JSON" ) ,
590+ Err ( error) => error,
591+ } ;
592+ assert ! (
593+ error. contains( "nesting depth exceeds supported maximum" ) ,
594+ "{error}"
531595 ) ;
596+ }
532597
533- json_to_ast ( & json) . expect ( "adapter should accept JSON nested beyond serde_json's default" ) ;
598+ #[ test]
599+ fn ignores_brackets_inside_json_strings_when_checking_depth ( ) {
600+ check_json_depth ( r#"{"text":"[[[{{{\\\""}"# )
601+ . expect ( "string contents should not contribute to JSON depth" ) ;
534602 }
535603
536604 #[ test]
0 commit comments