Skip to content

Commit 37a72c6

Browse files
author
dmitriy kalinin
committed
ReplaceOp clones value so that it's not accidently mutated later
1 parent 3534f67 commit 37a72c6

File tree

2 files changed

+53
-8
lines changed

2 files changed

+53
-8
lines changed

patch/replace_op.go

+35-7
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,26 @@ package patch
22

33
import (
44
"fmt"
5+
6+
"gopkg.in/yaml.v2"
57
)
68

79
type ReplaceOp struct {
810
Path Pointer
9-
Value interface{}
11+
Value interface{} // will be cloned using yaml library
1012
}
1113

1214
func (op ReplaceOp) Apply(doc interface{}) (interface{}, error) {
15+
// Ensure that value is not modified by future operations
16+
clonedValue, err := op.cloneValue(op.Value)
17+
if err != nil {
18+
return nil, fmt.Errorf("ReplaceOp cloning value: %s", err)
19+
}
20+
1321
tokens := op.Path.Tokens()
1422

1523
if len(tokens) == 1 {
16-
return op.Value, nil
24+
return clonedValue, nil
1725
}
1826

1927
obj := doc
@@ -36,7 +44,7 @@ func (op ReplaceOp) Apply(doc interface{}) (interface{}, error) {
3644
}
3745

3846
if isLast {
39-
typedObj[idx] = op.Value
47+
typedObj[idx] = clonedValue
4048
} else {
4149
obj = typedObj[idx]
4250
prevUpdate = func(newObj interface{}) { typedObj[idx] = newObj }
@@ -49,7 +57,7 @@ func (op ReplaceOp) Apply(doc interface{}) (interface{}, error) {
4957
}
5058

5159
if isLast {
52-
prevUpdate(append(typedObj, op.Value))
60+
prevUpdate(append(typedObj, clonedValue))
5361
} else {
5462
return nil, fmt.Errorf("Expected after last index token to be last in path '%s'", op.Path)
5563
}
@@ -73,7 +81,7 @@ func (op ReplaceOp) Apply(doc interface{}) (interface{}, error) {
7381

7482
if typedToken.Optional && len(idxs) == 0 {
7583
if isLast {
76-
prevUpdate(append(typedObj, op.Value))
84+
prevUpdate(append(typedObj, clonedValue))
7785
} else {
7886
obj = map[interface{}]interface{}{typedToken.Key: typedToken.Value}
7987
prevUpdate(append(typedObj, obj))
@@ -87,7 +95,7 @@ func (op ReplaceOp) Apply(doc interface{}) (interface{}, error) {
8795
idx := idxs[0]
8896

8997
if isLast {
90-
typedObj[idx] = op.Value
98+
typedObj[idx] = clonedValue
9199
} else {
92100
obj = typedObj[idx]
93101
// no need to change prevUpdate since matching item can only be a map
@@ -108,7 +116,7 @@ func (op ReplaceOp) Apply(doc interface{}) (interface{}, error) {
108116
}
109117

110118
if isLast {
111-
typedObj[typedToken.Key] = op.Value
119+
typedObj[typedToken.Key] = clonedValue
112120
} else {
113121
prevUpdate = func(newObj interface{}) { typedObj[typedToken.Key] = newObj }
114122

@@ -137,3 +145,23 @@ func (op ReplaceOp) Apply(doc interface{}) (interface{}, error) {
137145

138146
return doc, nil
139147
}
148+
149+
func (ReplaceOp) cloneValue(in interface{}) (out interface{}, err error) {
150+
defer func() {
151+
if recoverVal := recover(); recoverVal != nil {
152+
err = fmt.Errorf("Recovered: %s", recoverVal)
153+
}
154+
}()
155+
156+
bytes, err := yaml.Marshal(in)
157+
if err != nil {
158+
return nil, err
159+
}
160+
161+
err = yaml.Unmarshal(bytes, &out)
162+
if err != nil {
163+
return nil, err
164+
}
165+
166+
return out, nil
167+
}

patch/replace_op_test.go

+18-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,23 @@ import (
88
)
99

1010
var _ = Describe("ReplaceOp.Apply", func() {
11+
It("returns error if replacement value cloning fails", func() {
12+
_, err := ReplaceOp{Path: MustNewPointerFromString(""), Value: func() {}}.Apply("a")
13+
Expect(err).To(HaveOccurred())
14+
Expect(err.Error()).To(ContainSubstring("ReplaceOp cloning value"))
15+
})
16+
17+
It("uses cloned value for replacement", func() {
18+
repVal := map[interface{}]interface{}{"a": "b"}
19+
20+
res, err := ReplaceOp{Path: MustNewPointerFromString(""), Value: repVal}.Apply("a")
21+
Expect(err).ToNot(HaveOccurred())
22+
Expect(res).To(Equal(repVal))
23+
24+
res.(map[interface{}]interface{})["c"] = "d"
25+
Expect(res).ToNot(Equal(repVal))
26+
})
27+
1128
It("replaces document if path is for the entire document", func() {
1229
res, err := ReplaceOp{Path: MustNewPointerFromString(""), Value: "b"}.Apply("a")
1330
Expect(err).ToNot(HaveOccurred())
@@ -233,7 +250,7 @@ var _ = Describe("ReplaceOp.Apply", func() {
233250
map[interface{}]interface{}{"xyz": "xyz"},
234251
map[interface{}]interface{}{
235252
"name": "val",
236-
"efg": []interface{}{1},
253+
"efg": []interface{}{1},
237254
},
238255
}))
239256
})

0 commit comments

Comments
 (0)