Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 26 additions & 13 deletions ext/ffi/ffi.c
Original file line number Diff line number Diff line change
Expand Up @@ -3644,6 +3644,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);

Expand Down Expand Up @@ -5752,20 +5757,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);
}
Expand Down Expand Up @@ -5851,10 +5858,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;
Expand Down
12 changes: 12 additions & 0 deletions ext/ffi/ffi.g
Original file line number Diff line number Diff line change
Expand Up @@ -890,9 +890,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;
}
}
Expand All @@ -906,13 +912,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;
};
}
Expand Down
12 changes: 12 additions & 0 deletions ext/ffi/ffi_parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -3623,9 +3623,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;
}
}
Expand All @@ -3639,13 +3645,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;
};
}
Expand Down
1 change: 1 addition & 0 deletions ext/ffi/php_ffi.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,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)

Expand Down
64 changes: 64 additions & 0 deletions ext/ffi/tests/finalize_type_bailout_leak.phpt
Original file line number Diff line number Diff line change
@@ -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
Loading