PHP 8.5.10 升级要点:深层嵌套不再栈溢出、PDO_ODBC NULL 垃圾值修正、UTF-8 正则禁用 \C
PHP 8.5.10(2026-08-27)是 8.5 分支的 bugfix 版本,改动集中在递归/深层嵌套导致的崩溃、PDO 驱动取值正确性、非 UTF-8 字符串处理、Opcache JIT 以及一批释放后使用问题。以下按升级时容易直接踩中的模块整理,GH 编号可到 php.net changelog 对应 issue 核对。
深层嵌套:数组比较、递归数组函数、DOM 规范化
这次最集中的一类修复,是在深层嵌套数据上执行递归操作导致的栈溢出。
- Core GH-23088:深层嵌套数组比较栈溢出
- Standard GH-23111:
array_walk_recursive()处理深层嵌套数组栈溢出 - Standard GH-23113:
array_replace_recursive()同样问题 - Standard GH-23115:
compact()遇深层嵌套数组栈溢出 - DOM GH-23116:
DOMDocument::normalize()栈溢出 - DOM GH-23117:
Dom\XMLDocument::normalize()栈溢出 - DOM GH-23120:
DOMNode::isEqualNode()比较深层节点栈溢出
外部 JSON、XML、YAML 反序列化后如果嵌套层数不可控,并且后续有比较、递归遍历或 DOM 规范化操作,升级前建议重点回归。
同版 DOM 还修了一个不那么显眼的问题:GH-22825。当 DTD 给属性声明了默认值时,DOMElement::setAttribute() 会静默失败,现在能正确设置并反映到节点上。
正则与多字节字符串:\C 行为变更、偏移量错误
- PCRE GH-21134:
\C配合 UTF-8 模式会崩溃,PHP 8.5.10 起禁止在 UTF-8 pattern 中使用\C - MBString GH-22779:
mb_strrpos()在非 UTF-8 编码下使用负偏移量时返回错误位置 - MBString GH-21036:
mb_eregi()使正则缓存失效后,再调mb_ereg_search_getregs()会崩溃
\C 的改动是这版里少数属于行为变更的修复:
// 旧版在特定输入下可能崩溃(GH-21134)
preg_match('/\C/u', $input);
// 8.5.10 起:UTF-8 pattern 中不再允许 \C,需要按字符语义改写
代码里还有 /u 与 \C 组合的话,升级后会从“偶发崩溃”变成“编译/运行时报错”,需要提前清掉。
非 UTF-8 环境下使用 mb_strrpos() 且传入负 offset 的代码,返回结果会变化,建议对着固定用例做断言回归。
PDO:ODBC 长列 NULL 与 PgSQL lazy fetch
PDO_ODBC
GH-23016:ODBC 长列中的 NULL 值会以垃圾二进制串形式返回,8.5.10 修正为返回 NULL。受影响的是读取 LONG/VARCHAR 等长列且允许为 NULL 的查询。如果之前为绕开垃圾值写过类型判断或字符串过滤,升级后可以简化。
PDO_PGSQL
PDO_PGSQL 修了一批 PDO::ATTR_PREFETCH => 0(lazy fetch)下的缺陷,集中在语句生命周期管理:
- 清理一个遗留在 COPY 过程中的 fetch 时可能无限循环
- 使用 emulated prepares 或禁用 prepares 时销毁 statement 会触发 use-after-free
- fetch 结束后连接仍处于 busy 状态,影响下一次 fetch
- 某些场景下会从 statement 取出另一个 statement 接管结果后的行
启用 lazy fetch 并且涉及 PostgreSQL COPY、自定义 prepare 开关的代码,升级后要重点跑语句销毁与结果集切换路径。
Opcache JIT
JIT 修了 4 个代码生成问题:
- GH-22693:x86_64 上 JIT 生成的 TLS 访问出现 DT_TEXTREL
- GH-22763:设置寄存器后未清除
ZREG_TYPE_ONLY,导致类型状态残留 - GH-22857:带 property hook getter 的
FETCH_OBJ_FUNC_ARG生成错误代码,丢失寄存器持有的变量 - GH-22916:
zend_jit_deoptimizer_start()未保留父寄存器
这些基本是“错误代码生成 → 随机崩溃/错误结果”一类。生产环境开启 JIT 的,升级后先跑属性 hook 和函数调用密集的回归。
释放后使用与生命周期问题
- Core GH-22782:preloading 下常量表达式中的 first-class callable(如
strlen(...))崩溃 - Streams GH-15836:用户流过滤器在 close flush 期间访问
$this->stream触发 use-after-free - XSL:
DOMDocument子类的__clone()保留了XSLTProcessor::importStylesheet()制作的样式表副本,导致 use-after-free - Date:重复调用
DatePeriod::__construct()造成泄漏 - Session:
mod_mm存储损坏;GH-23043 中错误的 session id 会破坏zend_mm_heap - SQLite:blob stream 仍打开时关闭数据库会泄漏
- OpenSSL:无效 ALPN 协议列表缺少错误检查
- Exif:
exif_read_data()会按超出源文件大小的 HEIF meta box 分配缓冲区 - Intl:
IntlListFormatter::__construct()成功调用后仍残留旧的全局错误状态 - Sockets:
socket_set_option()对UDP_SEGMENT与SO_LINGER的校验错误信息修正,另有若干内存问题修复
自定义 stream filter、DOMDocument 子类 clone、preload 常量表达式属于低频但崩溃特征明显的路径,升级后值得单独回归。
Reflection 与 SimpleXML 边界行为
Reflection 三个修复:
- GH-22905:反射异常消息在 null 字节处截断,现可输出完整消息
ReflectionProperty::isLazy()与skipLazyInitialization():子类 hook 继承属性时错误使用父类槽位ReflectionMethod::createFromMethodName()在不可实例化子类上段错误
依赖反射做框架初始化、属性懒加载(lazy object)的代码,注意子类继承属性时的行为变化。
SimpleXML 两个修复:
- 无法解析的整数元素偏移现在不会错误别名到已有元素
- 比较未初始化的
SimpleXMLElement实例不再段错误
升级与自查点
这版 API 层面没有迁移项,但以下调用路径的行为有实际变化,建议在 CI 里补一轮回归:
- 深层嵌套数组的
==比较、array_walk_recursive()、array_replace_recursive()、compact() - DOM 的
normalize()、isEqualNode()、带 DTD 默认属性时的setAttribute() - 正则中
/u与\C的组合 - 非 UTF-8 编码下
mb_strrpos()的负偏移量 mb_eregi()与mb_ereg_search_getregs()混用的代码- PDO_ODBC 长列 NULL 取值
- PDO_PGSQL 的 lazy fetch、COPY、prepares 开关组合
- 开启 JIT 后涉及 property hook getter 的调用
- 自定义用户流过滤器的 close flush 路径
XSLTProcessor::importStylesheet()配合 DOMDocument 子类 clone 的场景- Reflection 异常消息、lazy 属性在子类 hook 继承属性时的表现
改动集中在“以前崩溃、现在正常”和“返回值修正”两类。行为变化的范围明确(主要是 \C 禁用),可以按常规 bugfix 版本节奏合入。