PHP 8.4.18 RC: What’s Fixed and Why You Should Test It
If you’ve been running the 8.4 series in production, the new release candidate brings enough core‑level bug squashing to merit a quick spin before the final launch. In this article I’ll point out the fixes that actually matter for everyday code, show where you might still hit trouble, and give a few tips on how to verify the changes on your own box.
Core bugs that finally stop breaking things
The biggest pain points in the last few releases were obscure use‑after‑free (UAF) errors that showed up only under very specific conditions. Two of them are now gone:
- NULL dereference in ob_start() during shutdown – I’ve seen this happen after a badly behaved driver update that forced PHP to bail out while a buffer was still being flushed. The crash manifested as “Segmentation fault (core dumped)” right at the end of the script, leaving no log entry. The fix removes the null pointer deref, so graceful shutdown works again even if ob_start() is called from inside a shutdown function.
- UAF in FE_FREE with GC interaction – This one was a nightmare for long‑running daemons that relied on cyclic garbage collection. Occasionally the engine would free an internal frame and then later try to read it, causing intermittent segfaults that were impossible to reproduce locally. The patch adds proper reference counting before freeing.
Both issues were flagged in GitHub as GH‑20837 and GH‑20766 respectively; they’re now cleared, which means your CLI workers and FPM pools should stay up longer without mysterious restarts.
OSS‑Fuzz finds the hidden loops
A handful of bugs were only exposed by Google’s fuzzing service. They don’t affect most developers directly, but they hint at deeper stability problems:
- Infinite loop in GC destructor for fibers – If you ever used Fiber::suspend() inside an object that also implements a custom destructor, the engine could spin forever. The new guard breaks out of the loop after a safe timeout.
- Borked block_pass JMP[N]Z optimization – This optimizer bug caused random crashes when a function returned by reference and the caller immediately discarded the result. The fix disables the faulty optimization path.
If you’re experimenting with fibers or heavy JIT code, give these a quick test run: php -d opcache.jit=1205 your_script.php and watch for any “Maximum execution time exceeded” messages that didn’t exist before.
Date handling gets a minor bump
Timelib was updated to 2022.16. The change is mostly about keeping the leap‑second table current, so you’ll see correct offsets for a few obscure historic zones (think “Europe/Moscow” in early 1990s). If your app parses old logs, this update alone can save you from subtle hour‑off errors.
DOM quirks fixed
The HTMLDocument parser used to corrupt closing tags that appeared inside <script> blocks – a classic case of “the parser got confused by angle brackets”. After the fix (GH‑21041) scripts that generate their own markup no longer break the surrounding document. If you’ve ever built an editor that injects <script> snippets into a live DOM, run your test suite again; you should see fewer malformed trees.
MbString edge cases
Two bugs made mbstring surprisingly fragile:
- Divide‑by‑zero in mb_str_pad() when the padding string is invalid – Passing an empty or non‑convertible padding string threw a warning and then crashed. The new guard returns early with a clean error instead.
- Stack overflow with recursive array references in mb_convert_variables – The conversion routine would recurse forever until the stack blew. Now it detects the recursion depth and aborts gracefully.
If you rely on multibyte string padding for table formatting, add an assert that your pad string is non‑empty before calling mb_str_pad().
Opcache JIT gets a safety net
The tracing JIT crashed when an object reference escaped the compiled trace (GH‑20818). This manifested as a segfault only when a method returned $this and the caller immediately chained another call. The patch adds a runtime check that forces deoptimization in those cases, so the engine falls back to interpreter mode instead of blowing up.
OpenSSL memory‑leak cleanup
Three separate crashes were tied to error paths inside openssl_x509_parse(). In production environments where certificate parsing fails frequently (think user‑uploaded certs), you could see a slow leak that eventually exhausted PHP’s heap. The new code releases the temporary structures even when i2s_ASN1_INTEGER() or X509_NAME_oneline() fail, keeping memory usage predictable.
Miscellaneous wins
- var_dump() no longer crashes on deeply nested objects – A nasty bug (GH‑20843) that could bring down debugging sessions.
- SplDoublyLinkedList iterator is safe when you modify the list during iteration – The old heap‑use‑after‑free (GH‑20856) caused random “Corrupted double-linked list” errors.
- Readline settings leak fixed, Phar builder now respects missing base directories, and a few other small but annoying quirks are gone.
How to give the RC a quick spin
- Pull the source or download the tarball from php.net.
- Compile with your usual configure flags, but add --enable-opcache-jit if you want to test the JIT fixes.
- Run the regression suite that ships with PHP: make test. It will surface any remaining crashes on your platform.
- For a real‑world sanity check, fire up an FPM pool and hit it with a script that exercises each of the areas above (ob_start in shutdown, fibers, mbstring padding, DOM parsing). Watch the error log – you should see none of the previous “Segmentation fault” lines.
If everything stays quiet, you’re good to roll the RC into your staging environment. Keep an eye on the PHP bug tracker for any new regressions that might surface once more users start testing.
That’s it – a handful of solid fixes and a few quirks still worth watching. Happy debugging!
Release php-8.4.18RC1
Tag for php-8.4.18RC1
