This is an automated email from the ASF dual-hosted git repository. sjanc pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/mynewt-newt.git
commit f1b78333ba9da8b8866618e7b1361c4acf85119b Author: Szymon Janc <[email protected]> AuthorDate: Mon Oct 19 17:52:07 2026 +0200 yaml: Reject configuration files without top-level mapping A YAML file whose top-level element is a sequence or a scalar was silently decoded as an empty mapping, so e.g. a syscfg.yml written as a list was ignored. Report an error instead; empty documents are still accepted. Assisted-by: Claude:claude-opus-5-5 --- yaml/decode.go | 13 ++++++++++++ yaml/toplevel_test.go | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/yaml/decode.go b/yaml/decode.go index 46588fe2..2661b62e 100644 --- a/yaml/decode.go +++ b/yaml/decode.go @@ -296,6 +296,19 @@ func DecodeStream(b []byte, values map[string]interface{}) error { case CTXT_STATE_DONE: return nil + case CTXT_STATE_SEQUENCE: + return decodeError(&parser, + "top-level element must be a mapping, not a sequence") + + case CTXT_STATE_SCALAR: + // An empty document is decoded as a null scalar. + switch stringValue(subCtxt.value) { + case "", "~", "null", "Null", "NULL": + default: + return decodeError(&parser, + "top-level element must be a mapping, not a scalar") + } + default: // Unneeded metadata; proceed to next event. break diff --git a/yaml/toplevel_test.go b/yaml/toplevel_test.go new file mode 100644 index 00000000..993dc6b6 --- /dev/null +++ b/yaml/toplevel_test.go @@ -0,0 +1,56 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package yaml + +import "testing" + +// A top-level element which is not a mapping must be rejected instead of +// being silently decoded as an empty mapping. +func TestUnmarshalTopLevel(t *testing.T) { + bad := []string{ + "- a\n- b\n", + "foo\n", + "5\n", + "---\nfoo\n", + } + for _, src := range bad { + m := map[string]interface{}{} + if err := Unmarshal([]byte(src), m); err == nil { + t.Errorf("%q: no error; decoded %v", src, m) + } + } + + good := map[string]int{ + "": 0, + "# comment\n": 0, + "---\n": 0, + "~\n": 0, + "a: 1\n": 1, + "---\na: 1\n": 1, + } + for src, n := range good { + m := map[string]interface{}{} + if err := Unmarshal([]byte(src), m); err != nil { + t.Errorf("%q: unexpected error: %s", src, err) + } else if len(m) != n { + t.Errorf("%q: decoded %v", src, m) + } + } +}
