[php-src] master: Merge branch 'PHP-8.6'
| From: | David Carlier | Date: | Sat, 24 Oct 2026 12:16:11 +0000 |
| Subject: | [php-src] master: Merge branch 'PHP-8.6' | ||
| Groups: | php.cvs | ||
| Request: | Send a blank email to php-cvs+get-142669@lists.php.net to get a copy of this message | ||
Author: David Carlier (devnexen)
Date: 2026-10-24T13:10:45+01:00
Commit: https://github.com/php/php-src/commit/29b5e168bfa71e94df2307dcd32be2a4f6353e9d
Raw diff: https://github.com/php/php-src/commit/29b5e168bfa71e94df2307dcd32be2a4f6353e9d.diff
Merge branch 'PHP-8.6'
* PHP-8.6:
Fix GH-24582: Memory leaks in ext/ffi parser when finalize_type() bails out
Changed paths:
A ext/ffi/tests/finalize_type_bailout_leak.phpt
M ext/ffi/ffi.c
M ext/ffi/ffi.g
M ext/ffi/ffi_parser.c
M ext/ffi/php_ffi.h
Diff:
diff --git a/ext/ffi/ffi.c b/ext/ffi/ffi.c
index 3a3ef149a3f3..a4408628380b 100644
--- a/ext/ffi/ffi.c
+++ b/ext/ffi/ffi.c
@@ -3675,6 +3675,11 @@ static void zend_ffi_throw_parser_error(const char *format, ...) /* {{{ */
va_list va;
char *message = NULL;
+ if (FFI_G(parse_failed)) {
+ /* An error was already reported, parsing only continues to unwind cleanly */
+ return;
+ }
+
va_start(va, format);
zend_vspprintf(&message, 0, format, va);
@@ -5750,20 +5755,22 @@ ZEND_GET_MODULE(ffi)
/* parser callbacks */
void zend_ffi_parser_error(const char *format, ...) /* {{{ */
{
- va_list va;
- char *message = NULL;
+ if (!FFI_G(parse_failed)) {
+ va_list va;
+ char *message = NULL;
- va_start(va, format);
- zend_vspprintf(&message, 0, format, va);
+ va_start(va, format);
+ zend_vspprintf(&message, 0, format, va);
- if (EG(current_execute_data)) {
- zend_throw_exception(zend_ffi_parser_exception_ce, message, 0);
- } else {
- zend_error(E_WARNING, "FFI Parser: %s", message);
- }
+ if (EG(current_execute_data)) {
+ zend_throw_exception(zend_ffi_parser_exception_ce, message, 0);
+ } else {
+ zend_error(E_WARNING, "FFI Parser: %s", message);
+ }
- efree(message);
- va_end(va);
+ efree(message);
+ va_end(va);
+ }
LONGJMP(FFI_G(bailout), FAILURE);
}
@@ -5849,10 +5856,16 @@ static void zend_ffi_finalize_type(zend_ffi_dcl *dcl) /* {{{ */
case ZEND_FFI_DCL_FLOAT|ZEND_FFI_DCL_COMPLEX:
case ZEND_FFI_DCL_DOUBLE|ZEND_FFI_DCL_COMPLEX:
case ZEND_FFI_DCL_DOUBLE|ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_COMPLEX:
- zend_ffi_parser_error("Unsupported type _Complex at line %d", FFI_G(line));
+ zend_ffi_throw_parser_error("Unsupported type _Complex at line %d", FFI_G(line));
+ /* Do not bail out here: callers may hold memory that is not owned by dcl yet.
+ * Continue with a placeholder type, zend_ffi_parse_*() report the failure. */
+ FFI_G(parse_failed) = 1;
+ dcl->type = (zend_ffi_type*)&zend_ffi_type_sint32;
break;
default:
- zend_ffi_parser_error("Unsupported type specifier combination at line %d",
FFI_G(line));
+ zend_ffi_throw_parser_error("Unsupported type specifier combination at line %d",
FFI_G(line));
+ FFI_G(parse_failed) = 1;
+ dcl->type = (zend_ffi_type*)&zend_ffi_type_sint32;
break;
}
dcl->flags &= ~ZEND_FFI_DCL_TYPE_SPECIFIERS;
diff --git a/ext/ffi/ffi.g b/ext/ffi/ffi.g
index 9ab8ee404edf..ceddcb409a64 100644
--- a/ext/ffi/ffi.g
+++ b/ext/ffi/ffi.g
@@ -886,9 +886,15 @@ zend_result zend_ffi_parse_decl(const char *str, size_t len) {
FFI_G(attribute_parsing) = 0;
yy_buf = (unsigned char*)str;
yy_end = yy_buf + len;
+ FFI_G(parse_failed) = 0;
parse();
+ if (FFI_G(parse_failed)) {
+ FFI_G(parse_failed) = 0;
+ return FAILURE;
+ }
return SUCCESS;
} else {
+ FFI_G(parse_failed) = 0;
return FAILURE;
}
}
@@ -902,13 +908,19 @@ zend_result zend_ffi_parse_type(const char *str, size_t len, zend_ffi_dcl
*dcl)
yy_pos = yy_text = yy_buf = (unsigned char*)str;
yy_end = yy_buf + len;
yy_line = 1;
+ FFI_G(parse_failed) = 0;
sym = parse_type_name(get_sym(), dcl);
if (sym != YY_EOF) {
yy_error_sym("<EOF> expected, got", sym);
}
zend_ffi_validate_type_name(dcl);
+ if (FFI_G(parse_failed)) {
+ FFI_G(parse_failed) = 0;
+ return FAILURE;
+ }
return SUCCESS;
} else {
+ FFI_G(parse_failed) = 0;
return FAILURE;
};
}
diff --git a/ext/ffi/ffi_parser.c b/ext/ffi/ffi_parser.c
index 27f1469133fe..0acf698b161a 100644
--- a/ext/ffi/ffi_parser.c
+++ b/ext/ffi/ffi_parser.c
@@ -3621,9 +3621,15 @@ zend_result zend_ffi_parse_decl(const char *str, size_t len) {
FFI_G(attribute_parsing) = 0;
yy_buf = (unsigned char*)str;
yy_end = yy_buf + len;
+ FFI_G(parse_failed) = 0;
parse();
+ if (FFI_G(parse_failed)) {
+ FFI_G(parse_failed) = 0;
+ return FAILURE;
+ }
return SUCCESS;
} else {
+ FFI_G(parse_failed) = 0;
return FAILURE;
}
}
@@ -3637,13 +3643,19 @@ zend_result zend_ffi_parse_type(const char *str, size_t len, zend_ffi_dcl
*dcl)
yy_pos = yy_text = yy_buf = (unsigned char*)str;
yy_end = yy_buf + len;
yy_line = 1;
+ FFI_G(parse_failed) = 0;
sym = parse_type_name(get_sym(), dcl);
if (sym != YY_EOF) {
yy_error_sym("<EOF> expected, got", sym);
}
zend_ffi_validate_type_name(dcl);
+ if (FFI_G(parse_failed)) {
+ FFI_G(parse_failed) = 0;
+ return FAILURE;
+ }
return SUCCESS;
} else {
+ FFI_G(parse_failed) = 0;
return FAILURE;
};
}
diff --git a/ext/ffi/php_ffi.h b/ext/ffi/php_ffi.h
index d56dc5a5552f..ef2513d8cda3 100644
--- a/ext/ffi/php_ffi.h
+++ b/ext/ffi/php_ffi.h
@@ -55,6 +55,7 @@ ZEND_BEGIN_MODULE_GLOBALS(ffi)
bool allow_vla;
bool attribute_parsing;
bool persistent;
+ bool parse_failed;
uint32_t default_type_attr;
ZEND_END_MODULE_GLOBALS(ffi)
diff --git a/ext/ffi/tests/finalize_type_bailout_leak.phpt
b/ext/ffi/tests/finalize_type_bailout_leak.phpt
new file mode 100644
index 000000000000..11b487806b09
--- /dev/null
+++ b/ext/ffi/tests/finalize_type_bailout_leak.phpt
@@ -0,0 +1,64 @@
+--TEST--
+FFI: no memory leak when zend_ffi_finalize_type() bails out inside a declarator
+--EXTENSIONS--
+ffi
+--INI--
+ffi.enable=1
+--FILE--
+<?php
+
+function leaks(string $code): bool {
+ $n = 200;
+ for ($i = 0; $i < 20; $i++) {
+ try { FFI::cdef($code); } catch (FFI\ParserException) {}
+ }
+ $before = memory_get_usage();
+ for ($i = 0; $i < $n; $i++) {
+ try { FFI::cdef($code); } catch (FFI\ParserException) {}
+ }
+ /* A leaked block is at least 16 bytes, so this catches a leak per call */
+ return memory_get_usage() - $before >= $n;
+}
+
+$cases = [
+ // zend_ffi_make_pointer_type()
+ 'double _Complex *x;',
+ 'double _Complex **x;',
+ // zend_ffi_make_func_type()
+ 'double _Complex f(int a, int b);',
+ // zend_ffi_add_arg(): first and later argument
+ 'void f(double _Complex x);',
+ 'void f(int a, double _Complex x);',
+ // zend_ffi_nested_declaration()
+ 'double _Complex (x[3]);',
+ 'double _Complex (f(int a));',
+ // parsing continues after the error: only the first one is reported
+ 'double _Complex x; int y;',
+ 'struct S { double _Complex a; int b; };',
+ 'int x; double _Complex y; double _Complex z;',
+];
+
+foreach ($cases as $code) {
+ try {
+ FFI::cdef($code);
+ echo "no error: $code\n";
+ } catch (FFI\ParserException $e) {
+ echo $e->getMessage(), "\n";
+ }
+ if (leaks($code)) {
+ echo "leak: $code\n";
+ }
+}
+
+?>
+--EXPECT--
+Unsupported type _Complex at line 1
+Unsupported type _Complex at line 1
+Unsupported type _Complex at line 1
+Unsupported type _Complex at line 1
+Unsupported type _Complex at line 1
+Unsupported type _Complex at line 1
+Unsupported type _Complex at line 1
+Unsupported type _Complex at line 1
+Unsupported type _Complex at line 1
+Unsupported type _Complex at line 1