diff --git a/NEWS b/NEWS index ea8935bd64ae..547ef1232f08 100644 --- a/NEWS +++ b/NEWS @@ -86,6 +86,8 @@ PHP NEWS - FFI: . Fixed bug GH-24582 (Memory leaks in ext/ffi parser when finalize_type() bails out). (matyapiro31, David Carlier) + . Fixed calling conventions inside a nested declarator, such as + "void (__stdcall *f)(void)", being ignored or rejected. (Nicolas Grekas) - FPM: . Fixed bug GH-24229 (fcgi_close() passes shutdown() a literal 1, which is diff --git a/ext/ffi/ffi.c b/ext/ffi/ffi.c index 73e738202c8d..cfffad70a87f 100644 --- a/ext/ffi/ffi.c +++ b/ext/ffi/ffi.c @@ -6497,6 +6497,22 @@ void zend_ffi_make_func_type(zend_ffi_dcl *dcl, HashTable *args, zend_ffi_dcl *n } } + /* The calling convention can be inside the parentheses, as in "void (__stdcall *f)(void)" */ + if (nested_dcl->abi != ZEND_FFI_ABI_DEFAULT) { + if (dcl->abi != ZEND_FFI_ABI_DEFAULT) { + zend_ffi_cleanup_dcl(nested_dcl); + zend_ffi_cleanup_dcl(dcl); + if (args) { + zend_hash_destroy(args); + pefree(args, FFI_G(persistent)); + } + zend_ffi_parser_error("Multiple calling convention specifiers at line %d", FFI_G(line)); + return; + } + dcl->abi = nested_dcl->abi; + nested_dcl->abi = ZEND_FFI_ABI_DEFAULT; + } + #ifdef HAVE_FFI_VECTORCALL_PARTIAL if (dcl->abi == ZEND_FFI_ABI_VECTORCALL && args) { zend_ulong i; diff --git a/ext/ffi/ffi.g b/ext/ffi/ffi.g index 8102e8f73a87..59bdc9950d67 100644 --- a/ext/ffi/ffi.g +++ b/ext/ffi/ffi.g @@ -798,6 +798,11 @@ nested_declarator_start: | "__attribute" | "__attribute__" | "__declspec" + | "__cdecl" + | "__stdcall" + | "__fastcall" + | "__thiscall" + | "__vectorcall" | "*" | "(" | "[" diff --git a/ext/ffi/ffi_parser.c b/ext/ffi/ffi_parser.c index bda13d323eda..aa365becc43f 100644 --- a/ext/ffi/ffi_parser.c +++ b/ext/ffi/ffi_parser.c @@ -1807,6 +1807,21 @@ static int check_nested_declarator_start(int sym) { case YY___DECLSPEC: sym = get_sym(); break; + case YY___CDECL: + sym = get_sym(); + break; + case YY___STDCALL: + sym = get_sym(); + break; + case YY___FASTCALL: + sym = get_sym(); + break; + case YY___THISCALL: + sym = get_sym(); + break; + case YY___VECTORCALL: + sym = get_sym(); + break; case YY__STAR: sym = get_sym(); break; diff --git a/ext/ffi/tests/callconv_nested_declarator.phpt b/ext/ffi/tests/callconv_nested_declarator.phpt new file mode 100644 index 000000000000..f6a911bd4233 --- /dev/null +++ b/ext/ffi/tests/callconv_nested_declarator.phpt @@ -0,0 +1,63 @@ +--TEST-- +FFI: calling convention inside a nested declarator +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +--FILE-- + [ + 'typedef void %s(%s *F)(int);', + 'F', + fn (FFI\CType $type) => $type->getPointerType(), + ], + 'parameter declarator' => [ + 'typedef void F(void %s(%s *cb)(int));', + 'F*', + fn (FFI\CType $type) => $type->getPointerType()->getFuncParameterType(0)->getPointerType(), + ], + 'abstract declarator' => [ + '', + 'void %s(%s *)(int)', + fn (FFI\CType $type) => $type->getPointerType(), + ], +]; + +function func_abi(array $case, string $before, string $inside): int|string { + [$decl, $type, $func] = $case; + try { + $ffi = FFI::cdef(sprintf($decl, $before, $inside)); + $type = $ffi->type(sprintf($type, $before, $inside)); + return $func($type)->getFuncABI(); + } catch (FFI\ParserException $e) { + return $e->getMessage(); + } +} + +foreach ($cases as $name => $case) { + echo "$name:"; + foreach (['__cdecl', '__stdcall', '__fastcall', '__thiscall', '__vectorcall', '__attribute__((fastcall))'] as $callconv) { + $expected = func_abi($case, "$callconv ", ''); + $actual = func_abi($case, '', "$callconv "); + if ($expected !== $actual) { + echo "\n $callconv: expected ", var_export($expected, true), ", got ", var_export($actual, true); + } + } + echo " done\n"; +} + +try { + FFI::cdef('typedef void __cdecl (__cdecl *F)(int);'); +} catch (FFI\ParserException $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECT-- +declarator: done +parameter declarator: done +abstract declarator: done +Multiple calling convention specifiers at line 1