作为 php core developer,我从事 php 语言的内核维护和开发工作有一段时间了。一直有很多想法和经验总是忘记。遂写一篇类似与小记的博客记录一下。
DONE
对于我在 php-src(php 内核)里做的所有贡献,可以在 commit list 里找到。
以下是我对 PHP 做过或正在推进的用户可见行为变化(不包含重构)。(更新于 2026-10-26 00:37 CST)
PHP 8.7.x
RFC
- 使
ini_get_all函数大小写不敏感 (RFC) - 添加
IntlRelativeDateTimeFormatter类,方便用户在 PHP 里用国际化的方式表示相对时间。(PR #23514, RFC)
新的函数/类/接口
Bug 修复
- ext/intl 让
IntlChar::enumCharNames()在回调抛出异常时停止枚举并正确传播异常,之后仍可继续使用该 API。(PR #23866, commit 5dfd11b) - ext/zip 修复
ZipArchive::registerCancelCallback()的取消回调在 PHP 关闭阶段返回无效类型时可能导致的断言失败,并安全处理返回值转换和析构期间的 bailout。(从 PHP 8.6 合并上来;PR #23914, merge commit 85a8601) - ext/curl 保持回调接收者和闭包在回调执行完成前存活,允许 cURL 回调替换或清除自身,避免过早释放对象。(从 PHP 8.6 合并上来;PR #23863, commit 9e7117f)
- ext/intl 修复
grapheme_strstr()和grapheme_stristr()在匹配位置之前包含补充平面字符时返回错误子串的问题,涵盖两种beforeNeedle模式。(从 PHP 8.6 合并上来;PR #24012) - ext/intl 修复
grapheme_strstr()和grapheme_strpos()漏掉 Unicode 规范等价匹配的问题,并避免字素搜索在空字符串中查找非空字符串时产生 ICU 错误。(从 PHP 8.6 合并上来;PR #24029) - ext/intl 修复
grapheme_strrpos()和grapheme_strripos()在使用负偏移量时漏掉重叠匹配的问题。(从 PHP 8.6 合并上来;PR #24028) - ext/readline 修复
readline_info()在completion_append_character含有非 ASCII 字节时可能崩溃的问题。(从 PHP 8.6 合并上来;PR #24263) - ext/opcache 修复 tracing JIT 比较时的整数范围上界推断,避免对最小整数减一造成溢出,并保留正确的操作数上界。(从 PHP 8.6 合并上来;PR #24633)
- ext/zip 在配置阶段排除 libzip 1.12 和 1.12.0,避免 TorrentZip 归档无法添加或替换文件的回归问题。(从 PHP 8.6 合并上来;PR #24358)
- ext/zip 在 Windows 配置阶段拒绝已知有问题的 libzip 1.3.1、1.7.0、1.12 和 1.12.0,与 Unix 的版本检查保持一致。(从 PHP 8.6 合并上来;PR #24371)
性能优化
- ext/standard
array_map()在 callback 为null的多数组 zip 场景中使用 packed array 填充结果行,并对空输入提前返回,减少哈希插入和中间数组构造开销。([PR #23809](https://github.com/php/php-src/pu
ll/23809), merge commit 82d91d4) - ext/standard
pack()在字符串格式字段中只填充实际需要的尾部填充区域,避免对整个字段重复写入,提升字符串打包场景的性能。(PR #24133, commit dc44321) - ext/standard
strcspn()和strpbrk()在单字符搜索且待搜索字符串较长时使用memchr()快速定位,减少逐字符扫描开销。(PR #24087, commit c33572a)
PHP 8.6.x
RFC
-
trim/ltrim/rtrim/chop支持换页符。(PR #20788, RFC, PHP.WATCH, blog) - 为 intl 添加
Locale::getDisplayKeyword()(别名locale_get_display_keyword()) 和Locale::getDisplayKeywordValue()(别名locale_get_display_keyword_value())。(PR #22264, RFC) - 弃用
metaphone函数。(PR #21889, RFC)
新的函数/类/接口
gmp
intl
Bug 修复
- ext/zip 修复
ZipArchive::registerCancelCallback()的取消回调在 PHP 关闭阶段返回无效类型时可能导致的断言失败,并安全处理返回值转换和析构期间的 bailout。(PR #23914, commit f3db5d6) - ext/curl 保持回调接收者和闭包在回调执行完成前存活,允许 cURL 回调替换或清除自身,避免过早释放对象。(从 PHP 8.5 合并上来;PR #23863, commit 9e7117f)
- Zend 修复数组重哈希或压缩空洞时未正确迁移内部指针与多个
foreach迭代器,导致嵌套按引用遍历跳过元素的问题。(仅保留在 PHP 8.6,已从 PHP 8.4 和 PHP 8.5 回退;PR #23607) - ext/intl 修复
IntlNumberRangeFormatter::createFromSkeleton()和IntlNumberRangeFormatter::format()成功调用后仍保留旧 intl 错误状态的问题。(PR #23191) - ext/intl 修复部分构建环境中
std::numeric_limits未由传递包含提供而导致的编译失败。(PR #23072) - ext/standard
phpcredits()修复 full-page HTML 输出中的页面标题错误。(PR #23007) - ext/standard 修复
Io\Pollwatcher registry 在 watcher 移除和注册失败时的状态同步。(PR #22760) - ext/soap 修复
xsd:hexBinary反序列化时对奇数长度十六进制字符串的误判崩溃。(PR #22698) - ext/dom 修复
Dom\DtdNamedNodeMap按维度访问时的索引溢出。(PR #22630) - ext/intl 修复
IntlIterator::current()在无效迭代器上返回UNKNOWN:0,现返回NULL。(PR #22495) - ext/intl 修复
IntlDateFormatter::parse()/datefmt_parse()和IntlDateFormatter::localtime()/datefmt_localtime()现在在偏移量参数不是 int 类型时抛出 TypeError。(PR #22533) - ext/intl 修复
NumberFormatter::parse()和NumberFormatter::parseCurrency()拒绝超出 32 位范围的偏移量值,而不是静默截断。(PR #22572) - ext/intl 在 64 位平台上让
MessageFormatter::parse()将PHP_INT_MIN作为int返回。(PR #22131) - ext/soap 拒绝超出 XML Schema integer 范围的值。(PR #22178)
- ext/intl 修复
UConverter::transcode()替换字符长度截断。(PR #22199) - ext/intl 在
IntlNumberRangeFormatter::format()中保留 64 位整数精度。(PR #22202) - ext/intl 修复
ResourceBundle禁用回退时的错误信息。(PR #22236) - ext/standard
phpcredits()修复团队标题对齐问题。(PR #22501, PR #22918) - ext/dom 修复
DOMException中INUSE_ATTRIBUTE_ERR的错误信息(PR #23381) - ext/dom 修复
DOMNode::isEqualNode()比较深层嵌套 DOM 树时可能触发栈溢出的问题。(从 PHP 8.5 合并上来;PR #23140) - ext/hash 修复
php_hash.h无法被 C++ 代码包含的问题。(从 PHP 8.5 合并上来;PR #22940) - ext/zip 避免重置
ZipArchive注释时发生崩溃。(从 PHP 8.5 合并上来;PR #22652) - ext/sockets 修复
socket_setopt()/socket_set_option()参数校验失败时的错误信息。(从 PHP 8.5 合并上来;PR #22772) - ext/intl 修复
IntlListFormatter在format()失败后的对象错误状态。(从 PHP 8.5 合并上来;commit 946d687) - ext/intl 修复日历日期/时间 API 中越界的参数位置。(从 PHP 8.5 合并上来;commit 10704f0)
- ext/intl 在
IntlDateFormatter错误信息中使用PATTERN常量名。(从 PHP 8.5 合并上来;commit 2d78489) - ext/intl 修复未初始化日历参数的参数位置。(从 PHP 8.5 合并上来;commit 27d593e)
- ext/soap 修复解码 SOAP 数组索引时的整数溢出。(从 PHP 8.5 合并上来;PR #21963)
- ext/phar 在
Phar::addEmptyDir()中更正确地保留.phar魔术目录。(从 PHP 8.5 合并上来;PR #22011) - ext/intl 为无效显示类型同步
IntlTimeZone对象错误状态。(从 PHP 8.5 合并上来;commit 9e68cf8) - ext/intl 保持
Spoofchecker限制级别 API 的 ICU 53 版本保护。(从 PHP 8.5 合并上来;commit edde169) - ext/opcache 修复有类型按值返回时未解开引用包装的问题。(从 PHP 8.5 合并上来;PR #21973)
- ext/intl 修复
transliterator_transliterate()错误信息中的参数位置。(从 PHP 8.5 合并上来;commit d8e7418) - ext/phar 拒绝格式错误的 ZIP 额外字段,避免剩余长度下溢。(从 PHP 8.5 合并上来;commit 657f0d6, PR #22330)
- ext/intl 让
Locale::lookup()和locale_lookup()在语言标签无效时返回null,而不是回退到默认 locale。(从 PHP 8.5 合并上来;PR #22306) - ext/intl 让
Collator::__construct()和Spoofchecker::__construct()在重复构造已初始化对象时抛出Error,避免覆盖 ICU 句柄造成内存泄漏。(从 PHP 8.5 合并上来;PR #22386) - ext/intl 让
IntlListFormatter::__construct()在重复构造已初始化对象时抛出Error,避免覆盖 ICU 句柄造成内存泄漏。(从 PHP 8.5 合并上来;PR #22394) - ext/phar 修复以
.phar为前缀但不是魔术目录的目录处理。(从 PHP 8.5 合并上来;PR #22372) base_convert()修复较小缓冲区长度下的转换结果错误。(从 PHP 8.5 合并上来;PR #22406)- ext/zip 修复
ZipArchive::getFromIndex()忽略已删除条目的FL_UNCHANGED标志。(从 PHP 8.5 合并上来;PR #21706) - ext/intl 在
IntlListFormatter被创建时重置错误状态。(从 PHP 8.5 合并上来;PR #22931) - ext/readline 修复交互式 Shell 不会等待分页器进程退出的问题。(从 PHP 8.5 合并上来;PR #23047)
- ext/readline 修复交互式 Shell 中的类常量自动补全功能。(从 PHP 8.5 合并上来;PR #23218)
- ext/gd 修复
imageaffinematrixget()和imageaffinematrixconcat报错信息中参数位置错位的问题。(从 PHP 8.5 合并上来;PR #23356) - ext/zip 修复当
ZipArchive子类保存了它自身打开的流时,该子类对象无法被垃圾回收的问题。(从 PHP 8.5 合并上来;PR #23282) - ext/phar 修复访问已挂载 Phar 目录的子目录时可能触发的 use-after-free。(从 PHP 8.5 合并上来;PR #23442)
- ext/phar 修复原生 Phar manifest 含有重复文件名时泄漏文件名和元数据的问题。(从 PHP 8.5 合并上来;PR #23479)
- ext/intl 修复
grapheme_strstr()和grapheme_stristr()在匹配位置之前包含补充平面字符时返回错误子串的问题,涵盖两种beforeNeedle模式。(从 PHP 8.5 合并上来;PR #24012) - ext/intl 修复
grapheme_strstr()和grapheme_strpos()漏掉 Unicode 规范等价匹配的问题,并避免字素搜索在空字符串中查找非空字符串时产生 ICU 错误。(从 PHP 8.5 合并上来;PR #24029) - ext/intl 修复
grapheme_strrpos()和grapheme_strripos()在使用负偏移量时漏掉重叠匹配的问题。(从 PHP 8.5 合并上来;PR #24028) - ext/readline 修复
readline_info()在completion_append_character含有非 ASCII 字节时可能崩溃的问题。(从 PHP 8.5 合并上来;PR #24263) - ext/opcache 修复 tracing JIT 比较时的整数范围上界推断,避免对最小整数减一造成溢出,并保留正确的操作数上界。(从 PHP 8.5 合并上来;PR #24633)
- ext/zip 在配置阶段排除 libzip 1.12 和 1.12.0,避免 TorrentZip 归档无法添加或替换文件的回归问题。(从 PHP 8.5 合并上来;PR #24358)
- ext/zip 在 Windows 配置阶段拒绝已知有问题的 libzip 1.3.1、1.7.0、1.12 和 1.12.0,与 Unix 的版本检查保持一致。(从 PHP 8.5 合并上来;PR #24371)
其他重大改动
- ext/phar 包含文件时,仅当包含文件的文件名部分含有以结尾或点号分隔的
.phar标记时,才将其解释为 Phar 归档并触发反序列化。(PR #23260) - ext/gmp 给
gmp_prevprime函数添加可选的definitelyPrime参数。(PR #22907) - ext/soap 改进 SOAP 标量解码失败时的错误信息,说明具体格式错误原因。(PR #22706)
linkinfo('')现在会抛出ValueError。(PR #21793)getenv()和putenv()拒绝包含 NUL 字节的参数。(PR #21817)proc_open()拒绝$cwd中的 NUL 字节。(PR #21871)parse_str()拒绝$string中的 NUL 字节。(PR #21942)dl()拒绝$extension_filename中的 NUL 字节。(PR #22358)openlog()拒绝$prefix中的 NUL 字节。(PR #22366)setlocale()拒绝$locales中的 NUL 字节。同时,禁止在$locales参数后传入数组。若$locales本身为数组,则不允许在该数组参数后额外追加其他入参。(PR #22843)- ext/gmp 对
**、<<、>>中会损失精度的浮点数右操作数发出弃用警告。(PR #21945)
其他微小改动
- ext/soap 在
classmap选项校验中一致拒绝包含整数键的数组。(PR #22884) - ext/intl 通过 intl 国际化扩展的错误处理机制,上报
Collator排序比对过程中出现的 UTF-8 与 UTF-16 互相转换失败问题。(PR #22893) - ext/intl 移除对 ICU IO 库的依赖。(PR #22891)
- ext/intl 修正时区偏移量过大和 normalization form 校验相关的错误信息文本。(PR #22828)
- ext/phar 在错误信息文本中删除关于
safe mode的描述。(PR #22858) - ext/gmp 在解析整数字符串时拒绝含 NUL 字节的字符串。(PR #22798)
- ext/gmp 在
**、<<、>>中拒绝不在unsigned long范围内的GMP对象右操作数,避免静默截断。(PR #22656) shmop_open()拒绝超出key_t长度的值。(PR #22423)- ext/zlib 在
deflate_init()和inflate_init()中使用更严格的整数选项处理。(PR #21841, PR #21860, PR #21909) - ext/intl 在所有受支持的 ICU 版本上暴露
Spoofchecker限制级别 API。(PR #22055, commit edde169) pcntl_exec()验证$args必须是列表数组。(PR #21951)- ext/intl 对不可转为字符串的时区对象抛出
TypeError。(PR #21990) IntlBreakIterator::getLocale()对无效 locale type 值抛出ValueError。(PR #21997)。shm_attach()拒绝超出key_t长度的值。(PR #22423)number_format()拒绝过大的$decimals参数,避免 OOM。(PR #22435)sleep()和usleep()现在拒绝超出平台上限的值,修复无符号整数溢出。(PR #22619)- ext/gd 修复
imageaffinematrixget()对$options参数强制校验文档声明的array|float类型,否则抛出TypeError。(PR #23384)
性能优化
- ext/standard
array_column()对空输入提前返回,在未指定列和索引时直接使用 packed array 快速复制,并将数字字符串键的规范化移出主循环,提升常见列提取场景的性能。(PR #23786, merge commit 5d24c8b) - ext/intl
IntlChar::enumCharTypes()优化字符类型枚举的回调处理,直接传递FCC,减少不必要的回调上下文包装。(PR #23748, merge commit 305a60b) - ext/standard
pathinfo()重用已生成的 basename,并共享PATHINFO_ALL中扩展名和文件名的末尾点查找,提升PATHINFO_BASENAME、PATHINFO_EXTENSION和PATHINFO_FILENAME的性能;对无扩展名文件的PATHINFO_FILENAME还可避免一次内存分配。(PR #23726, commit c9e2bc0) - ext/standard
stat()、lstat()和fstat()预先分配返回结果数组,减少构造文件状态信息时的扩容和哈希检查开销。(commit 9687652) - ext/standard
error_get_last()使用已知的新键构造错误信息数组,减少键更新时不必要的哈希检查。(commit 6541e83) - ext/standard
getopt()使用已知的新键插入选项结果,减少解析命令行选项时的哈希检查开销。(commit f0ed2c5) - ext/standard
fgetcsv()和str_getcsv()在解析 CSV 字段时使用追加新元素的插入路径,减少数组插入开销。(commit a318ae8) - ext/standard
array_change_key_case()复制整数键时使用已知的新键插入路径,提升数组键转换性能。(commit 1849f03) - ext/standard
str_repeat()在 multiplier 为 1 时直接复用输入字符串,避免不必要的字符串分配和拷贝,提升性能。(PR #23681, commit 93fd714) - ext/standard
addcslashes()在生成八进制转义时改为手动写入三位八进制字符,减少snprintf开销,提升性能。(PR #23680, commit 2101993) - ext/mbstring
mb_str_pad()使用倍增拷贝策略拼接填充内容,降低重复拷贝次数提高性能。(PR #23667, commit ae62043) - ext/standard
str_pad()在填充字符串时改用倍增拷贝(doubling copies),减少多次小拷贝开销,性能更优。(PR #23661, commit 74e3a6) - ext/intl 在
Collator::sort()与Collator::asort()的比较回调路径(collator_regular_compare_function())里减少了临时变量和栈使用,降低排序开销。(PR #23627, commit 0b548e8) - ext/standard
explode()在负向limit分支上提升性能,内层用ZVAL_STRINGL_FAST()替代ZVAL_STRINGL,减少一次emalloc + memcpy + free的成本。(PR #23631, commit ac53f14) - Zend 将所有对单元素数组的排序性能提升 50%。(PR #23550)
- ext/gd 将
imagegrabscreen和imagegrabwindow的性能提升到原来的约 814 倍。(PR #23485) - ext/intl
intlcal_get_available_locales()和IntlCalendar::getAvailableLocales()预先分配返回数组,减少获取可用 locale 列表时的扩容开销。(PR #22069, commit 900797e) - ext/intl
datefmt_localtime()和IntlDateFormatter::localtime()预先分配固定大小的返回数组,减少日期解析结果构造时的扩容开销。(PR #22069, commit 900797e) - ext/intl
locale_get_primary_language()、locale_get_script()、locale_get_region()和locale_canonicalize()复用 ICU 返回值的已知长度,减少重复的字符串长度计算。(PR #22069, commit 900797e) - ext/intl
locale_get_keywords()、locale_parse()和locale_filter_matches()复用已知的 locale 字符串长度,减少重复计算和不必要的内存分配。(PR #22069, commit 900797e) - ext/zip 在已知数组大小时预先初始化数组。(PR #21572)
- ext/uri 预先初始化错误数组。(PR #21560)
- ext/uri 优化 RFC3986 解析中的字符串分配。(PR #21550)
- ext/pgsql 追加换行时移除一次不必要的 NUL 字节复制。(PR #21597)
- ext/json 优化美化输出时的缩进逻辑。(PR #21474)
- ext/phar 优化 Phar 目录流中的临时字符串处理。(PR #22374)
PHP 8.5.x
Bug 修复
- ext/curl 保持回调接收者和闭包在回调执行完成前存活,允许 cURL 回调替换或清除自身,避免过早释放对象。(从 PHP 8.4 合并上来;PR #23863, commit 9e7117f)
- ext/sockets 修复
socket_setopt()/socket_set_option()参数校验失败时的错误信息。(PR #22772) - ext/intl 在
IntlListFormatter被创建时重置错误状态。(PR #22931) - ext/intl 修复
IntlListFormatter在format()失败后的对象错误状态。(commit 946d687) - ext/readline 修复交互式 Shell 不会等待分页器进程退出的问题。(PR #23047)
- ext/dom 修复
DOMNode::isEqualNode()比较深层嵌套 DOM 树时可能触发栈溢出的问题。(从 PHP 8.4 合并上来;PR #23140) - ext/hash 修复
php_hash.h无法被 C++ 代码包含的问题。(从 PHP 8.4 合并上来;PR #22940) - ext/intl 修复日历日期/时间 API 中越界的参数位置。(从 PHP 8.4 合并上来;commit 10704f0)
- ext/intl 在
IntlDateFormatter错误信息中使用PATTERN常量名。(从 PHP 8.4 合并上来;commit 2d78489) - ext/intl 修复未初始化日历参数的参数位置。(从 PHP 8.4 合并上来;commit 27d593e)
- ext/soap 修复解码 SOAP 数组索引时的整数溢出。(从 PHP 8.4 合并上来;PR #21963)
- ext/phar 在
Phar::addEmptyDir()中更正确地保留.phar魔术目录。(从 PHP 8.4 合并上来;commit 3064540) - ext/intl 为无效显示类型同步
IntlTimeZone对象错误状态。(从 PHP 8.4 合并上来;commit 9e68cf8) - ext/intl 保持
Spoofchecker限制级别 API 的 ICU 53 版本保护。(从 PHP 8.4 合并上来;commit edde169) - ext/opcache 修复有类型按值返回时未解开引用包装的问题。(从 PHP 8.4 合并上来;PR #21973)
- ext/intl 修复
transliterator_transliterate()错误信息中的参数位置。(从 PHP 8.4 合并上来;commit d8e7418) - ext/phar 拒绝格式错误的 ZIP 额外字段,避免剩余长度下溢。(从 PHP 8.4 合并上来;commit 657f0d6, PR #22330)
- ext/intl 让
Locale::lookup()和locale_lookup()在语言标签无效时返回null,而不是回退到默认 locale。(从 PHP 8.4 合并上来;PR #22306) - ext/intl 让
Collator::__construct()和Spoofchecker::__construct()在重复构造已初始化对象时抛出Error,避免覆盖 ICU 句柄造成内存泄漏。(从 PHP 8.4 合并上来;PR #22386) - ext/intl 让
IntlListFormatter::__construct()在重复构造已初始化对象时抛出Error,避免覆盖 ICU 句柄造成内存泄漏。(PR #22394) - ext/phar 修复以
.phar为前缀但不是魔术目录的目录处理。(从 PHP 8.4 合并上来;PR #22372) base_convert()修复较小缓冲区长度下的转换结果错误。(从 PHP 8.4 合并上来;PR #22406)- ext/zip 修复
ZipArchive::getFromIndex()忽略已删除条目的FL_UNCHANGED标志。(从 PHP 8.4 合并上来;PR #21706) - ext/zip 避免重置
ZipArchive注释时发生崩溃。(从 PHP 8.4 合并上来;PR #22652) - ext/readline 修复交互式 Shell 中的类常量自动补全功能。(从 PHP 8.4 合并上来;PR #23218)
- ext/gd 修复
imageaffinematrixget()和imageaffinematrixconcat报错信息中参数位置错位的问题。(从 PHP 8.4 合并上来;PR #23356) - ext/zip 修复当
ZipArchive子类保存了它自身打开的流时,该子类对象无法被垃圾回收的问题。(从 PHP 8.4 合并上来;PR #23282) - ext/phar 修复访问已挂载 Phar 目录的子目录时可能触发的 use-after-free。(从 PHP 8.4 合并上来;PR #23442)
- ext/phar 修复原生 Phar manifest 含有重复文件名时泄漏文件名和元数据的问题。(从 PHP 8.4 合并上来;PR #23479)
- ext/intl 修复
grapheme_strstr()和grapheme_stristr()在匹配位置之前包含补充平面字符时返回错误子串的问题,涵盖两种beforeNeedle模式。(从 PHP 8.4 合并上来;PR #24012) - ext/intl 修复
grapheme_strstr()和grapheme_strpos()漏掉 Unicode 规范等价匹配的问题,并避免字素搜索在空字符串中查找非空字符串时产生 ICU 错误。(从 PHP 8.4 合并上来;PR #24029) - ext/intl 修复
grapheme_strrpos()和grapheme_strripos()在使用负偏移量时漏掉重叠匹配的问题。(从 PHP 8.4 合并上来;PR #24028) - ext/readline 修复
readline_info()在completion_append_character含有非 ASCII 字节时可能崩溃的问题。(从 PHP 8.4 合并上来;PR #24263) - ext/opcache 修复 tracing JIT 比较时的整数范围上界推断,避免对最小整数减一造成溢出,并保留正确的操作数上界。(从 PHP 8.4 合并上来;PR #24633)
- ext/zip 在配置阶段排除 libzip 1.12 和 1.12.0,避免 TorrentZip 归档无法添加或替换文件的回归问题。(从 PHP 8.4 合并上来;PR #24358)
- ext/zip 在 Windows 配置阶段拒绝已知有问题的 libzip 1.3.1、1.7.0、1.12 和 1.12.0,与 Unix 的版本检查保持一致。(从 PHP 8.4 合并上来;PR #24371)
PHP 8.4.x
Bug 修复
- ext/curl 保持回调接收者和闭包在回调执行完成前存活,允许 cURL 回调替换或清除自身,避免过早释放对象。(PR #23863, commit 9e7117f)
- ext/dom 修复
DOMNode::isEqualNode()比较深层嵌套 DOM 树时可能触发栈溢出的问题。(PR #23140) - ext/hash 修复
php_hash.h无法被 C++ 代码包含的问题。(PR #22940) - ext/intl 修复日历日期/时间 API 中越界的参数位置。(commit 10704f0)
- ext/intl 在
IntlDateFormatter错误信息中使用PATTERN常量名。(commit 2d78489) - ext/intl 修复未初始化日历参数的参数位置。(commit 27d593e)
- ext/soap 修复解码 SOAP 数组索引时的整数溢出。(PR #21963)
- ext/phar 在
Phar::addEmptyDir()中更正确地保留.phar魔术目录。(commit 3064540) - ext/intl 为无效显示类型同步
IntlTimeZone对象错误状态。(commit 9e68cf8) - ext/intl 保持
Spoofchecker限制级别 API 的 ICU 53 版本保护。(commit edde169) - ext/opcache 修复有类型按值返回时未解开引用包装的问题。(PR #21973)
- ext/intl 修复
transliterator_transliterate()错误信息中的参数位置。(commit d8e7418) - ext/phar 拒绝格式错误的 ZIP 额外字段,避免剩余长度下溢。(commit 657f0d6, PR #22330)
- ext/intl 让
Locale::lookup()和locale_lookup()在语言标签无效时返回null,而不是回退到默认 locale。(PR #22306) - ext/intl 让
Collator::__construct()和Spoofchecker::__construct()在重复构造已初始化对象时抛出Error,避免覆盖 ICU 句柄造成内存泄漏。(PR #22386) - ext/phar 修复以
.phar为前缀但不是魔术目录的目录处理。(PR #22372) base_convert()修复较小缓冲区长度下的转换结果错误。(PR #22406)- ext/zip 修复
ZipArchive::getFromIndex()忽略已删除条目的FL_UNCHANGED标志。(PR #21706) - ext/zip 避免重置
ZipArchive注释时发生崩溃。(PR #22652) - ext/readline 修复交互式 Shell 中的类常量自动补全功能。(PR #23218)
- ext/gd 修复
imageaffinematrixget()和imageaffinematrixconcat报错信息中参数位置错位的问题。(PR #23356) - ext/zip 修复当
ZipArchive子类保存了它自身打开的流时,该子类对象无法被垃圾回收的问题。(PR #23282) - ext/phar 修复访问已挂载 Phar 目录的子目录时可能触发的 use-after-free。(PR #23442)
- ext/phar 修复原生 Phar manifest 含有重复文件名时泄漏文件名和元数据的问题。(PR #23479)
- ext/intl 修复
grapheme_strstr()和grapheme_stristr()在匹配位置之前包含补充平面字符时返回错误子串的问题,涵盖两种beforeNeedle模式。(PR #24012) - ext/intl 修复
grapheme_strstr()和grapheme_strpos()漏掉 Unicode 规范等价匹配的问题,并避免字素搜索在空字符串中查找非空字符串时产生 ICU 错误。(PR #24029) - ext/intl 修复
grapheme_strrpos()和grapheme_strripos()在使用负偏移量时漏掉重叠匹配的问题。(PR #24028) - ext/intl 修复
grapheme_extract()从多字节 UTF-8 字符内部开始提取时返回错误的$next字节偏移量的问题。(PR #24160) - ext/readline 修复
readline_info()在completion_append_character含有非 ASCII 字节时可能崩溃的问题。(PR #24263) - ext/opcache 修复 tracing JIT 比较时的整数范围上界推断,避免对最小整数减一造成溢出,并保留正确的操作数上界。(PR #24633)
- ext/zip 在配置阶段排除 libzip 1.12 和 1.12.0,避免 TorrentZip 归档无法添加或替换文件的回归问题。(PR #24358)
- ext/zip 在 Windows 配置阶段拒绝已知有问题的 libzip 1.3.1、1.7.0、1.12 和 1.12.0,与 Unix 的版本检查保持一致。(PR #24371)
TODO
Zend 内核
新 API
- 添加 inline 的 helper function,分别是
- zend_bin2hex()
zend_bin2hex_upper()- zend_bin2hex_str()
这是因为内核里有很多扩展依赖 ext/hash,这本身没什么问题,但是这个依赖仅仅是要调用 ext/hash/php_hash 的 php_hash_bin2hex。这些功能很简单。所以我们可以把这些 bin2hex 的东西做成 Zend 里内置的 API,直接给 ext 使用。这样可以避免很多 ext 依赖于 ext/hash。
- 添加 inline 的 helper function,分别是
- zend_cstr_append_char
- zend_cstr_concat
- zend_cstr_concat3
现在,我们有很多 zend_str_* 的 helper 可以完成 zend_string 的拼接。但是对于 C char,利用这些 helper 会多进行一次内存的 allocation,导致性能下降,因此,所有对于 C char 拼接的场景我们都是用原始的 memchr 方式。这样会导致代码的可读性降低。我们可以实现这些 helper 来帮助我们进行 C char 的拼接。
我们还可以在这些 helper 里融入溢出检查(拼接的时候涉及到长度的整形相加),避免长度溢出导致 bug。
API 改进
- 重写
zend_string_concat2和zend_string_concat3。这两个 inline helper 被大量使用,却没有检查字符串长度是否超出边界。理想情况下我们应该重写这些函数,在拼接前检查一下长度之和是否超出字符串最大长度以防止溢出。 - 对以下函数
- ext/zlib deflate_init
- ext/zlib inflate_init
- …
用 zval_try_get_* 替换 zval_get_*。zval_get_*系列的函数,比如 zval_get_long,会直接强制把 zval 类型的参数转化为 long。这个强转甚至是静默的。所以在我看来任何人都不应该用这个函数(它应该被 deprecated)。我们应当审计所有该函数的应用场景并换成更严格的 zval_try_get_*,以在强制转换时抛出 TypeError/ValueError。
性能优化
- 对以下函数
- 优化 ext/zip php_zip_glob
- 优化 ext/zip php_zip_pcre
- 优化 ext/uri fill_errors
- …
php代码库中有很多地方采用 array_init 来初始化数组。这个函数会先计算参数的长度,分配内存再创建数组。很多情况下我们在初始化之前就知道数组的长度了。因此,可以使用 array_init_size 函数直接分配内存创建数组,省去计算长度的一部分以达到性能优化。
- 对以下函数
- 优化 ext/json php_json_pretty_print_indent
- 优化 ext/uri php_uri_parser_rfc3986_path_read
- 优化 ext/uri php_uri_parser_rfc3986_path_host
- …
这些函数中都多多少少涉及到利用循环的方式动态扩容字符串/数组长度的行为。我们可以使用分配(pre-allocation)的方式一次性将长度计算完全后直接分配所有内存,避免在循环体中多次动态扩容,以在复杂场景(即,假设使用优化前版本需要循环很多次的场景)下带来巨量优化。
ext
- php 的
password_verify函数里的密码会被 NUL 截断。这主要是因为 php 的 bcrypt 实现是完全用 C char 写的。我们应该整个用 zend_string 重写一遍这个实现 (ps:巨大工作量) - 对以下函数进行如下描述中的改动
- linkinfo
- putenv
- getenv
- proc_open
- parse_string
- …
检查所有 ext 的函数在参数中包含空 and/or 含 NUL 的字符串时是否能正常工作。如果不能,抛出 ValueError。
重写整个
RecursiveArrayIterator的逻辑。目前该类会存储 bucket pointer。这是一种相当危险的行为(i.e. 导致静默的内存泄露),而且已经使 UAF 在任意版本中的存在。OpenSSL 对于证书的处理用的是 libc 的
mktime,然后因为微软死了亲妈导致经典的 2038 问题,这个时间在 windows 系统上一旦超过 2038 年就会溢出。我们要重写这个实现避免使用微软的傻逼接口。declare(encoding=...)在 encoding 为 ASCII 或 UTF-8 这种不是 multibyte 的编码方式时,在zend.multibyte=0时仍会导致关于 multibyte 的 warning 出现。
RFC
- deprecate 掉 open_basedir。(我知道听起来很疯狂。我懒得解释为什么…)
- deprecate 掉
soundex函数。 - deprecate 掉 php://dechunk 的 filter。
- php 的 UUID 扩展。
研究
- __PHP_Incomplete_Class
- Reflection