PHP 8.4.25 - Nested Array And DOM Stack Guards


php-src published php-8.4.25 on 28 August 2026. PHP 8.4.25 is a bug fix release. Recursive array comparison and several standard library walks no longer overflow the native stack on deeply nested documents.

The full release notes and downloads are on the GitHub release page. The canonical change list is the PHP 8.4.25 ChangeLog.

ETL jobs that decode JSON or merge config trees often walk the result with array_walk_recursive(), array_replace_recursive(), compact(), or count() with COUNT_RECURSIVE. Those paths overflowed the C stack when the document was deep enough. GH-23088 is the same class of failure on array equality.

A worker that used to die in the engine now survives the call. That is a net win for FPM pools and CLI batch runners that ingest untrusted nested payloads. The cost is that a tree which previously crashed the process now fails in userland, so retry logic that keyed off a dead worker will not see the same signal.

None of this is a new API. The functions keep their signatures. The change is only that depth is bounded by the engine stack limit instead of by luck.

The DOM extension had the same overflow on document walks that scrapers and CMS importers actually run. GH-23116 is normalize() on a deeply nested DOMDocument. GH-23117 is the same call on Dom\XMLDocument. GH-23120 is DOMNode::isEqualNode() on a deep tree. Canonicalize or diff a large feed and those methods are on the hot path.

Two correctness bugs sit next to the stack work. DOMElement::setAttribute() failed silently when the DTD already declared a default value for that attribute (GH-22825). Setting an attribute node that collides by local name with a namespaced attribute used after free in dom_objects_free_storage (GH-22447).

XSL has a related lifetime bug. A DOMDocument subclass __clone() that kept the stylesheet copy made by XSLTProcessor::importStylesheet() could use after free. Transform pipelines that clone the document after import are the blast radius.

GH-21134 was a crash with \C plus UTF-8. The fix is a ban. \C in UTF-8 patterns is now forbidden.

That is the one behavior change in an otherwise bug fix tag. Regex that matched a single byte inside a UTF-8 subject will fail to compile after this upgrade. Search job configs, CMS filters, and validation rules for \\C before rolling the package. Patterns that are not UTF-8 are outside this restriction.

mb_strrpos() returned the wrong position for a negative offset in a non UTF-8 encoding (GH-22779). mb_ereg_search_getregs() crashed after mb_eregi() invalidated the regex cache (GH-21036). Encoding bugs of that kind show up in log parsers and feed cleaners more than in application CRUD.

Function JIT emitted wrong code for FETCH_OBJ_FUNC_ARG on a property hook getter and dropped register held variables (GH-22857). zend_jit_deoptimizer_start() now preserves parent registers (GH-22916). That second fix is internal deoptimizer bookkeeping. Workloads that set function JIT and then call PHP 8.4 property hook getters should retest those getters. Tracing JIT is not named in the notes.

User stream filters that touch $this->stream during the close flush used after free (GH-15836). Closing a SQLite database while a blob stream is still open leaked. shm_attach() wrote out of bounds when the requested size was larger than an existing segment. Session mod_mm corruption and a broken session id path that corrupted zend_mm_heap (GH-23043) are in the same bucket of process lifetime defects.

Libxml 2.15 failed the PHP build (GH-22752). Operators compiling from source against a current distro libxml need this tag. OpenSSL now checks the error from an invalid ALPN protocol list. That is a missing check, not a new API. The notes also list memory fixes in ext/sockets without naming the functions.

No migration guide shipped with this tag. Treat it as a drop in 8.4 patch except for PCRE. \C inside UTF-8 patterns no longer compiles.

Source tarballs are on the downloads page. Windows source and binaries are on the Windows 8.4 downloads page. The GitHub tag is a stable release, not a candidate.