diff --git a/ext/ffi/ffi.c b/ext/ffi/ffi.c index 79783441c1db..73e738202c8d 100644 --- a/ext/ffi/ffi.c +++ b/ext/ffi/ffi.c @@ -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); @@ -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); } @@ -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; diff --git a/ext/ffi/ffi.g b/ext/ffi/ffi.g index 364837242890..8102e8f73a87 100644 --- a/ext/ffi/ffi.g +++ b/ext/ffi/ffi.g @@ -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; } } @@ -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(" 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 26d623a40290..bda13d323eda 100644 --- a/ext/ffi/ffi_parser.c +++ b/ext/ffi/ffi_parser.c @@ -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; } } @@ -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(" 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 430b8a2e568f..69ee099528f5 100644 --- a/ext/ffi/php_ffi.h +++ b/ext/ffi/php_ffi.h @@ -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) 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-- += $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