SplFixedArray: leak on re-initialisation during setSize(0), and setSize() broken on missing parent::__construct()
还没有人认领这个 Issue。
评估
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 新手友好度
- 56/100
- Issue 类型
- 缺陷
- 描述清晰度
- 描述清楚
- 活跃度
- 活跃
- 领域
- backend, testing-qa
调研方向
Start with spl_fixedarray_resize() and spl_fixedarray_dtor(), tracing how cached_resize is read during setSize(0), __construct(), __wakeup(), and __unserialize(). Use ext/spl/tests/bug62904.phpt as the existing regression-test entry point and add coverage for both re-entrant reinitialisation and a subclass that omits parent::__construct(). Done means the reported leaks and no-op resize are covered by tests and the expected sizes are returned.
由索引模型根据 Issue 内容生成。
描述
Description
Two defects in SplFixedArray. Both come down to cached_resize being unreliable where it is read, and the fix touches the same function, so I am reporting them together.
1. Memory leak when the array is re-initialised from an element destructor during setSize(0)
spl_fixedarray_dtor() clears elements and size before running the element destructors (from the GH-11959 use-after-free fix), so the array then looks like one that was never constructed. __construct(), __wakeup() and __unserialize() gate on emptiness and re-initialise it, and the in-progress
clear discards the buffer they allocated.
The following code:
<?php
class Reentrant {
public static ?SplFixedArray $arr = null;
public function __destruct() {
if (self::$arr !== null) {
$arr = self::$arr;
self::$arr = null;
$arr->__construct(5);
}
}
}
$arr = new SplFixedArray(2);
Reentrant::$arr = $arr;
$arr[0] = new Reentrant();
$arr[1] = "tail";
$arr->setSize(0);
var_dump($arr->getSize());
Resulted in this output:
int(0)
ext/spl/spl_fixedarray.c(95) : Freeing 0x0000e716d0e84070 (80 bytes)
=== Total 1 memory leaks detected ===
But I expected this output instead:
int(0)
Replacing __construct(5) with __unserialize(["a", "b", "c"]) or __wakeup() leaks the same way. A second site leaks when the destructor follows the re-init with a setSize(): the re-init resets the sentinel, so the setSize() performs a real nested shrink and its erealloc() buffer is discarded too.
This is the same bug as GH-21920, which cb3dc62fd90 fixed for a re-entrant setSize() by testing the resize sentinel first. The other three entry points were not covered.
2. setSize() silently does nothing on a subclass whose constructor does not call parent::__construct()
The following code:
<?php
class S extends SplFixedArray {
public function __construct() {
}
}
$s = new S();
var_dump($s->setSize(5));
var_dump($s->getSize());
$s[0] = 'x';
Resulted in this output:
bool(true)
int(0)
Fatal error: Uncaught OutOfBoundsException: Index invalid or out of range in /tmp/rep2.php:10
Stack trace:
#0 {main}
thrown in /tmp/rep2.php on line 10
But I expected this output instead:
bool(true)
int(5)
zend_object_alloc() zeroes the object, so cached_resize starts at 0 instead of the documented -1. spl_fixedarray_resize() reads >= 0 as "a resize is already in progress" and returns early, so setSize() returns true and does nothing — permanently, for every call on that instance.
This is a regression: cb3dc62fd90 moved the "first initialization" branch below the sentinel check, which exposed the uninitialised sentinel. Before that, this path reached spl_fixedarray_init() and worked.
ext/spl/tests/bug62904.phpt already uses this class shape but only asserts "No crash" after a clone, which is why it was not caught.
PHP Version
PHP 8.4.27-dev (cli) (built from git, NTS DEBUG)
PHP 8.6.0-dev (cli) (built from git, NTS DEBUG)
Operating System
Linux aarch64 (Ubuntu 24.04)
- 主要语言
- C
- 星标
- 40.4k
- 派生
- 8.2k
- 平均合并
- 2 天 17 小时
- 30 天内合并 PR
- 115
贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
php/php-src 的其他 Issue
-
Bug Status: Needs Triage
难度 2/5 1-3 小时 新手友好度 76/100
-
Bug Status: Needs Triage
难度 1/5 1 小时以内 新手友好度 90/100
-
Bug Status: Needs Triage
难度 2/5 1-3 小时 新手友好度 78/100
-
Bug Category: Tests Status: Verified
难度 2/5 1-3 小时 新手友好度 68/100
-
Bug SAPI: fpm Status: Needs Triage
难度 2/5 1-3 小时 新手友好度 65/100
相似的 Issue
-
bug
难度 1/5 1 小时以内 新手友好度 60/100
-
Nmap
难度 1/5 1 小时以内 新手友好度 85/100
-
难度 2/5 1-3 小时 新手友好度 65/100
-
难度 2/5 1-3 小时 新手友好度 65/100
-
flang:fir-hlfir
难度 2/5 1-3 小时 新手友好度 70/100
llvm/llvm-project#225935 ·