Skip to content

std.json: json_free leaks nested objects built with the creation API (json.obj/set) #1447

Description

@paul-hammant

Summary

Building a JSON tree with the creation API (json.obj() / json.arr()) and
freeing the root with json.json_free(root) leaks the nested created objects/
arrays. json_free does not fully reclaim children attached via json.set /
json.push on the builder (non-parsed) path.

Minimal repro

import std.json
main() {
    root  = json.obj()
    props = json.obj()
    p     = json.obj()
    _ = json.set(p, "type", json.str("integer"))
    _ = json.set(props, "age", p)
    _ = json.set(root, "properties", props)
    out, _ = json.encode(root)
    print("${out}\n")
    json.json_free(root)
}
$ valgrind --leak-check=full ./repro
definitely lost: 64 bytes in 2 blocks   # props + p, the two nested objects

The two nested json_create_object allocations (props, p) are lost. A single
top-level object frees fine; only nested-attached children leak.

Root cause (likely)

json_free (std/json/aether_json.c) frees via an arena when one is attached,
else heap_free_tree. Objects made with the creation API are heap_new'd with
JV_FLAG_HEAP_STRUCT; children attached later via object_set/array_add don't
appear to be linked into the parent's free path the way parsed-tree children are,
so heap_free_tree(root) doesn't reach them. (The parser/arena path is fine — this
is specific to hand-built trees.)

Impact

Any code that builds JSON with the creation API and frees the root leaks its
nested nodes. Notably contrib/tinyweb/schema_api's JSON:API error envelopes
(json.obj() nesting errors/source/etc.) leak on their error paths. It was
also why std.schema's to_json_schema() initially leaked — worked around there
by building the string with strbuilder instead of the JSON DOM (#1445), but the
underlying std.json issue remains.

Suggested fix

Make json_free reclaim builder-attached children on the heap path (or have
object_set/array_add link children into the root's arena/free-list so one
json_free(root) reclaims the whole tree), matching the parsed-tree behavior.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions