[php-src] PR #24591: Fix GH-24582: Memory leaks in ext/ffi parser when finalize_type() bails out
| From: | matyapiro31 | Date: | Fri, 23 Oct 2026 09:07:41 +0000 |
| Subject: | [php-src] PR #24591: Fix GH-24582: Memory leaks in ext/ffi parser when finalize_type() bails out | ||
| Groups: | php.git-pulls | ||
| Request: | Send a blank email to git-pulls+get-39756@lists.php.net to get a copy of this message | ||
Pull Request: https://github.com/php/php-src/pull/24591
Author: matyapiro31
### Problem
zend_ffi_finalize_type() calls zend_ffi_parser_error() for unsupported
specifier combinations, which always ends in LONGJMP(FFI_G(bailout), FAILURE). It only
does so when dcl->type == NULL, so dcl owns nothing, but four callbacks
own other memory at that point:
| callback | leaked |
|---|---|
| zend_ffi_make_pointer_type() | the type it had just allocated |
| zend_ffi_make_func_type() | args, nested_dcl |
| zend_ffi_add_arg() | *args, including the arguments collected so far |
| zend_ffi_nested_declaration() | nested_dcl |
Reproducer (a debug build reports it at shutdown):
```php
FFI::cdef("double _Complex *x;"); // FFI\ParserException: Unsupported type _Complex
```
### Change
- zend_ffi_try_finalize_type() throws the parser exception and returns
FAILURE, like the zend_ffi_validate_* helpers.
zend_ffi_finalize_type() stays as a wrapper that bails out, so the other callers are
not touched.
- The four callbacks use the new function and release what they own before LONGJMP,
with the same calls the later error paths of the same functions already use.
- zend_ffi_make_pointer_type() allocates after the checks, as
zend_ffi_make_array_type() does.
### Testing
Debug ZTS build on 8.4.28-dev (Windows, Visual C++ 2022):
- New test ext/ffi/tests/finalize_type_bailout_leak.phpt checks the exception message
and that memory_get_usage() does not grow, so it also detects the leak on non-debug
builds. Without the fix it reports a leak for all seven declarations; with the fix it passes.
- ext/ffi suite: 87 passed, 0 failed (24 skipped, no libc.so.6 on
Windows).
- Double free: the default debug allocator does not detect a double free (I checked with a
deliberate one), so I also ran the suite and 42 declarations x 300 runs with
USE_ZEND_ALLOC=0: no crash.
- 42 declarations (nested declarators, function pointers, struct fields, typedefs, valid controls),
one process each: unpatched 33 leak, patched 10; 23 fully fixed, 7 reduced, none worse.
### Not covered
Ten of those declarations still leak. The cause is different: a callee bails out while an enclosing
declarator (nested_dcl of parse_declarator(), or the dcl of
an outer *) owns a type the callee cannot reach, for example double _Complex
(*x)[3];, int *f(double _Complex x); and, without _Complex,
int *f(@);. A proper fix needs to be at the parser level; see the discussion in
GH-24582. If that is the direction, the cleanups here have to be dropped with it to avoid releasing
a type twice.
The persistent (ffi.preload) path was not run (no opcache in my build); the new code
uses the same FFI_G(persistent) expressions as the existing error paths.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
✔ Checked by me.