diff --git a/CHANGELOG.md b/CHANGELOG.md index b2a91a5..78dc04a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,9 @@ ### Breaking -- Change how some plain YAML scalars are parsed in `std.parseYaml`: +- Some semantics of `std.parseYaml` have changed: + - Empty YAML documents are now parsed as `null` instead of an empty string. + - Anchors and aliases are now supported. - `Null`, `NULL` and `~` are now parsed as `null`. - `True` and `TRUE` are now parsed as `true` . - `False` and `FALSE` are now parsed as `false`. @@ -21,6 +23,7 @@ - Avoid panic when encountering an array or object as object key in `std.parseYaml`. - Avoid panic when encountering a YAML alias in `std.parseYaml`. +- Avoid some panics in `std.parseYaml` with some inputs found with fuzzing. ## 0.1.2 (2024-07-20) diff --git a/Cargo.lock b/Cargo.lock index 6a52c5a..76db9f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -69,6 +69,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "arraydeque" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" + [[package]] name = "autocfg" version = "1.3.0" @@ -225,6 +231,15 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown", +] + [[package]] name = "heck" version = "0.5.0" @@ -271,12 +286,6 @@ dependencies = [ "threadpool", ] -[[package]] -name = "libyaml-safer" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cff24caa08828851c9b7ed1bce365b1d2496b9323dd0d4b241c1d10135f5bd6" - [[package]] name = "lock_api" version = "0.4.12" @@ -414,8 +423,18 @@ name = "rsjsonnet-lang" version = "0.2.0-pre" dependencies = [ "hashbrown", - "libyaml-safer", "md-5", + "saphyr-parser", +] + +[[package]] +name = "saphyr-parser" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f85693131191aebc97ccb170ed0fb4fe490df76636402b6e0773e94f8b0e8670" +dependencies = [ + "arraydeque", + "hashlink", ] [[package]] diff --git a/rsjsonnet-lang/Cargo.toml b/rsjsonnet-lang/Cargo.toml index 86a5c7e..9adac28 100644 --- a/rsjsonnet-lang/Cargo.toml +++ b/rsjsonnet-lang/Cargo.toml @@ -12,5 +12,5 @@ publish.workspace = true [dependencies] hashbrown = "0.14.5" -libyaml-safer = "0.1.1" md-5 = { version = "0.10.6", default-features = false } +saphyr-parser = "0.0.2" diff --git a/rsjsonnet-lang/src/program/eval/mod.rs b/rsjsonnet-lang/src/program/eval/mod.rs index 4750f5e..a399f36 100644 --- a/rsjsonnet-lang/src/program/eval/mod.rs +++ b/rsjsonnet-lang/src/program/eval/mod.rs @@ -1999,7 +1999,7 @@ impl<'a> Evaluator<'a> { State::StdParseYaml => { let arg = self.value_stack.pop().unwrap(); let s = self.expect_std_func_arg_string(arg, "parseYaml", 0)?; - match parse_yaml::parse_yaml(self.program, &s, true) { + match parse_yaml::parse_yaml(self.program, &s) { Ok(value) => { self.value_stack.push(value); } diff --git a/rsjsonnet-lang/src/program/eval/parse_yaml.rs b/rsjsonnet-lang/src/program/eval/parse_yaml.rs index 9788308..a6fa7c5 100644 --- a/rsjsonnet-lang/src/program/eval/parse_yaml.rs +++ b/rsjsonnet-lang/src/program/eval/parse_yaml.rs @@ -1,42 +1,52 @@ use std::cell::OnceCell; use std::collections::HashMap; -use super::{ObjectData, ObjectField, Program, ThunkData, ValueData}; +use super::{ArrayData, ObjectData, ObjectField, Program, ThunkData, ValueData}; use crate::ast; use crate::gc::Gc; use crate::interner::InternedStr; pub(crate) enum ParseError { - LibYaml(libyaml_safer::Error), - Stream, + Parser(saphyr_parser::ScanError), EmptyStream, - Anchor, - Alias, Tag, - KeyIsObject(libyaml_safer::Mark), - KeyIsArray(libyaml_safer::Mark), + AnchorContainsItself, + AnchorFromOtherDoc, + KeyIsObject(saphyr_parser::Marker), + KeyIsArray(saphyr_parser::Marker), NumberOverflow, RepeatedFieldName(InternedStr), } -impl From for ParseError { +impl From for ParseError { #[inline] - fn from(e: libyaml_safer::Error) -> Self { - Self::LibYaml(e) + fn from(e: saphyr_parser::ScanError) -> Self { + Self::Parser(e) } } impl std::fmt::Display for ParseError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match *self { - Self::LibYaml(ref e) => write!(f, "{e}"), - Self::Stream => write!(f, "YAML stream not allowed"), + Self::Parser(ref e) => write!(f, "{e}"), Self::EmptyStream => write!(f, "empty stream"), - Self::Anchor => write!(f, "anchors are not allowed"), - Self::Alias => write!(f, "aliases are not allowed"), Self::Tag => write!(f, "tags are not allowed"), - Self::KeyIsObject(ref mark) => write!(f, "{mark}: object key is an object"), - Self::KeyIsArray(ref mark) => write!(f, "{mark}: object key is an array"), + Self::AnchorContainsItself => write!(f, "anchor contains itself"), + Self::AnchorFromOtherDoc => { + write!(f, "alias refers to an anchor from a previous document") + } + Self::KeyIsObject(ref marker) => write!( + f, + "object key is an object at line {} column {}", + marker.line(), + marker.col(), + ), + Self::KeyIsArray(ref marker) => write!( + f, + "object key is an array at line {} column {}", + marker.line(), + marker.col(), + ), Self::NumberOverflow => write!(f, "number overflow"), Self::RepeatedFieldName(ref name) => { write!(f, "repeated field name {:?}", name.value()) @@ -45,17 +55,11 @@ impl std::fmt::Display for ParseError { } } -pub(super) fn parse_yaml( - program: &mut Program, - s: &str, - allow_stream: bool, -) -> Result { - let mut parser = libyaml_safer::Parser::new(); - let mut bytes = s.as_bytes(); - parser.set_input_string(&mut bytes); +pub(super) fn parse_yaml(program: &mut Program, s: &str) -> Result { + let mut parser = saphyr_parser::Parser::new_from_str(s); - let first_event = parser.parse()?; - if let libyaml_safer::EventData::StreamStart { .. } = first_event.data { + let (first_event, _) = parser.next_event().unwrap()?; + if let saphyr_parser::Event::StreamStart = first_event { drop(first_event); } else { unreachable!(); @@ -69,43 +73,28 @@ pub(super) fn parse_yaml( let mut stream_kind = StreamKind::Empty; loop { - let event = parser.parse()?; - match event.data { - libyaml_safer::EventData::StreamEnd => match stream_kind { - StreamKind::Empty => return Err(ParseError::EmptyStream), - StreamKind::Single(value) => return Ok(value), - StreamKind::Stream(items) => { - if items.is_empty() { - return Ok(ValueData::Array(Gc::from(&program.empty_array))); - } else { - return Ok(ValueData::Array(program.gc_alloc(items.into_boxed_slice()))); - } - } - }, - libyaml_safer::EventData::DocumentStart { implicit, .. } => { - if !implicit { - if !allow_stream { - return Err(ParseError::Stream); - } - match stream_kind { - StreamKind::Empty => { - stream_kind = StreamKind::Stream(Vec::new()); - } - StreamKind::Single(single_value) => { + let (event, _) = parser.next_event().unwrap()?; + match event { + saphyr_parser::Event::StreamEnd => break, + saphyr_parser::Event::DocumentStart => { + let value = parse_yaml_document(program, &mut parser)?; + match stream_kind { + StreamKind::Empty => { + let explicit = false; // TODO + if explicit { stream_kind = StreamKind::Stream(vec![ - program.gc_alloc(ThunkData::new_done(single_value)) + program.gc_alloc(ThunkData::new_done(value)) ]); + } else { + stream_kind = StreamKind::Single(value); } - StreamKind::Stream(_) => {} } - } - - let value = parse_yaml_document(program, &mut parser)?; - match stream_kind { - StreamKind::Empty => { - stream_kind = StreamKind::Single(value); + StreamKind::Single(item0) => { + stream_kind = StreamKind::Stream(vec![ + program.gc_alloc(ThunkData::new_done(item0)), + program.gc_alloc(ThunkData::new_done(value)), + ]); } - StreamKind::Single(_) => unreachable!(), StreamKind::Stream(ref mut items) => { items.push(program.gc_alloc(ThunkData::new_done(value))); } @@ -114,80 +103,175 @@ pub(super) fn parse_yaml( _ => unreachable!(), } } + + match stream_kind { + StreamKind::Empty => Err(ParseError::EmptyStream), + StreamKind::Single(value) => { + // FIXME: This is a hack to check if the single YAML document had an explicit + // start (`---`). Remove this once a new version of saphyr-parser is released, + // which would include https://github.com/saphyr-rs/saphyr-parser/pull/5 + let mut explicit_document = false; + let mut scanner = saphyr_parser::scanner::Scanner::new(s.chars()); + while let Some(token) = scanner.next_token().unwrap() { + match token.1 { + saphyr_parser::scanner::TokenType::StreamStart(_) => {} + saphyr_parser::scanner::TokenType::VersionDirective(..) + | saphyr_parser::scanner::TokenType::TagDirective(..) => {} + saphyr_parser::scanner::TokenType::DocumentStart => { + // There is an explicit document start + explicit_document = true; + break; + } + _ => break, + } + } + + if explicit_document { + Ok(ValueData::Array(program.gc_alloc(Box::new([ + program.gc_alloc(ThunkData::new_done(value)), + ])))) + } else { + Ok(value) + } + } + StreamKind::Stream(items) => { + if items.is_empty() { + Ok(ValueData::Array(Gc::from(&program.empty_array))) + } else { + Ok(ValueData::Array(program.gc_alloc(items.into_boxed_slice()))) + } + } + } } -pub(super) fn parse_yaml_document( +enum AnchorValue { + Pending, + Scalar { + style: saphyr_parser::TScalarStyle, + value: String, + }, + Array(Gc), + Object(Gc), +} + +fn parse_yaml_document( program: &mut Program, - parser: &mut libyaml_safer::Parser<'_>, + parser: &mut saphyr_parser::Parser>, ) -> Result { enum StackItem { - Array(Vec>), - Object(HashMap, InternedStr), + Array { + anchor_id: usize, + items: Vec>, + }, + Object { + anchor_id: usize, + fields: HashMap, + current_key: InternedStr, + }, } + let mut anchors = HashMap::new(); let mut stack = Vec::new(); - let mut event = parser.parse()?; + let mut event = parser.next_event().unwrap()?; loop { - let mut value = match event.data { - libyaml_safer::EventData::Alias { .. } => { - return Err(ParseError::Alias); - } - libyaml_safer::EventData::Scalar { - anchor, - tag, - value, - style, - .. - } => { - if anchor.is_some() { - return Err(ParseError::Anchor); + let mut value = match event.0 { + saphyr_parser::Event::Alias(anchor_id) => { + let Some(value) = anchors.get(&anchor_id) else { + return Err(ParseError::AnchorFromOtherDoc); + }; + match value { + AnchorValue::Pending => { + return Err(ParseError::AnchorContainsItself); + } + AnchorValue::Scalar { style, value } => scalar_to_value(*style, value)?, + AnchorValue::Array(array) => ValueData::Array(array.clone()), + AnchorValue::Object(object) => ValueData::Object(object.clone()), } + } + saphyr_parser::Event::Scalar(value, style, anchor_id, tag) => { if tag.is_some() { return Err(ParseError::Tag); } - scalar_to_value(style, &value)? + let program_value = scalar_to_value(style, &value)?; + anchors.insert(anchor_id, AnchorValue::Scalar { style, value }); + + program_value } - libyaml_safer::EventData::SequenceStart { anchor, tag, .. } => { - if anchor.is_some() { - return Err(ParseError::Anchor); - } + saphyr_parser::Event::SequenceStart(anchor_id, tag) => { if tag.is_some() { return Err(ParseError::Tag); } - event = parser.parse()?; - if matches!(event.data, libyaml_safer::EventData::SequenceEnd) { + event = parser.next_event().unwrap()?; + if matches!(event.0, saphyr_parser::Event::SequenceEnd) { + anchors.insert( + anchor_id, + AnchorValue::Array(Gc::from(&program.empty_array)), + ); ValueData::Array(Gc::from(&program.empty_array)) } else { - stack.push(StackItem::Array(Vec::new())); + stack.push(StackItem::Array { + anchor_id, + items: Vec::new(), + }); + anchors.insert(anchor_id, AnchorValue::Pending); continue; } } - libyaml_safer::EventData::MappingStart { anchor, tag, .. } => { - if anchor.is_some() { - return Err(ParseError::Anchor); - } + saphyr_parser::Event::MappingStart(anchor_id, tag) => { if tag.is_some() { return Err(ParseError::Tag); } - event = parser.parse()?; - match event.data { - libyaml_safer::EventData::Alias { .. } => { - return Err(ParseError::Alias); + event = parser.next_event().unwrap()?; + match event.0 { + saphyr_parser::Event::Alias(key_anchor_id) => { + anchors.insert(anchor_id, AnchorValue::Pending); + let Some(value) = anchors.get(&key_anchor_id) else { + return Err(ParseError::AnchorFromOtherDoc); + }; + match value { + AnchorValue::Pending => { + return Err(ParseError::AnchorContainsItself); + } + AnchorValue::Scalar { value, .. } => { + let key = program.str_interner.intern(value); + stack.push(StackItem::Object { + anchor_id, + fields: HashMap::new(), + current_key: key, + }); + event = parser.next_event().unwrap()?; + continue; + } + AnchorValue::Array(_) => { + return Err(ParseError::KeyIsArray(event.1)); + } + AnchorValue::Object(_) => { + return Err(ParseError::KeyIsObject(event.1)); + } + } } - libyaml_safer::EventData::SequenceStart { .. } => { - return Err(ParseError::KeyIsArray(event.start_mark)); + saphyr_parser::Event::SequenceStart { .. } => { + return Err(ParseError::KeyIsArray(event.1)); } - libyaml_safer::EventData::MappingStart { .. } => { - return Err(ParseError::KeyIsObject(event.start_mark)); + saphyr_parser::Event::MappingStart { .. } => { + return Err(ParseError::KeyIsObject(event.1)); } - libyaml_safer::EventData::MappingEnd => { - ValueData::Object(program.gc_alloc(ObjectData::new_simple(HashMap::new()))) + saphyr_parser::Event::MappingEnd => { + let object = program.gc_alloc(ObjectData::new_simple(HashMap::new())); + anchors.insert(anchor_id, AnchorValue::Object(object.clone())); + ValueData::Object(object) } - libyaml_safer::EventData::Scalar { value, .. } => { + saphyr_parser::Event::Scalar(value, style, key_anchor_id, _) => { let key = program.str_interner.intern(&value); - stack.push(StackItem::Object(HashMap::new(), key)); - event = parser.parse()?; + anchors.insert(key_anchor_id, AnchorValue::Scalar { style, value }); + stack.push(StackItem::Object { + anchor_id, + fields: HashMap::new(), + current_key: key, + }); + anchors.insert(anchor_id, AnchorValue::Pending); + event = parser.next_event().unwrap()?; continue; } _ => unreachable!(), @@ -196,21 +280,31 @@ pub(super) fn parse_yaml_document( _ => unreachable!(), }; - event = parser.parse()?; + event = parser.next_event().unwrap()?; loop { if let Some(stack_item) = stack.pop() { match stack_item { - StackItem::Array(mut items) => { + StackItem::Array { + anchor_id, + mut items, + } => { items.push(program.gc_alloc(ThunkData::new_done(value))); - if matches!(event.data, libyaml_safer::EventData::SequenceEnd) { - value = ValueData::Array(program.gc_alloc(items.into_boxed_slice())); - event = parser.parse()?; + if matches!(event.0, saphyr_parser::Event::SequenceEnd) { + let array = program.gc_alloc(items.into_boxed_slice()); + anchors.insert(anchor_id, AnchorValue::Array(array.clone())); + value = ValueData::Array(array); + + event = parser.next_event().unwrap()?; } else { - stack.push(StackItem::Array(items)); + stack.push(StackItem::Array { anchor_id, items }); break; } } - StackItem::Object(mut fields, current_key) => { + StackItem::Object { + anchor_id, + mut fields, + current_key, + } => { match fields.entry(current_key) { std::collections::hash_map::Entry::Vacant(entry) => { entry.insert(ObjectField { @@ -227,26 +321,55 @@ pub(super) fn parse_yaml_document( } } - match event.data { - libyaml_safer::EventData::Alias { .. } => { - return Err(ParseError::Alias); + match event.0 { + saphyr_parser::Event::Alias(key_anchor_id) => { + let Some(value) = anchors.get(&key_anchor_id) else { + return Err(ParseError::AnchorFromOtherDoc); + }; + match value { + AnchorValue::Pending => { + return Err(ParseError::AnchorContainsItself); + } + AnchorValue::Scalar { value, .. } => { + let key = program.str_interner.intern(value); + stack.push(StackItem::Object { + anchor_id, + fields, + current_key: key, + }); + event = parser.next_event().unwrap()?; + break; + } + AnchorValue::Array(_) => { + return Err(ParseError::KeyIsArray(event.1)); + } + AnchorValue::Object(_) => { + return Err(ParseError::KeyIsObject(event.1)); + } + } } - libyaml_safer::EventData::SequenceStart { .. } => { - return Err(ParseError::KeyIsArray(event.start_mark)); + saphyr_parser::Event::SequenceStart(..) => { + return Err(ParseError::KeyIsArray(event.1)); } - libyaml_safer::EventData::MappingStart { .. } => { - return Err(ParseError::KeyIsObject(event.start_mark)); + saphyr_parser::Event::MappingStart(..) => { + return Err(ParseError::KeyIsObject(event.1)); } - libyaml_safer::EventData::MappingEnd => { - value = ValueData::Object( - program.gc_alloc(ObjectData::new_simple(fields)), - ); - event = parser.parse()?; + saphyr_parser::Event::MappingEnd => { + let object = program.gc_alloc(ObjectData::new_simple(fields)); + anchors.insert(anchor_id, AnchorValue::Object(object.clone())); + value = ValueData::Object(object); + + event = parser.next_event().unwrap()?; } - libyaml_safer::EventData::Scalar { value, .. } => { + saphyr_parser::Event::Scalar(value, style, key_anchor_id, _) => { let key = program.str_interner.intern(&value); - stack.push(StackItem::Object(fields, key)); - event = parser.parse()?; + anchors.insert(key_anchor_id, AnchorValue::Scalar { style, value }); + stack.push(StackItem::Object { + anchor_id, + fields, + current_key: key, + }); + event = parser.next_event().unwrap()?; break; } _ => unreachable!(), @@ -254,10 +377,7 @@ pub(super) fn parse_yaml_document( } } } else { - assert!(matches!( - event.data, - libyaml_safer::EventData::DocumentEnd { .. } - )); + assert!(matches!(event.0, saphyr_parser::Event::DocumentEnd)); return Ok(value); } } @@ -265,10 +385,10 @@ pub(super) fn parse_yaml_document( } fn scalar_to_value( - style: libyaml_safer::ScalarStyle, + style: saphyr_parser::TScalarStyle, value: &str, ) -> Result { - if style == libyaml_safer::ScalarStyle::Plain { + if style == saphyr_parser::TScalarStyle::Plain { match value { "null" | "Null" | "NULL" | "~" => Ok(ValueData::Null), "true" | "True" | "TRUE" => Ok(ValueData::Bool(true)), diff --git a/ui-tests/fail/stdlib/parseYaml/alias.jsonnet b/ui-tests/fail/stdlib/parseYaml/alias.jsonnet deleted file mode 100644 index 63a7050..0000000 --- a/ui-tests/fail/stdlib/parseYaml/alias.jsonnet +++ /dev/null @@ -1 +0,0 @@ -std.parseYaml("a: *alias") diff --git a/ui-tests/fail/stdlib/parseYaml/alias.jsonnet.stderr b/ui-tests/fail/stdlib/parseYaml/alias.jsonnet.stderr deleted file mode 100644 index 842d9a7..0000000 --- a/ui-tests/fail/stdlib/parseYaml/alias.jsonnet.stderr +++ /dev/null @@ -1,8 +0,0 @@ -error: failed to parse YAML: aliases are not allowed -note: while evaluating call to `parseYaml` - --> alias.jsonnet:1:1 - | -1 | std.parseYaml("a: *alias") - | -------------------------- -note: during top-level value evaluation - diff --git a/ui-tests/fail/stdlib/parseYaml/alias_key0.jsonnet b/ui-tests/fail/stdlib/parseYaml/alias_key0.jsonnet deleted file mode 100644 index ab07f8c..0000000 --- a/ui-tests/fail/stdlib/parseYaml/alias_key0.jsonnet +++ /dev/null @@ -1 +0,0 @@ -std.parseYaml("*alias: 1") diff --git a/ui-tests/fail/stdlib/parseYaml/alias_key0.jsonnet.stderr b/ui-tests/fail/stdlib/parseYaml/alias_key0.jsonnet.stderr deleted file mode 100644 index 01f28ab..0000000 --- a/ui-tests/fail/stdlib/parseYaml/alias_key0.jsonnet.stderr +++ /dev/null @@ -1,8 +0,0 @@ -error: failed to parse YAML: aliases are not allowed -note: while evaluating call to `parseYaml` - --> alias_key0.jsonnet:1:1 - | -1 | std.parseYaml("*alias: 1") - | -------------------------- -note: during top-level value evaluation - diff --git a/ui-tests/fail/stdlib/parseYaml/alias_key1.jsonnet b/ui-tests/fail/stdlib/parseYaml/alias_key1.jsonnet deleted file mode 100644 index 645d99f..0000000 --- a/ui-tests/fail/stdlib/parseYaml/alias_key1.jsonnet +++ /dev/null @@ -1 +0,0 @@ -std.parseYaml("a: 1\n*alias: 2") diff --git a/ui-tests/fail/stdlib/parseYaml/alias_key1.jsonnet.stderr b/ui-tests/fail/stdlib/parseYaml/alias_key1.jsonnet.stderr deleted file mode 100644 index 6a337ed..0000000 --- a/ui-tests/fail/stdlib/parseYaml/alias_key1.jsonnet.stderr +++ /dev/null @@ -1,8 +0,0 @@ -error: failed to parse YAML: aliases are not allowed -note: while evaluating call to `parseYaml` - --> alias_key1.jsonnet:1:1 - | -1 | std.parseYaml("a: 1\n*alias: 2") - | -------------------------------- -note: during top-level value evaluation - diff --git a/ui-tests/fail/stdlib/parseYaml/anchor.jsonnet b/ui-tests/fail/stdlib/parseYaml/anchor.jsonnet deleted file mode 100644 index c050e2f..0000000 --- a/ui-tests/fail/stdlib/parseYaml/anchor.jsonnet +++ /dev/null @@ -1,6 +0,0 @@ -std.parseYaml(||| - x: &anchor - a: 1 - b: 2 - y: *anchor -|||) diff --git a/ui-tests/fail/stdlib/parseYaml/anchor_itself.jsonnet b/ui-tests/fail/stdlib/parseYaml/anchor_itself.jsonnet new file mode 100644 index 0000000..ed878e6 --- /dev/null +++ b/ui-tests/fail/stdlib/parseYaml/anchor_itself.jsonnet @@ -0,0 +1,3 @@ +std.parseYaml(||| + a: &x *x +|||) diff --git a/ui-tests/fail/stdlib/parseYaml/anchor_itself.jsonnet.stderr b/ui-tests/fail/stdlib/parseYaml/anchor_itself.jsonnet.stderr new file mode 100644 index 0000000..2e65ade --- /dev/null +++ b/ui-tests/fail/stdlib/parseYaml/anchor_itself.jsonnet.stderr @@ -0,0 +1,10 @@ +error: failed to parse YAML: while parsing a block mapping, did not find expected key at byte 6 line 1 column 7 +note: while evaluating call to `parseYaml` + --> anchor_itself.jsonnet:1:1 + | +1 | / std.parseYaml(||| + : | +3 | | |||) + | |____- +note: during top-level value evaluation + diff --git a/ui-tests/fail/stdlib/parseYaml/anchor_itself_array.jsonnet b/ui-tests/fail/stdlib/parseYaml/anchor_itself_array.jsonnet new file mode 100644 index 0000000..5bebb67 --- /dev/null +++ b/ui-tests/fail/stdlib/parseYaml/anchor_itself_array.jsonnet @@ -0,0 +1,3 @@ +std.parseYaml(||| + a: &x [*x] +|||) diff --git a/ui-tests/fail/stdlib/parseYaml/anchor.jsonnet.stderr b/ui-tests/fail/stdlib/parseYaml/anchor_itself_array.jsonnet.stderr similarity index 56% rename from ui-tests/fail/stdlib/parseYaml/anchor.jsonnet.stderr rename to ui-tests/fail/stdlib/parseYaml/anchor_itself_array.jsonnet.stderr index b534e68..69f1ef4 100644 --- a/ui-tests/fail/stdlib/parseYaml/anchor.jsonnet.stderr +++ b/ui-tests/fail/stdlib/parseYaml/anchor_itself_array.jsonnet.stderr @@ -1,10 +1,10 @@ -error: failed to parse YAML: anchors are not allowed +error: failed to parse YAML: anchor contains itself note: while evaluating call to `parseYaml` - --> anchor.jsonnet:1:1 + --> anchor_itself_array.jsonnet:1:1 | 1 | / std.parseYaml(||| : | -6 | | |||) +3 | | |||) | |____- note: during top-level value evaluation diff --git a/ui-tests/fail/stdlib/parseYaml/anchor_itself_object.jsonnet b/ui-tests/fail/stdlib/parseYaml/anchor_itself_object.jsonnet new file mode 100644 index 0000000..c3b060a --- /dev/null +++ b/ui-tests/fail/stdlib/parseYaml/anchor_itself_object.jsonnet @@ -0,0 +1,3 @@ +std.parseYaml(||| + a: &x {a: *x} +|||) diff --git a/ui-tests/fail/stdlib/parseYaml/anchor_itself_object.jsonnet.stderr b/ui-tests/fail/stdlib/parseYaml/anchor_itself_object.jsonnet.stderr new file mode 100644 index 0000000..9c439aa --- /dev/null +++ b/ui-tests/fail/stdlib/parseYaml/anchor_itself_object.jsonnet.stderr @@ -0,0 +1,10 @@ +error: failed to parse YAML: anchor contains itself +note: while evaluating call to `parseYaml` + --> anchor_itself_object.jsonnet:1:1 + | +1 | / std.parseYaml(||| + : | +3 | | |||) + | |____- +note: during top-level value evaluation + diff --git a/ui-tests/fail/stdlib/parseYaml/anchor_itself_object_key0.jsonnet b/ui-tests/fail/stdlib/parseYaml/anchor_itself_object_key0.jsonnet new file mode 100644 index 0000000..695f0ad --- /dev/null +++ b/ui-tests/fail/stdlib/parseYaml/anchor_itself_object_key0.jsonnet @@ -0,0 +1,3 @@ +std.parseYaml(||| + a: &x {*x : 1} +|||) diff --git a/ui-tests/fail/stdlib/parseYaml/anchor_itself_object_key0.jsonnet.stderr b/ui-tests/fail/stdlib/parseYaml/anchor_itself_object_key0.jsonnet.stderr new file mode 100644 index 0000000..c1b44be --- /dev/null +++ b/ui-tests/fail/stdlib/parseYaml/anchor_itself_object_key0.jsonnet.stderr @@ -0,0 +1,10 @@ +error: failed to parse YAML: anchor contains itself +note: while evaluating call to `parseYaml` + --> anchor_itself_object_key0.jsonnet:1:1 + | +1 | / std.parseYaml(||| + : | +3 | | |||) + | |____- +note: during top-level value evaluation + diff --git a/ui-tests/fail/stdlib/parseYaml/anchor_itself_object_key1.jsonnet b/ui-tests/fail/stdlib/parseYaml/anchor_itself_object_key1.jsonnet new file mode 100644 index 0000000..77fc60d --- /dev/null +++ b/ui-tests/fail/stdlib/parseYaml/anchor_itself_object_key1.jsonnet @@ -0,0 +1,3 @@ +std.parseYaml(||| + a: &x {b: 1, *x : 2} +|||) diff --git a/ui-tests/fail/stdlib/parseYaml/anchor_itself_object_key1.jsonnet.stderr b/ui-tests/fail/stdlib/parseYaml/anchor_itself_object_key1.jsonnet.stderr new file mode 100644 index 0000000..47e97b4 --- /dev/null +++ b/ui-tests/fail/stdlib/parseYaml/anchor_itself_object_key1.jsonnet.stderr @@ -0,0 +1,10 @@ +error: failed to parse YAML: anchor contains itself +note: while evaluating call to `parseYaml` + --> anchor_itself_object_key1.jsonnet:1:1 + | +1 | / std.parseYaml(||| + : | +3 | | |||) + | |____- +note: during top-level value evaluation + diff --git a/ui-tests/fail/stdlib/parseYaml/cross_document_anchor.jsonnet b/ui-tests/fail/stdlib/parseYaml/cross_document_anchor.jsonnet new file mode 100644 index 0000000..d60a1d5 --- /dev/null +++ b/ui-tests/fail/stdlib/parseYaml/cross_document_anchor.jsonnet @@ -0,0 +1,5 @@ +std.parseYaml(||| + a: &alias 1 + --- + b: *alias +|||) diff --git a/ui-tests/fail/stdlib/parseYaml/cross_document_anchor.jsonnet.stderr b/ui-tests/fail/stdlib/parseYaml/cross_document_anchor.jsonnet.stderr new file mode 100644 index 0000000..5a7242f --- /dev/null +++ b/ui-tests/fail/stdlib/parseYaml/cross_document_anchor.jsonnet.stderr @@ -0,0 +1,10 @@ +error: failed to parse YAML: alias refers to an anchor from a previous document +note: while evaluating call to `parseYaml` + --> cross_document_anchor.jsonnet:1:1 + | +1 | / std.parseYaml(||| + : | +5 | | |||) + | |____- +note: during top-level value evaluation + diff --git a/ui-tests/fail/stdlib/parseYaml/cross_document_anchor_in_key0.jsonnet b/ui-tests/fail/stdlib/parseYaml/cross_document_anchor_in_key0.jsonnet new file mode 100644 index 0000000..452bf55 --- /dev/null +++ b/ui-tests/fail/stdlib/parseYaml/cross_document_anchor_in_key0.jsonnet @@ -0,0 +1,5 @@ +std.parseYaml(||| + a: &alias 1 + --- + *alias : 2 +|||) diff --git a/ui-tests/fail/stdlib/parseYaml/cross_document_anchor_in_key0.jsonnet.stderr b/ui-tests/fail/stdlib/parseYaml/cross_document_anchor_in_key0.jsonnet.stderr new file mode 100644 index 0000000..f953e27 --- /dev/null +++ b/ui-tests/fail/stdlib/parseYaml/cross_document_anchor_in_key0.jsonnet.stderr @@ -0,0 +1,10 @@ +error: failed to parse YAML: alias refers to an anchor from a previous document +note: while evaluating call to `parseYaml` + --> cross_document_anchor_in_key0.jsonnet:1:1 + | +1 | / std.parseYaml(||| + : | +5 | | |||) + | |____- +note: during top-level value evaluation + diff --git a/ui-tests/fail/stdlib/parseYaml/cross_document_anchor_in_key1.jsonnet b/ui-tests/fail/stdlib/parseYaml/cross_document_anchor_in_key1.jsonnet new file mode 100644 index 0000000..7e90f23 --- /dev/null +++ b/ui-tests/fail/stdlib/parseYaml/cross_document_anchor_in_key1.jsonnet @@ -0,0 +1,6 @@ +std.parseYaml(||| + a: &alias 1 + --- + b: 2 + *alias : 3 +|||) diff --git a/ui-tests/fail/stdlib/parseYaml/cross_document_anchor_in_key1.jsonnet.stderr b/ui-tests/fail/stdlib/parseYaml/cross_document_anchor_in_key1.jsonnet.stderr new file mode 100644 index 0000000..a2da775 --- /dev/null +++ b/ui-tests/fail/stdlib/parseYaml/cross_document_anchor_in_key1.jsonnet.stderr @@ -0,0 +1,10 @@ +error: failed to parse YAML: alias refers to an anchor from a previous document +note: while evaluating call to `parseYaml` + --> cross_document_anchor_in_key1.jsonnet:1:1 + | +1 | / std.parseYaml(||| + : | +6 | | |||) + | |____- +note: during top-level value evaluation + diff --git a/ui-tests/fail/stdlib/parseYaml/key0_is_array.jsonnet.stderr b/ui-tests/fail/stdlib/parseYaml/key0_is_array.jsonnet.stderr index 77253db..7e419b8 100644 --- a/ui-tests/fail/stdlib/parseYaml/key0_is_array.jsonnet.stderr +++ b/ui-tests/fail/stdlib/parseYaml/key0_is_array.jsonnet.stderr @@ -1,4 +1,4 @@ -error: failed to parse YAML: line 0 column 1: object key is an array +error: failed to parse YAML: object key is an array at line 1 column 1 note: while evaluating call to `parseYaml` --> key0_is_array.jsonnet:1:1 | diff --git a/ui-tests/fail/stdlib/parseYaml/key0_is_object.jsonnet.stderr b/ui-tests/fail/stdlib/parseYaml/key0_is_object.jsonnet.stderr index 66f2403..a55e974 100644 --- a/ui-tests/fail/stdlib/parseYaml/key0_is_object.jsonnet.stderr +++ b/ui-tests/fail/stdlib/parseYaml/key0_is_object.jsonnet.stderr @@ -1,4 +1,4 @@ -error: failed to parse YAML: line 0 column 1: object key is an object +error: failed to parse YAML: object key is an object at line 1 column 1 note: while evaluating call to `parseYaml` --> key0_is_object.jsonnet:1:1 | diff --git a/ui-tests/fail/stdlib/parseYaml/key1_is_array.jsonnet.stderr b/ui-tests/fail/stdlib/parseYaml/key1_is_array.jsonnet.stderr index 76d874e..58ac75b 100644 --- a/ui-tests/fail/stdlib/parseYaml/key1_is_array.jsonnet.stderr +++ b/ui-tests/fail/stdlib/parseYaml/key1_is_array.jsonnet.stderr @@ -1,4 +1,4 @@ -error: failed to parse YAML: line 0 column 7: object key is an array +error: failed to parse YAML: object key is an array at line 1 column 7 note: while evaluating call to `parseYaml` --> key1_is_array.jsonnet:1:1 | diff --git a/ui-tests/fail/stdlib/parseYaml/key1_is_object.jsonnet.stderr b/ui-tests/fail/stdlib/parseYaml/key1_is_object.jsonnet.stderr index b0a1455..2248c74 100644 --- a/ui-tests/fail/stdlib/parseYaml/key1_is_object.jsonnet.stderr +++ b/ui-tests/fail/stdlib/parseYaml/key1_is_object.jsonnet.stderr @@ -1,4 +1,4 @@ -error: failed to parse YAML: line 0 column 7: object key is an object +error: failed to parse YAML: object key is an object at line 1 column 7 note: while evaluating call to `parseYaml` --> key1_is_object.jsonnet:1:1 | diff --git a/ui-tests/fail/stdlib/parseYaml/syntax.jsonnet.stderr b/ui-tests/fail/stdlib/parseYaml/syntax.jsonnet.stderr index 1136680..a2bb0f7 100644 --- a/ui-tests/fail/stdlib/parseYaml/syntax.jsonnet.stderr +++ b/ui-tests/fail/stdlib/parseYaml/syntax.jsonnet.stderr @@ -1,4 +1,4 @@ -error: failed to parse YAML: Parser error: line 1 column 0: did not find expected node content while parsing a flow node (line 1 column 0) +error: failed to parse YAML: while parsing a node, did not find expected node content at byte 7 line 2 column 1 note: while evaluating call to `parseYaml` --> syntax.jsonnet:1:1 | diff --git a/ui-tests/fuzz/stdlib/parseYaml/fuzz_1.jsonnet b/ui-tests/fuzz/stdlib/parseYaml/fuzz_1.jsonnet new file mode 100644 index 0000000..fa6f33d --- /dev/null +++ b/ui-tests/fuzz/stdlib/parseYaml/fuzz_1.jsonnet @@ -0,0 +1 @@ +std.parseYaml("a: ''|\n 0") diff --git a/ui-tests/fuzz/stdlib/parseYaml/fuzz_1.jsonnet.stderr b/ui-tests/fuzz/stdlib/parseYaml/fuzz_1.jsonnet.stderr new file mode 100644 index 0000000..a540780 --- /dev/null +++ b/ui-tests/fuzz/stdlib/parseYaml/fuzz_1.jsonnet.stderr @@ -0,0 +1,8 @@ +error: failed to parse YAML: invalid trailing content after double-quoted scalar at byte 5 line 1 column 6 +note: while evaluating call to `parseYaml` + --> fuzz_1.jsonnet:1:1 + | +1 | std.parseYaml("a: ''|\n 0") + | --------------------------- +note: during top-level value evaluation + diff --git a/ui-tests/fuzz/stdlib/parseYaml/fuzz_2.jsonnet b/ui-tests/fuzz/stdlib/parseYaml/fuzz_2.jsonnet new file mode 100644 index 0000000..7cfefa6 --- /dev/null +++ b/ui-tests/fuzz/stdlib/parseYaml/fuzz_2.jsonnet @@ -0,0 +1 @@ +std.parseYaml("[!,") diff --git a/ui-tests/fuzz/stdlib/parseYaml/fuzz_2.jsonnet.stderr b/ui-tests/fuzz/stdlib/parseYaml/fuzz_2.jsonnet.stderr new file mode 100644 index 0000000..edbca20 --- /dev/null +++ b/ui-tests/fuzz/stdlib/parseYaml/fuzz_2.jsonnet.stderr @@ -0,0 +1,8 @@ +error: failed to parse YAML: tags are not allowed +note: while evaluating call to `parseYaml` + --> fuzz_2.jsonnet:1:1 + | +1 | std.parseYaml("[!,") + | -------------------- +note: during top-level value evaluation + diff --git a/ui-tests/fuzz/stdlib/parseYaml/fuzz_3.jsonnet b/ui-tests/fuzz/stdlib/parseYaml/fuzz_3.jsonnet new file mode 100644 index 0000000..1bf4a4c --- /dev/null +++ b/ui-tests/fuzz/stdlib/parseYaml/fuzz_3.jsonnet @@ -0,0 +1 @@ +std.parseYaml(">\n 0") diff --git a/ui-tests/fuzz/stdlib/parseYaml/fuzz_3.jsonnet.stdout b/ui-tests/fuzz/stdlib/parseYaml/fuzz_3.jsonnet.stdout new file mode 100644 index 0000000..796109b --- /dev/null +++ b/ui-tests/fuzz/stdlib/parseYaml/fuzz_3.jsonnet.stdout @@ -0,0 +1 @@ +"0\n" diff --git a/ui-tests/fuzz/stdlib/parseYaml/fuzz_4.jsonnet b/ui-tests/fuzz/stdlib/parseYaml/fuzz_4.jsonnet new file mode 100644 index 0000000..18a2332 --- /dev/null +++ b/ui-tests/fuzz/stdlib/parseYaml/fuzz_4.jsonnet @@ -0,0 +1 @@ +std.parseYaml("[!<0>,") diff --git a/ui-tests/fuzz/stdlib/parseYaml/fuzz_4.jsonnet.stderr b/ui-tests/fuzz/stdlib/parseYaml/fuzz_4.jsonnet.stderr new file mode 100644 index 0000000..24365c7 --- /dev/null +++ b/ui-tests/fuzz/stdlib/parseYaml/fuzz_4.jsonnet.stderr @@ -0,0 +1,8 @@ +error: failed to parse YAML: tags are not allowed +note: while evaluating call to `parseYaml` + --> fuzz_4.jsonnet:1:1 + | +1 | std.parseYaml("[!<0>,") + | ----------------------- +note: during top-level value evaluation + diff --git a/ui-tests/fuzz/stdlib/parseYaml/fuzz_5.jsonnet b/ui-tests/fuzz/stdlib/parseYaml/fuzz_5.jsonnet new file mode 100644 index 0000000..87768ea --- /dev/null +++ b/ui-tests/fuzz/stdlib/parseYaml/fuzz_5.jsonnet @@ -0,0 +1 @@ +std.parseYaml("{!,") diff --git a/ui-tests/fuzz/stdlib/parseYaml/fuzz_5.jsonnet.stderr b/ui-tests/fuzz/stdlib/parseYaml/fuzz_5.jsonnet.stderr new file mode 100644 index 0000000..19660f5 --- /dev/null +++ b/ui-tests/fuzz/stdlib/parseYaml/fuzz_5.jsonnet.stderr @@ -0,0 +1,8 @@ +error: failed to parse YAML: while parsing a node, did not find expected node content at byte 3 line 2 column 1 +note: while evaluating call to `parseYaml` + --> fuzz_5.jsonnet:1:1 + | +1 | std.parseYaml("{!,") + | -------------------- +note: during top-level value evaluation + diff --git a/ui-tests/pass/stdlib/parseYaml.jsonnet b/ui-tests/pass/stdlib/parseYaml.jsonnet index dc2330e..0657ffb 100644 --- a/ui-tests/pass/stdlib/parseYaml.jsonnet +++ b/ui-tests/pass/stdlib/parseYaml.jsonnet @@ -184,6 +184,57 @@ std.assertEqual( }, ) && +std.assertEqual( + std.parseYaml( + ||| + --- + ||| + ), + [ + null, + ], +) && + +std.assertEqual( + std.parseYaml( + ||| + --- + --- + ||| + ), + [ + null, + null, + ], +) && + +std.assertEqual( + std.parseYaml( + ||| + --- + - 1 + - 2 + ||| + ), + [ + [1, 2], + ], +) && + +std.assertEqual( + std.parseYaml( + ||| + - 1 + - 2 + --- + ||| + ), + [ + [1, 2], + null, + ], +) && + std.assertEqual( std.parseYaml( ||| @@ -252,4 +303,68 @@ std.assertEqual( ], ) && +std.assertEqual( + std.parseYaml( + ||| + a: &x 1 + b: *x + ||| + ), + { a: 1, b: 1 }, +) && + +std.assertEqual( + std.parseYaml( + ||| + a: &x [1, 2] + b: *x + ||| + ), + { a: [1, 2], b: [1, 2] }, +) && + +std.assertEqual( + std.parseYaml( + ||| + a: &x { c: 1, d: 2 } + b: *x + ||| + ), + { a: { c: 1, d: 2 }, b: { c: 1, d: 2 } }, +) && + +std.assertEqual( + std.parseYaml( + ||| + &x a: 1 + b: *x + ||| + ), + { a: 1, b: "a" }, +) && + +std.assertEqual( + std.parseYaml( + ||| + a: &x 1 + *x : 2 + ||| + ), + { a: 1, "1": 2 }, +) && + +std.assertEqual( + std.parseYaml( + ||| + a: &x 1 + b: &y 2 + c: &z + *x : *y + *y : *x + d: *z + ||| + ), + { a: 1, b: 2, c: { "1": 2, "2": 1 }, d: { "1": 2, "2": 1 } }, +) && + true