Skip to content

Commit b901210

Browse files
committed
Unified: Allow NULs in source code
These are apparently valid in Swift (there's a test for it in `swiftlang/swift` that is parsed -- with a warning -- by the Swift compiler). To allow these, the SwiftSyntaxFFI now passes a string-with-length rather than a NUL-terminated string.
1 parent e6222a7 commit b901210

2 files changed

Lines changed: 25 additions & 17 deletions

File tree

‎unified/swift-syntax-rs/src/lib.rs‎

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@
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;
1414
use std::os::raw::c_char;
1515

1616
// C ABI exported by the `SwiftSyntaxFFI` dynamic library.
1717
unsafe 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)]
2929
pub 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 {
3937
impl 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
/// ```
6057
pub 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} */\nlet x = 1";

‎unified/swift-syntax-rs/swift/Sources/SwiftSyntaxFFI/SwiftSyntaxFFI.swift‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -292,15 +292,19 @@ private func appendJSON(_ value: Any, to output: inout [UInt8]) throws {
292292
}
293293
}
294294

295-
/// Parse the given NUL-terminated Swift source string and return a
296-
/// heap-allocated, NUL-terminated JSON representation of the syntax tree.
295+
/// Parse the given UTF-8 Swift source buffer and return a heap-allocated,
296+
/// NUL-terminated JSON representation of the syntax tree.
297297
///
298298
/// The returned pointer is owned by the caller and MUST be released with
299299
/// `ssr_string_free`. Returns `nil` on failure.
300300
@_cdecl("ssr_parse_json")
301-
public func ssr_parse_json(_ source: UnsafePointer<CChar>?) -> UnsafeMutablePointer<CChar>? {
302-
guard let source = source else { return nil }
303-
let code = String(cString: source)
301+
public func ssr_parse_json(
302+
_ source: UnsafePointer<UInt8>?,
303+
_ sourceLength: Int
304+
) -> UnsafeMutablePointer<CChar>? {
305+
guard sourceLength >= 0, source != nil || sourceLength == 0 else { return nil }
306+
let sourceBytes = UnsafeBufferPointer(start: source, count: sourceLength)
307+
let code = String(decoding: sourceBytes, as: UTF8.self)
304308
let tree = Parser.parse(source: code)
305309
// Fold operator sequences before serializing. Source positions are
306310
// preserved by folding (the same tokens, in the same places), so a

0 commit comments

Comments
 (0)