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)
+               }
+       }
+}

Reply via email to