diff --git a/kyaml/yaml/rnode.go b/kyaml/yaml/rnode.go index 0059ec2eb..43cad97e5 100644 --- a/kyaml/yaml/rnode.go +++ b/kyaml/yaml/rnode.go @@ -718,10 +718,11 @@ func (rn *RNode) MustString() string { // Content returns Node Content field. func (rn *RNode) Content() []*yaml.Node { - if rn == nil { + yNode := rn.YNode() + if yNode == nil { return nil } - return rn.YNode().Content + return yNode.Content } // Fields returns the list of field names for a MappingNode. @@ -756,7 +757,11 @@ func (rn *RNode) FieldRNodes() ([]*RNode, error) { // Field returns a fieldName, fieldValue pair for MappingNodes. // Returns nil for non-MappingNodes. func (rn *RNode) Field(field string) *MapNode { - if rn.YNode().Kind != yaml.MappingNode { + yNode := rn.YNode() + if yNode == nil { + return nil + } + if yNode.Kind != yaml.MappingNode { return nil } var result *MapNode @@ -892,7 +897,11 @@ func (rn *RNode) ElementValuesList(keys []string) ([][]string, error) { // Element returns the element in the list which contains the field matching the value. // Returns nil for non-SequenceNodes or if no Element matches. func (rn *RNode) Element(key, value string) *RNode { - if rn.YNode().Kind != yaml.SequenceNode { + yNode := rn.YNode() + if yNode == nil { + return nil + } + if yNode.Kind != yaml.SequenceNode { return nil } elem, err := rn.Pipe(MatchElement(key, value)) @@ -906,7 +915,11 @@ func (rn *RNode) Element(key, value string) *RNode { // corresponding values[i]. // Returns nil for non-SequenceNodes or if no Element matches. func (rn *RNode) ElementList(keys []string, values []string) *RNode { - if rn.YNode().Kind != yaml.SequenceNode { + yNode := rn.YNode() + if yNode == nil { + return nil + } + if yNode.Kind != yaml.SequenceNode { return nil } elem, err := rn.Pipe(MatchElementList(keys, values)) @@ -960,12 +973,17 @@ func (rn *RNode) GetAssociativeKey() string { // MarshalJSON creates a byte slice from the RNode. func (rn *RNode) MarshalJSON() ([]byte, error) { + yNode := rn.YNode() + if yNode == nil { + return []byte("null"), nil + } + s, err := rn.String() if err != nil { return nil, err } - if rn.YNode().Kind == SequenceNode { + if yNode.Kind == SequenceNode { var a []interface{} if err := Unmarshal([]byte(s), &a); err != nil { return nil, err @@ -977,6 +995,7 @@ func (rn *RNode) MarshalJSON() ([]byte, error) { if err := Unmarshal([]byte(s), &m); err != nil { return nil, err } + return json.Marshal(m) } diff --git a/kyaml/yaml/rnode_test.go b/kyaml/yaml/rnode_test.go index 3bd037648..a032ae683 100644 --- a/kyaml/yaml/rnode_test.go +++ b/kyaml/yaml/rnode_test.go @@ -2356,3 +2356,70 @@ metadata: require.NoError(t, err) require.Equal(t, "hello-world-foo", fooAppName) // no field named 'foo.appname' } + +func TestRNode_nilSafety(t *testing.T) { + // Both of these scenarios should cause rn.YNode() to return nil. + nodesToTest := [...]struct { + name string + rn *RNode + }{ + {"nil *RNode receiver", nil}, + {"RNode with nil internal node", &RNode{value: nil}}, + } + + t.Run("Content", func(t *testing.T) { + for _, tc := range nodesToTest { + t.Run(tc.name, func(t *testing.T) { + assert.NotPanics(t, func() { + assert.Nil(t, tc.rn.Content()) + }) + }) + } + }) + + t.Run("Field", func(t *testing.T) { + for _, tc := range nodesToTest { + t.Run(tc.name, func(t *testing.T) { + assert.NotPanics(t, func() { + assert.Nil(t, tc.rn.Field("any-field")) + }) + }) + } + }) + + t.Run("Element", func(t *testing.T) { + for _, tc := range nodesToTest { + t.Run(tc.name, func(t *testing.T) { + assert.NotPanics(t, func() { + assert.Nil(t, tc.rn.Element("any-key", "any-value")) + }) + }) + } + }) + + t.Run("ElementList", func(t *testing.T) { + for _, tc := range nodesToTest { + t.Run(tc.name, func(t *testing.T) { + assert.NotPanics(t, func() { + assert.Nil(t, tc.rn.ElementList([]string{"any-key"}, []string{"any-value"})) + }) + }) + } + }) + + t.Run("MarshalJSON", func(t *testing.T) { + for _, tc := range nodesToTest { + t.Run(tc.name, func(t *testing.T) { + var ( + jsonData []byte + err error + ) + assert.NotPanics(t, func() { + jsonData, err = tc.rn.MarshalJSON() + }) + require.NoError(t, err) + assert.Equal(t, []byte("null"), jsonData) + }) + } + }) +}