Skip to content

Fix three VM memory leaks: computed-property double creation, missing frees in gravity_core_free, orphaned GC gray-list buffer - #445

Merged
marcobambini merged 2 commits into
marcobambini:masterfrom
barchett:fix/vm-memory-leaks
Jul 20, 2026
Merged

Fix three VM memory leaks: computed-property double creation, missing frees in gravity_core_free, orphaned GC gray-list buffer#445
marcobambini merged 2 commits into
marcobambini:masterfrom
barchett:fix/vm-memory-leaks

Conversation

@barchett

Copy link
Copy Markdown
Contributor

Found while running an embedding host (repeated compile/run/free cycles) under AddressSanitizer/LeakSanitizer. Three distinct leaks, all reproducible with a minimal embed loop (below):

1. Computed properties created twice in gravity_core_init (double macro expansion)

When GRAVITY_USE_HIDDEN_INITIALIZERS is not set, VALUE_FROM_OBJECT() evaluates its argument twice:

#define VALUE_FROM_OBJECT(obj) ((gravity_value_t){.isa = ((gravity_object_t *)(obj)->isa), .p = (gravity_object_t *)(obj)})

Six bind sites in gravity_core_init call computed_property_create() inline inside that macro (Object.class, Object.meta, Int meta min/max, Float meta min/max). Each of those computed properties is therefore created twice — one copy gets bound, the duplicate is orphaned immediately (~2.2KB leaked per gravity_core_init, even without ever freeing the core). The fix uses the same temp-variable pattern the rest of gravity_core_init already uses for every other computed property, plus a comment warning against calling computed_property_create inline inside the macro.

2. Nine computed properties missing from gravity_core_free's manual free list

gravity_core_free manually frees List.count, Map.count, Range.count, String.length, Int/Float.radians/degrees, and System.gcEnabled — but not Object.class, Object.meta, Range.from, Range.to, String.bytes, Int meta min/max, or Float meta min/max, which gravity_core_init also creates. Each core init/free cycle leaked these (~3.3KB per cycle for hosts that restart the script engine).

3. GC gray-list buffer orphaned in gravity_vm_new

gravity_vm_new calls gravity_gc_setenabled(vm, true) before marray_init(vm->graylist). Enabling the GC can trigger a collection (gravity_gc_checkgravity_gc_start), which grows the gray-list buffer via marray_push/realloc — and the marray_init that follows zeroes the array struct, orphaning that buffer (one buffer leaked per VM). Fixed by initializing graylist/gctemp before enabling the GC.

Repro

for (int i = 0; i < 3; ++i) {
    gravity_delegate_t delegate = { .error_callback = report_error };
    gravity_compiler_t *compiler = gravity_compiler_create(&delegate);
    gravity_closure_t *closure = gravity_compiler_run(compiler, source, strlen(source), 0, true, true);
    gravity_vm *vm = gravity_vm_new(&delegate);
    gravity_compiler_transfer(compiler, vm);
    gravity_compiler_free(compiler);
    if (closure) { gravity_vm_runmain(vm, closure); gravity_gc_start(vm); }
    gravity_vm_free(vm);
    gravity_core_free();
}

Built with -fsanitize=address: beforeSUMMARY: AddressSanitizer: 22296 byte(s) leaked in 156 allocation(s); after — clean exit, zero leaks.

Testing

  • make && test/unittest/run_all.sh: 350/350 pass, 0 failed, 0 timed out with the patches applied.
  • The ASan embed loop above runs leak-free.

🤖 Generated with Claude Code

barchett added 2 commits July 19, 2026 00:07
…core_init

VALUE_FROM_OBJECT() evaluates its argument twice when
GRAVITY_USE_HIDDEN_INITIALIZERS is not set:

    #define VALUE_FROM_OBJECT(obj) ((gravity_value_t){.isa = ((gravity_object_t *)(obj)->isa), .p = (gravity_object_t *)(obj)})

Six bind sites in gravity_core_init called computed_property_create()
inline inside that macro (Object.class, Object.meta, Int.min/max meta,
Float.min/max meta), so each of those computed properties was created
twice: one copy bound, the duplicate orphaned (~2.2KB leaked per
gravity_core_init).

Use the same temp-variable pattern the rest of gravity_core_init already
uses for every other computed property.

Also free the nine computed properties missing from gravity_core_free's
manual free list (Object.class, Object.meta, Range.from, Range.to,
String.bytes, Int meta min/max, Float meta min/max) — previously leaked
on every core init/free cycle.
gravity_vm_new calls gravity_gc_setenabled(vm, true) before
marray_init(vm->graylist). Enabling the GC can trigger a collection
(gravity_gc_check -> gravity_gc_start), which grows the graylist buffer
via marray_push/realloc. The marray_init that follows then zeroes the
array struct, orphaning that buffer (leaked once per VM).

Initialize graylist/gctemp before enabling the GC.
@vercel

vercel Bot commented Jul 19, 2026

Copy link
Copy Markdown

@barchett is attempting to deploy a commit to the sqlitecloud Team on Vercel.

A member of the Team first needs to authorize it.

@marcobambini

Copy link
Copy Markdown
Owner

Thanks @barchett

@marcobambini
marcobambini merged commit 17a280b into marcobambini:master Jul 20, 2026
2 of 3 checks passed
@marcobambini marcobambini mentioned this pull request Aug 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants