1010//! by the extractor's own pure-Rust adapter module, keeping the Swift toolchain
1111//! out of the extractor's build.
1212
13- use std:: ffi:: { CStr , CString } ;
13+ use std:: ffi:: CStr ;
1414use std:: os:: raw:: c_char;
1515
1616// C ABI exported by the `SwiftSyntaxFFI` dynamic library.
1717unsafe extern "C" {
18- /// Parse a NUL-terminated Swift source string , returning a heap-allocated
18+ /// Parse a UTF-8 Swift source buffer , returning a heap-allocated
1919 /// NUL-terminated JSON string (or null on failure). The caller owns the
2020 /// returned pointer and must release it with `ssr_string_free`.
21- fn ssr_parse_json ( source : * const c_char ) -> * mut c_char ;
21+ fn ssr_parse_json ( source : * const u8 , source_len : usize ) -> * mut c_char ;
2222
2323 /// Free a string previously returned by `ssr_parse_json`.
2424 fn ssr_string_free ( ptr : * mut c_char ) ;
@@ -27,8 +27,6 @@ unsafe extern "C" {
2727/// Errors that can occur while parsing Swift source.
2828#[ derive( Debug ) ]
2929pub enum ParseError {
30- /// The provided source contained an interior NUL byte.
31- NulByte ,
3230 /// The Swift shim returned no result. `SwiftParser` recovers from invalid
3331 /// syntax (it always produces a tree, possibly with error nodes), so this
3432 /// does *not* indicate a syntax error in the source — it means the shim
@@ -39,7 +37,6 @@ pub enum ParseError {
3937impl std:: fmt:: Display for ParseError {
4038 fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
4139 match self {
42- ParseError :: NulByte => write ! ( f, "source contained an interior NUL byte" ) ,
4340 ParseError :: SwiftFailure => {
4441 write ! ( f, "the swift-syntax shim failed to produce a JSON result" )
4542 }
@@ -58,13 +55,11 @@ impl std::error::Error for ParseError {}
5855/// println!("{json}");
5956/// ```
6057pub fn parse_to_json ( source : & str ) -> Result < String , ParseError > {
61- let c_source = CString :: new ( source) . map_err ( |_| ParseError :: NulByte ) ?;
62-
63- // SAFETY: `c_source` is a valid NUL-terminated string for the duration of
64- // the call. The returned pointer, if non-null, is owned by us and freed via
65- // `ssr_string_free` before returning.
58+ // SAFETY: `source` is valid UTF-8 and its buffer remains alive for the
59+ // duration of the call. The returned pointer, if non-null, is owned by us
60+ // and freed via `ssr_string_free` before returning.
6661 unsafe {
67- let ptr = ssr_parse_json ( c_source . as_ptr ( ) ) ;
62+ let ptr = ssr_parse_json ( source . as_ptr ( ) , source . len ( ) ) ;
6863 if ptr. is_null ( ) {
6964 return Err ( ParseError :: SwiftFailure ) ;
7065 }
@@ -132,6 +127,15 @@ mod tests {
132127 ) ;
133128 }
134129
130+ #[ test]
131+ fn parses_source_with_interior_nul ( ) {
132+ let json = parse_to_json ( "let x =\0 1" ) . expect ( "parsing interior NUL should succeed" ) ;
133+ assert ! (
134+ json. contains( r#"\u0000"# ) ,
135+ "interior NUL should be preserved in the JSON tree: {json}"
136+ ) ;
137+ }
138+
135139 #[ test]
136140 fn serializes_json_strings_and_keys_deterministically ( ) {
137141 let source = "/* quote \" slash / backslash \\ tab \t newline\n emoji 😀 combining e\u{301} control \u{1} */\n let x = 1" ;
0 commit comments