fix: release the allocator when an updated value escapes to a query
The allocator of _modify tracks the values it created so that a later
update can modify them in place instead of copying them. When a target
path is a proper prefix of an earlier one, getpath hands such a value to
the update function, which may store it in more than one place; updating
through one of those places then changes all of them.
$ gojq -cn '{"a":{"b":1}} | (.a.b, .a, .a.p.z) |= {p: ., q: .}'
.a.p and .a.q hold separate values in jq, so only .a.p gains the "z" key,
while gojq adds it to both. Release the allocator whenever a value it
created leaves the update, so that the values are copied on update from
then on. The same applies to an object used as a path element, which
stays reachable through the path after the update.
This commit is contained in:
1 parent
f2996ce6e7
commit
a7ed5ccbe9
3 files changed
+36
-5
No files matched your search
@@ -1488,6 +1488,17 @@ func (a allocator) allocated(v any) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// release forgets every allocated address when the value is one of them, so
|
||||
// that a value handed to a query is copied on update again.
|
||||
func (a allocator) release(v any) {
|
||||
switch v.(type) {
|
||||
case map[string]any, []any:
|
||||
if a.allocated(v) {
|
||||
clear(a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a allocator) makeObject(l int) map[string]any {
|
||||
v := make(map[string]any, l)
|
||||
if a != nil {
|
||||
@@ -1514,6 +1525,15 @@ func funcSetpathWithAllocator(v any, args []any) any {
|
||||
return setpath(v, args[0], args[1], args[2].(allocator))
|
||||
}
|
||||
|
||||
// Used in compiler#compileModify: the update function may store the value
|
||||
// anywhere, so it must not be modified in place afterwards. Registered as
|
||||
// getpath to keep tracking paths through the call.
|
||||
func funcGetpathWithAllocator(v any, args []any) any {
|
||||
w := funcGetpath(v, args[0])
|
||||
args[1].(allocator).release(w)
|
||||
return w
|
||||
}
|
||||
|
||||
func setpath(v, p, n any, a allocator) any {
|
||||
path, ok := p.([]any)
|
||||
if !ok {
|
||||
@@ -1591,6 +1611,8 @@ func update(v any, path []any, n any, a allocator) (any, error) {
|
||||
return nil, &expectedArrayError{v}
|
||||
}
|
||||
case map[string]any:
|
||||
// A slice bound stays reachable through the path.
|
||||
a.release(p)
|
||||
switch v := v.(type) {
|
||||
case nil:
|
||||
return updateArraySlice(nil, p, path[1:], n, a)
|
||||
|
||||
Reference in new issue
Block a user