[php-src] PR #24062: Fix refcount corruption when GC runs while a WeakMap key is freed
| From: | rlorenzo | Date: | Fri, 02 Oct 2026 01:00:23 +0000 |
| Subject: | [php-src] PR #24062: Fix refcount corruption when GC runs while a WeakMap key is freed | ||
| Groups: | php.git-pulls | ||
| Request: | Send a blank email to git-pulls+get-39061@lists.php.net to get a copy of this message | ||
Pull Request: https://github.com/php/php-src/pull/24062
Author: rlorenzo
AI disclosure: I used an LLM (Claude) to investigate, fix and test this bug, and to write this
description. Per CONTRIBUTING.md, everything below this line is LLM-written and is quoted.
> When an object that is a key in two or more WeakMaps is freed,
>
zend_weakrefs_notify() releases each map's value in turn. If
> releasing one value runs the GC, the dying object
> (IS_OBJ_FREE_CALLED, so not scanned) is still a key in the remaining
> maps. gc_scan_black() leaves those entries for the key to blacken,
> which never happens, so their refcount stays decremented. The value then gets freed while still in
> use, which shows up as zend_mm_heap corrupted in release builds and
> as Assertion failed: (idx) in
> gc_remove_from_buffer in debug builds.
>
> ```php
> class CollectOnDestruct { function __destruct() { gc_collect_cycles(); } }
> $wm1 = new WeakMap; $wm2 = new WeakMap;
> $key = new stdClass;
> $wm1[$key] = new CollectOnDestruct;
> $keep = new stdClass;
> $wm2[$key] = $keep;
> $tmp = $wm2; unset($tmp); // make $wm2 a GC root
> $tmp = $key; unset($tmp); // make $key a GC root
> unset($key); // crashes / asserts
> ```
>
> Fix: remove the object from every map first (with pDestructor
> set to NULL, so nothing runs), then release the values.
> zend_weakmap_free_obj() already does it in this order.
>
> - The new test fails without the fix and passes with it on a debug + ASAN build.
> Zend/tests has no new failures.
> - Affects 8.3+ (since cbf67e4feee). This targets PHP-8.4 and merges up cleanly.
> - Side effect: WeakReferences to the object are now cleared
> before any value destructor runs.
> - Not tested: ZTS, Windows, JIT.