From c81c1d3ee1f95fb352d58ac400431aa3816b17fb Mon Sep 17 00:00:00 2001 From: Gargan Date: Mon, 20 Jul 2026 20:10:12 -0400 Subject: [PATCH] Fix for YAML 1.1 multi-document directive inheritance fixes [#341](https://github.com/yaml/libyaml/issues/341) The [YAML 1.1](http://yaml.org/spec/1.1/#l-first-document) specification says: > If the document specifies no directives, it is parsed using the same settings as the previous document. If the document does specify any directives, all directives of previous documents, if any, are ignored. This is a simple update so that if no directives are present in the current document, then the previously declared directives are used instead. --- src/parser.c | 18 ++++++--- tests/CMakeLists.txt | 2 + tests/test-directive-inheritance.c | 59 ++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 tests/test-directive-inheritance.c diff --git a/src/parser.c b/src/parser.c index 10770936..9ae6a1bb 100644 --- a/src/parser.c +++ b/src/parser.c @@ -491,12 +491,6 @@ yaml_parser_parse_document_end(yaml_parser_t *parser, yaml_event_t *event) implicit = 0; } - while (!STACK_EMPTY(parser, parser->tag_directives)) { - yaml_tag_directive_t tag_directive = POP(parser, parser->tag_directives); - yaml_free(tag_directive.handle); - yaml_free(tag_directive.prefix); - } - parser->state = YAML_PARSE_DOCUMENT_START_STATE; DOCUMENT_END_EVENT_INIT(*event, implicit, start_mark, end_mark); @@ -1250,6 +1244,7 @@ yaml_parser_process_directives(yaml_parser_t *parser, }; yaml_tag_directive_t *default_tag_directive; yaml_version_directive_t *version_directive = NULL; + int first_directive = 1; struct { yaml_tag_directive_t *start; yaml_tag_directive_t *end; @@ -1266,6 +1261,17 @@ yaml_parser_process_directives(yaml_parser_t *parser, while (token->type == YAML_VERSION_DIRECTIVE_TOKEN || token->type == YAML_TAG_DIRECTIVE_TOKEN) { + if (first_directive) { + /* First directive seen: this document has its own directives, + * so discard any inherited from the previous document. */ + while (!STACK_EMPTY(parser, parser->tag_directives)) { + yaml_tag_directive_t tag_directive = POP(parser, parser->tag_directives); + yaml_free(tag_directive.handle); + yaml_free(tag_directive.prefix); + } + first_directive = 0; + } + if (token->type == YAML_VERSION_DIRECTIVE_TOKEN) { if (version_directive) { yaml_parser_set_parser_error(parser, diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 17a9c671..97010eae 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -19,10 +19,12 @@ foreach(name IN ITEMS test-nesting test-reader test-version + test-directive-inheritance ) add_yaml_executable(${name}) endforeach() +add_test(NAME directive-inheritance COMMAND test-directive-inheritance) add_test(NAME version COMMAND test-version) add_test(NAME reader COMMAND test-reader) add_test(NAME nesting COMMAND test-nesting) diff --git a/tests/test-directive-inheritance.c b/tests/test-directive-inheritance.c new file mode 100644 index 00000000..399b2420 --- /dev/null +++ b/tests/test-directive-inheritance.c @@ -0,0 +1,59 @@ +#include + +#include +#include +#include + +#ifdef NDEBUG +#undef NDEBUG +#endif +#include + +/* + * Test: %TAG directive inheritance across documents in a multi-document stream. + * + * YAML 1.1 spec section 7.4: + * "If the document specifies no directives, it is parsed using the same + * settings as the previous document. If the document does specify any + * directives, all directives of previous documents, if any, are ignored." + * + * libyaml bug: yaml_parser_parse_document_end() unconditionally clears + * parser->tag_directives, so document 2 (which has no directives) cannot + * inherit the %TAG mapping from document 1. When the parser then encounters + * !x!bar in document 2 it cannot resolve the handle and throws a parse + * error. The assert inside the loop will FAIL until the bug is fixed. + */ + +static const unsigned char yaml_input[] = + "%YAML 1.1\n" + "%TAG !x! tag:example.com,2026:\n" + "--- !x!food\n" + "x: 0\n" + "--- !x!bar\n" + "x: 1\n"; + +int main(void) +{ + yaml_parser_t parser; + yaml_event_t event; + int done = 0; + + assert(yaml_parser_initialize(&parser)); + yaml_parser_set_input_string(&parser, yaml_input, sizeof(yaml_input) - 1); + + printf("Testing Directive Inheritance ... "); + fflush(stdout); + + while (!done) { + /* With the bug, this assert fires when the parser hits !x!bar + * in doc 2 and cannot resolve the handle. A spec-compliant + * parser must succeed for the entire stream. */ + assert(yaml_parser_parse(&parser, &event)); + done = (event.type == YAML_STREAM_END_EVENT); + yaml_event_delete(&event); + } + + yaml_parser_delete(&parser); + printf("PASSED\n"); + return 0; +} \ No newline at end of file