PureYAML.parse() silently accepted raw C0 control characters anywhere in the input; Yams's underlying libyaml rejects them unconditionally at the reader level. Found by comparing parse() directly against Yams on a real-world OpenAPI corpus. The printable-character predicate this needed already existed (CharacterClass.isPrintable, matching libyaml's c-printable set) but was only ever wired into anchor-name validation, and the separate PreflightScanner control-character check is opt-in via validate(), not part of what parse() itself runs. Reader now records the index of the first non-printable scalar as it buffers input (covering both batch and streaming), and fetchNextToken() throws once scanning has actually reached that position, not merely once one exists, so an earlier real syntax error is not hidden behind a later control character. Verified with 5 new tests and a full re-run of the official yaml-test-suite conformance (333/333 parse-level, 208/208 value-level, unchanged) and the whole-suite streaming differential (still clean), confirming no regression.
42 lines
2.0 KiB
Swift
42 lines
2.0 KiB
Swift
extension PureYAML.Parsing.ParseError {
|
|
/// Translate a ported-scanner error into the package's diagnostic-aware error,
|
|
/// converting libyaml's 0-based mark to the 1-based convention and mapping the
|
|
/// well-known scanner failures to precise diagnostic codes.
|
|
init(translating error: PureYAML.Parsing.LibYAML.ScannerError) {
|
|
let line = error.mark.line + 1
|
|
let column = error.mark.column + 1
|
|
let context = error.context ?? ""
|
|
if error.problem.hasPrefix("found a tab") {
|
|
self = .tabIndentation(line: line)
|
|
} else if error.problem == "control characters are not allowed" {
|
|
self = .invalidControlCharacter(line: line, column: column)
|
|
} else if context == "while scanning a quoted scalar", error.problem.hasSuffix("end of stream") {
|
|
self = .unterminatedQuotedString(line: line)
|
|
} else if context.hasPrefix("while scanning a tag") || context.hasSuffix("TAG directive") {
|
|
self = .unterminatedTag(line: line)
|
|
} else {
|
|
self = .malformed(reason: Self.reason(context: error.context, problem: error.problem), line: line, column: column)
|
|
}
|
|
}
|
|
|
|
/// Translate a ported-parser error into the package's diagnostic-aware error.
|
|
init(translating error: PureYAML.Parsing.LibYAML.ParserError) {
|
|
let line = error.mark.line + 1
|
|
let column = error.mark.column + 1
|
|
if error.problem == "found incompatible YAML document" {
|
|
self = .incompatibleYAMLDirective(line: line)
|
|
} else {
|
|
self = .malformed(reason: Self.reason(context: error.context, problem: error.problem), line: line, column: column)
|
|
}
|
|
}
|
|
|
|
/// Join a libyaml context and problem into one readable reason, e.g.
|
|
/// "while parsing a flow sequence, did not find expected ',' or ']'".
|
|
private static func reason(context: String?, problem: String) -> String {
|
|
guard let context else {
|
|
return problem
|
|
}
|
|
return "\(context), \(problem)"
|
|
}
|
|
}
|