Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,19 @@ var unmarshalTests = []struct {
&struct{ B []int }{[]int{1, 2}},
},

// Bug https://github.com/yaml/go-yaml/issues/109
{
// alias must be followed by a space in mapping node
"foo: &bar bar\n*bar : quz\n",
map[string]any{"foo": "bar", "bar": "quz"},
},

{
// alias can contain various characters specified by the YAML specification
"foo: &b./ar bar\n*b./ar : quz\n",
map[string]any{"foo": "bar", "bar": "quz"},
},

// Bug #1133337
{
"foo: ''",
Expand Down Expand Up @@ -1111,6 +1124,9 @@ var unmarshalErrorTests = []struct {
{"a: 1\nb: 2\nc 2\nd: 3\n", "^yaml: line 3: could not find expected ':'$"},
{"#\n-\n{", "yaml: line 3: could not find expected ':'"}, // Issue #665
{"0: [:!00 \xef", "yaml: incomplete UTF-8 octet sequence"}, // Issue #666
// anchor cannot contain a colon
// https://github.com/yaml/go-yaml/issues/109
{"foo: &bar: bar\n*bar: : quz\n", "^yaml: mapping values are not allowed in this context$"},
{
"a: &a [00,00,00,00,00,00,00,00,00]\n" +
"b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a]\n" +
Expand Down
11 changes: 10 additions & 1 deletion emitterc.go
Original file line number Diff line number Diff line change
Expand Up @@ -813,7 +813,16 @@ func (emitter *yamlEmitter) emitBlockMappingKey(event *yamlEvent, first bool) bo
}
if emitter.checkSimpleKey() {
emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE)
return emitter.emitNode(event, false, false, true, true)
if !emitter.emitNode(event, false, false, true, true) {
return false
}

if event.typ == yaml_ALIAS_EVENT {
// make sure there's a space after the alias
return emitter.put(' ')
}

return true
}
if !emitter.writeIndicator([]byte{'?'}, true, false, true) {
return false
Expand Down
209 changes: 163 additions & 46 deletions node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"fmt"
"io"
"os"
"reflect"
"strings"
"testing"

Expand Down Expand Up @@ -1129,7 +1130,7 @@ var nodeTests = []struct {
}},
},
}, {
"a: &anchor(.!@#$%^&*+=?:;)name [1, 2]\nb: *anchor(.!@#$%^&*+=?:;)name\n",
"a: &anchor(.!@#$%^&*+=?;)name [1, 2]\nb: *anchor(.!@#$%^&*+=?;)name\n",
yaml.Node{
Kind: yaml.DocumentNode,
Line: 1,
Expand All @@ -1147,25 +1148,25 @@ var nodeTests = []struct {
Line: 1,
Column: 1,
},
saveNode("anchor(.!@#$%^&*+=?:;)name", &yaml.Node{
saveNode("anchor(.!@#$%^&*+=?;)name", &yaml.Node{
Kind: yaml.SequenceNode,
Style: yaml.FlowStyle,
Tag: "!!seq",
Anchor: "anchor(.!@#$%^&*+=?:;)name",
Anchor: "anchor(.!@#$%^&*+=?;)name",
Line: 1,
Column: 4,
Content: []*yaml.Node{{
Kind: yaml.ScalarNode,
Value: "1",
Tag: "!!int",
Line: 1,
Column: 33,
Column: 32,
}, {
Kind: yaml.ScalarNode,
Value: "2",
Tag: "!!int",
Line: 1,
Column: 36,
Column: 35,
}},
}),
{
Expand All @@ -1177,14 +1178,58 @@ var nodeTests = []struct {
},
{
Kind: yaml.AliasNode,
Value: "anchor(.!@#$%^&*+=?:;)name",
Alias: dropNode("anchor(.!@#$%^&*+=?:;)name"),
Value: "anchor(.!@#$%^&*+=?;)name",
Alias: dropNode("anchor(.!@#$%^&*+=?;)name"),
Line: 2,
Column: 4,
},
},
}},
},
}, {
"a: &x 1\n*x : c\n",
yaml.Node{
Kind: yaml.DocumentNode,
Line: 1,
Column: 1,
Content: []*yaml.Node{{
Kind: yaml.MappingNode,
Line: 1,
Column: 1,
Tag: "!!map",
Content: []*yaml.Node{
{
Kind: yaml.ScalarNode,
Value: "a",
Tag: "!!str",
Line: 1,
Column: 1,
},
saveNode("x", &yaml.Node{
Kind: yaml.ScalarNode,
Value: "1",
Tag: "!!int",
Anchor: "x",
Line: 1,
Column: 4,
}),
{
Kind: yaml.AliasNode,
Value: "x",
Alias: dropNode("x"),
Line: 2,
Column: 1,
},
{
Kind: yaml.ScalarNode,
Value: "c",
Tag: "!!str",
Line: 2,
Column: 6,
},
},
}},
},
}, {
"# One\n# Two\ntrue # Three\n# Four\n# Five\n",
yaml.Node{
Expand Down Expand Up @@ -2756,53 +2801,125 @@ func TestNodeRoundtrip(t *testing.T) {
defer os.Setenv("TZ", os.Getenv("TZ"))
os.Setenv("TZ", "UTC")
for i, item := range nodeTests {
t.Logf("test %d: %q", i, item.yaml)
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
t.Logf("test %d: %q", i, item.yaml)
if strings.Contains(item.yaml, "#") {
var buf bytes.Buffer
fprintComments(&buf, &item.node, " ")
t.Logf(" expected comments:\n%s", buf.Bytes())
}

if strings.Contains(item.yaml, "#") {
var buf bytes.Buffer
fprintComments(&buf, &item.node, " ")
t.Logf(" expected comments:\n%s", buf.Bytes())
}
decode := true
encode := true

decode := true
encode := true
testYaml := item.yaml
if s := strings.TrimPrefix(testYaml, "[decode]"); s != testYaml {
encode = false
testYaml = s
}
if s := strings.TrimPrefix(testYaml, "[encode]"); s != testYaml {
decode = false
testYaml = s
}

testYaml := item.yaml
if s := strings.TrimPrefix(testYaml, "[decode]"); s != testYaml {
encode = false
testYaml = s
}
if s := strings.TrimPrefix(testYaml, "[encode]"); s != testYaml {
decode = false
testYaml = s
}
if decode {
var node yaml.Node
err := yaml.Unmarshal([]byte(testYaml), &node)
assert.NoError(t, err)
if strings.Contains(item.yaml, "#") {
var buf bytes.Buffer
fprintComments(&buf, &node, " ")
t.Logf(" obtained comments:\n%s", buf.Bytes())
}

if decode {
var node yaml.Node
err := yaml.Unmarshal([]byte(testYaml), &node)
assert.NoError(t, err)
if strings.Contains(item.yaml, "#") {
var buf bytes.Buffer
fprintComments(&buf, &node, " ")
t.Logf(" obtained comments:\n%s", buf.Bytes())
assertNodeEqual(t, &item.node, &node)
}
assert.DeepEqual(t, &item.node, &node)
if encode {
node := deepCopyNode(&item.node, nil)
buf := bytes.Buffer{}
enc := yaml.NewEncoder(&buf)
enc.SetIndent(2)
err := enc.Encode(node)
assert.NoError(t, err)
err = enc.Close()
assert.NoError(t, err)
assert.Equal(t, buf.String(), testYaml)

// Ensure there were no mutations to the tree.
assertNodeEqual(t, &item.node, node)
}
})
}
}

// assertNodeEqual is a helper to check whether two YAML nodes are equal.
func assertNodeEqual(t *testing.T, want *yaml.Node, got *yaml.Node) {
t.Helper()

if reflect.DeepEqual(got, want) {
// fast path
return
}

if got.Tag != want.Tag {
t.Errorf("Tag mismatch: want: %q got: %q", want.Tag, got.Tag)
}

if got.Kind != want.Kind {
t.Errorf("Kind mismatch: want: %q got: %q", want.Kind, got.Kind)
}

if got.Style != want.Style {
t.Errorf("Style mismatch: want: %q got: %q", want.Style, got.Style)
}

if got.HeadComment != want.HeadComment {
t.Errorf("HeadComment mismatch: want: %#v got: %#v", want.HeadComment, got.HeadComment)
}

if got.LineComment != want.LineComment {
t.Errorf("LineComment mismatch: want: %#v got: %#v", want.LineComment, got.LineComment)
}

if got.FootComment != want.FootComment {
t.Errorf("FootComment mismatch: want: %#v got: %#v", want.FootComment, got.FootComment)
}

if got.Value != want.Value {
t.Errorf("Value mismatch: want: %q got: %q", want.Value, got.Value)
}

if got.Anchor != want.Anchor {
t.Errorf("Anchor mismatch: want: %q got: %q", want.Anchor, got.Anchor)
}

if got.Line != want.Line {
t.Errorf("Line mismatch: want: %d got: %d", want.Line, got.Line)
}

if got.Column != want.Column {
t.Errorf("Column mismatch: want: %d got: %d", want.Column, got.Column)
}

if !reflect.DeepEqual(got.Content, want.Content) {
// Content differs

if len(got.Content) != len(want.Content) {
t.Errorf("Content length mismatch:\nwant: %d\ngot: %d", len(want.Content), len(got.Content))
}
if encode {
node := deepCopyNode(&item.node, nil)
buf := bytes.Buffer{}
enc := yaml.NewEncoder(&buf)
enc.SetIndent(2)
err := enc.Encode(node)
assert.NoError(t, err)
err = enc.Close()
assert.NoError(t, err)
assert.Equal(t, buf.String(), testYaml)

// Ensure there were no mutations to the tree.
assert.DeepEqual(t, &item.node, node)
for i := 0; i < len(want.Content) && i < len(got.Content); i++ {
assertNodeEqual(t, want.Content[i], got.Content[i])
}
}

if t.Failed() {
// we already reported an error, there is no need to report it again.
return
}

// this error message is harder to read, and is only shown if no other errors were reported.
t.Errorf("nodes differ:\nwant:\n%#v\ngot:\n%#v", want, got)
}

func deepCopyNode(node *yaml.Node, cache map[*yaml.Node]*yaml.Node) *yaml.Node {
Expand Down Expand Up @@ -2909,7 +3026,7 @@ func TestSetString(t *testing.T) {

node.SetString(item.str)

assert.DeepEqual(t, item.node, node)
assertNodeEqual(t, &item.node, &node)

buf := bytes.Buffer{}
enc := yaml.NewEncoder(&buf)
Expand Down
17 changes: 17 additions & 0 deletions yamlprivateh.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,18 @@ func isFlowIndicator(b []byte, i int) bool {
// We further limit it to ascii chars only, which is a subset of the spec
// production but is usually what most people expect.
func isAnchorChar(b []byte, i int) bool {
if isColon(b, i) {
// [Go] we exclude colons from anchor/alias names.
//
// A colon is a valid anchor character according to the YAML 1.2 specification,
// but it can lead to ambiguity.
// https://github.com/yaml/go-yaml/issues/109
//
// Also, it would have been a breaking change to support it, as go.yaml.in/yaml/v3 ignores it.
// Supporting it could lead to unexpected behavior.
return false
}

return isPrintable(b, i) &&
!isLineBreak(b, i) &&
!isBlank(b, i) &&
Expand All @@ -75,6 +87,11 @@ func isAnchorChar(b []byte, i int) bool {
isASCII(b, i)
}

// isColon checks whether the character at the specified position is a colon.
func isColon(b []byte, i int) bool {
return b[i] == ':'
}

// Check if the character at the specified position is a digit.
func isDigit(b []byte, i int) bool {
return b[i] >= '0' && b[i] <= '9'
Expand Down
6 changes: 6 additions & 0 deletions yts/known-failing-tests
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ TestYAMLSuite/2JQS/UnmarshalTest
TestYAMLSuite/2JQS/EventComparisonTest
TestYAMLSuite/2LFX/UnmarshalTest
TestYAMLSuite/2LFX/EventComparisonTest
TestYAMLSuite/2SXE/UnmarshalTest
TestYAMLSuite/2SXE/EventComparisonTest
TestYAMLSuite/35KP/JSONComparisonTest
TestYAMLSuite/3HFZ/UnmarshalTest
TestYAMLSuite/3UYS/UnmarshalTest
Expand Down Expand Up @@ -185,13 +187,17 @@ TestYAMLSuite/01#15/UnmarshalTest
TestYAMLSuite/01#15/EventComparisonTest
TestYAMLSuite/W4TN/UnmarshalTest
TestYAMLSuite/W4TN/EventComparisonTest
TestYAMLSuite/W5VH/UnmarshalTest
TestYAMLSuite/W5VH/EventComparisonTest
TestYAMLSuite/WZ62/UnmarshalTest
TestYAMLSuite/WZ62/EventComparisonTest
TestYAMLSuite/X38W/UnmarshalTest
TestYAMLSuite/X38W/EventComparisonTest
TestYAMLSuite/X4QW/UnmarshalTest
TestYAMLSuite/X4QW/EventComparisonTest
TestYAMLSuite/XW4D/UnmarshalTest
TestYAMLSuite/Y2GN/EventComparisonTest
TestYAMLSuite/Y2GN/JSONComparisonTest
TestYAMLSuite/001/UnmarshalTest
TestYAMLSuite/001/EventComparisonTest
TestYAMLSuite/003/UnmarshalTest
Expand Down