Commit 6e4b81c
fix: core.clone wrote through stale captured slices across a relocating allocation, risking memory corruption
Found while continuing the gc-rooting-hardening audit (5d38c29 and
earlier on main). This is a write-side sibling of the read-side
stale-slice class the 2026-07-25 sweep and 5d38c29 both targeted, and
more serious: a stale *read* returns a wrong value; a stale *write*
lands in whatever memory now occupies the old, relocated-away address,
corrupting an unrelated live allocation.
cloneObject's array_managed and struct_instance branches populate a
freshly-allocated destination slice in a loop whose body (cloneValue)
recurses arbitrarily and can allocate — which can, rarely, trigger
compactManagedHeap and relocate the destination's own just-allocated
backing block. Both branches wrote through a local slice variable
captured once before the loop (`out[i] = ...`, `fields[i].value = ...`)
instead of through the owning Object's own field, which is the only
thing compaction's relocation bookkeeping (heap.zig's compactUpdateObj)
actually keeps current. struct_instance also read its *source* fields
through a similarly stale captured local. Fixed both to read and write
through src/out_obj's own fields, re-derived fresh on every iteration —
matching the pattern array.zig's itemAt and TempRootedManagedMap.set
already use correctly.
variant_value's shared_values/arm_fields had a deeper version of the
same bug: it used a "publish empty, grow visible by one each iteration"
scheme to keep the GC marker from tracing uninitialized memory, writing
through a captured local exactly like the above. But
compactManagedHeap sizes the block it relocates from the *currently
visible* length (heap.zig's compactFillBlocks reads the object's own
field length at scan time) — so a compaction mid-loop would only
preserve the already-grown prefix, permanently losing the reserved but
not-yet-written tail of the original allocation, on top of the same
write-through-stale-local hazard. Fixed by adopting the safer pattern
array_managed/struct_instance already use: pre-fill the whole
destination with .null and publish the full length immediately (every
slot is then always a valid, traceable Value, so no incremental
visibility is needed), then read/write through out_obj's own field per
iteration like the other branches.
map's clone branch was already correct — TempRootedManagedMap.set()
re-derives self.obj.map[i] fresh on every call.
Verified: existing conformance tests 165_clone_variant_shared.gengo and
202_clone_variant_gc_window.gengo (the latter built specifically to
stress this exact loop with GC firing mid-fill) both still pass via the
rebuilt CLI. zig build compiler-test (native Debug, -Dgc_stress=true,
-Dheap_paranoia=true) all 993/993. heap-test and chaos-spec-test pass.
No dedicated fragmentation-triggering repro was engineered for the
compaction-specific scenario (rare to hit deliberately; same tradeoff
as validateNamedCollectionElements in 5d38c29) — CI's gc-stress-test
job runs the full tests/spec/*.gengo conformance suite, including both
tests above, under -Dgc_stress=true already.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C3pMYWS2xy8zitUkpJaiZU1 parent 5d38c29 commit 6e4b81c
1 file changed
Lines changed: 57 additions & 19 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
735 | 735 | | |
736 | 736 | | |
737 | 737 | | |
738 | | - | |
739 | | - | |
740 | | - | |
741 | | - | |
| 738 | + | |
| 739 | + | |
| 740 | + | |
| 741 | + | |
| 742 | + | |
| 743 | + | |
| 744 | + | |
| 745 | + | |
| 746 | + | |
| 747 | + | |
| 748 | + | |
742 | 749 | | |
743 | 750 | | |
744 | | - | |
| 751 | + | |
| 752 | + | |
745 | 753 | | |
746 | 754 | | |
747 | 755 | | |
| |||
789 | 797 | | |
790 | 798 | | |
791 | 799 | | |
792 | | - | |
| 800 | + | |
| 801 | + | |
793 | 802 | | |
794 | 803 | | |
795 | | - | |
796 | | - | |
797 | | - | |
| 804 | + | |
| 805 | + | |
| 806 | + | |
| 807 | + | |
| 808 | + | |
| 809 | + | |
| 810 | + | |
| 811 | + | |
| 812 | + | |
798 | 813 | | |
799 | 814 | | |
800 | 815 | | |
| |||
814 | 829 | | |
815 | 830 | | |
816 | 831 | | |
817 | | - | |
818 | | - | |
819 | | - | |
820 | | - | |
821 | | - | |
| 832 | + | |
| 833 | + | |
| 834 | + | |
| 835 | + | |
| 836 | + | |
| 837 | + | |
| 838 | + | |
| 839 | + | |
| 840 | + | |
| 841 | + | |
| 842 | + | |
| 843 | + | |
| 844 | + | |
| 845 | + | |
| 846 | + | |
| 847 | + | |
| 848 | + | |
| 849 | + | |
| 850 | + | |
| 851 | + | |
| 852 | + | |
| 853 | + | |
| 854 | + | |
| 855 | + | |
| 856 | + | |
822 | 857 | | |
823 | | - | |
824 | | - | |
825 | | - | |
826 | | - | |
827 | | - | |
| 858 | + | |
| 859 | + | |
| 860 | + | |
| 861 | + | |
| 862 | + | |
| 863 | + | |
| 864 | + | |
| 865 | + | |
828 | 866 | | |
829 | 867 | | |
830 | 868 | | |
| |||
0 commit comments