From 40f3a6b9458fd6217823bdeb662c198521efe85b Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 21 Sep 2026 09:56:52 +0200 Subject: [PATCH] Let extensions accept Io\Poll\Handle objects php_poll.h exports php_poll_handle_get_fd() and the handle object struct, but not the class entry of the interface, so an extension cannot check that an object is a handle before running PHP_POLL_HANDLE_OBJ_FROM_ZV() on it without looking the class up by name. Export the entry and a resolver for parameters that accept more than a handle. php_poll_handle_default_ops was declared without PHPAPI, so an extension providing its own handle type could not link it on Windows. --- ext/standard/io_poll.c | 11 +++++---- ext/zend_test/test.c | 18 +++++++++++++++ ext/zend_test/test.stub.php | 6 +++++ ext/zend_test/test_arginfo.h | 8 ++++++- ext/zend_test/test_decl.h | 8 +++---- ext/zend_test/test_legacy_arginfo.h | 8 ++++++- ext/zend_test/tests/io_poll_handle_api.phpt | 25 +++++++++++++++++++++ main/php_poll.h | 8 ++++++- main/poll/poll_handle.c | 15 ++++++++++++- 9 files changed, 93 insertions(+), 14 deletions(-) create mode 100644 ext/zend_test/tests/io_poll_handle_api.phpt diff --git a/ext/standard/io_poll.c b/ext/standard/io_poll.c index a8a0563627fa..7875ee414d68 100644 --- a/ext/standard/io_poll.c +++ b/ext/standard/io_poll.c @@ -26,7 +26,6 @@ static zend_class_entry *php_io_poll_backend_class_entry; static zend_class_entry *php_io_poll_event_class_entry; static zend_class_entry *php_io_poll_context_class_entry; static zend_class_entry *php_io_poll_watcher_class_entry; -static zend_class_entry *php_io_poll_handle_class_entry; static zend_class_entry *php_io_exception_class_entry; static zend_class_entry *php_io_poll_exception_class_entry; static zend_class_entry *php_io_poll_failed_backend_unavailable_class_entry; @@ -709,7 +708,7 @@ PHP_METHOD(Io_Poll_Context, add) zval *data = NULL; ZEND_PARSE_PARAMETERS_START(2, 3) - Z_PARAM_OBJECT_OF_CLASS(handle_obj, php_io_poll_handle_class_entry) + Z_PARAM_OBJECT_OF_CLASS(handle_obj, php_poll_handle_ce) Z_PARAM_ARRAY(event_enums) Z_PARAM_OPTIONAL Z_PARAM_ZVAL(data) @@ -861,13 +860,13 @@ PHP_MINIT_FUNCTION(poll) /* Register event enum */ php_io_poll_event_class_entry = register_class_Io_Poll_Event(); - /* Register Handle interface */ - php_io_poll_handle_class_entry = register_class_Io_Poll_Handle(); - php_io_poll_handle_class_entry->interface_gets_implemented = php_stream_poll_handle_implement_interface; + /* Register Handle interface, which php_poll.h exports to extensions */ + php_poll_handle_ce = register_class_Io_Poll_Handle(); + php_poll_handle_ce->interface_gets_implemented = php_stream_poll_handle_implement_interface; /* Register StreamPollHandle class */ php_stream_poll_handle_class_entry - = register_class_StreamPollHandle(php_io_poll_handle_class_entry); + = register_class_StreamPollHandle(php_poll_handle_ce); php_stream_poll_handle_class_entry->create_object = php_stream_poll_handle_create_object; memcpy(&php_io_poll_handle_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 82bfa8d38e33..a946d0a5e81c 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -29,6 +29,7 @@ #include "zend_attributes.h" #include "zend_enum.h" #include "zend_interfaces.h" +#include "main/php_poll.h" #include "zend_weakrefs.h" #include "Zend/Optimizer/zend_optimizer.h" #include "Zend/zend_alloc.h" @@ -1193,6 +1194,23 @@ static ZEND_FUNCTION(zend_test_zstr_init_literal) RETURN_STR(ZSTR_INIT_LITERAL("foo\0bar", false)); } +static ZEND_FUNCTION(zend_test_poll_handle_descriptor) +{ + zval *zv; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(zv) + ZEND_PARSE_PARAMETERS_END(); + + php_poll_handle_object *handle = php_poll_handle_from_zval(zv); + + if (handle == NULL) { + RETURN_FALSE; + } + + RETURN_LONG((zend_long) php_poll_handle_get_fd(handle)); +} + static ZEND_FUNCTION(zend_test_is_string_marked_as_valid_utf8) { zend_string *str; diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php index 3c09668bddb1..86d5d3ae872b 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -419,6 +419,12 @@ function zend_test_zend_call_stack_use_all(): int {} function zend_test_is_string_marked_as_valid_utf8(string $string): bool {} + /** + * Resolves a handle the way an extension consuming Io\Poll\Handle does, or false when + * the argument is not a handle. + */ + function zend_test_poll_handle_descriptor(mixed $handle): int|false {} + function zend_get_map_ptr_last(): int {} function zend_test_crash(?string $message = null): void {} diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index f3901c9b7330..619161678eb8 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit test.stub.php instead. - * Stub hash: 4d728e740122add9d4c91f5c1abb5f5017690636 + * Stub hash: a132305dcf88ab989f0d16ae9f2a62699395e90b * Has decl header: yes */ #include "zend_attributes.h" @@ -279,6 +279,10 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_is_string_marked_as_va ZEND_ARG_TYPE_INFO(0, string, IS_STRING, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_zend_test_poll_handle_descriptor, 0, 1, MAY_BE_LONG|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, handle, IS_MIXED, 0) +ZEND_END_ARG_INFO() + #define arginfo_zend_get_map_ptr_last arginfo_zend_test_nodiscard ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_crash, 0, 0, IS_VOID, 0) @@ -505,6 +509,7 @@ static ZEND_FUNCTION(zend_test_zend_call_stack_get); static ZEND_FUNCTION(zend_test_zend_call_stack_use_all); #endif static ZEND_FUNCTION(zend_test_is_string_marked_as_valid_utf8); +static ZEND_FUNCTION(zend_test_poll_handle_descriptor); static ZEND_FUNCTION(zend_get_map_ptr_last); static ZEND_FUNCTION(zend_test_crash); static ZEND_FUNCTION(zend_test_fill_packed_array); @@ -681,6 +686,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(zend_test_zend_call_stack_use_all, arginfo_zend_test_zend_call_stack_use_all) #endif ZEND_FE(zend_test_is_string_marked_as_valid_utf8, arginfo_zend_test_is_string_marked_as_valid_utf8) + ZEND_FE(zend_test_poll_handle_descriptor, arginfo_zend_test_poll_handle_descriptor) ZEND_FE(zend_get_map_ptr_last, arginfo_zend_get_map_ptr_last) ZEND_FE(zend_test_crash, arginfo_zend_test_crash) ZEND_FE(zend_test_fill_packed_array, arginfo_zend_test_fill_packed_array) diff --git a/ext/zend_test/test_decl.h b/ext/zend_test/test_decl.h index 7e41dc18eabb..ef0d55d557e0 100644 --- a/ext/zend_test/test_decl.h +++ b/ext/zend_test/test_decl.h @@ -1,8 +1,8 @@ /* This is a generated file, edit test.stub.php instead. - * Stub hash: 4d728e740122add9d4c91f5c1abb5f5017690636 */ + * Stub hash: a132305dcf88ab989f0d16ae9f2a62699395e90b */ -#ifndef ZEND_TEST_DECL_4d728e740122add9d4c91f5c1abb5f5017690636_H -#define ZEND_TEST_DECL_4d728e740122add9d4c91f5c1abb5f5017690636_H +#ifndef ZEND_TEST_DECL_a132305dcf88ab989f0d16ae9f2a62699395e90b_H +#define ZEND_TEST_DECL_a132305dcf88ab989f0d16ae9f2a62699395e90b_H typedef enum zend_enum_ZendTestUnitEnum { ZEND_ENUM_ZendTestUnitEnum_Foo = 1, @@ -27,4 +27,4 @@ typedef enum zend_enum_ZendTestEnumWithInterface { ZEND_ENUM_ZendTestEnumWithInterface_Bar = 2, } zend_enum_ZendTestEnumWithInterface; -#endif /* ZEND_TEST_DECL_4d728e740122add9d4c91f5c1abb5f5017690636_H */ +#endif /* ZEND_TEST_DECL_a132305dcf88ab989f0d16ae9f2a62699395e90b_H */ diff --git a/ext/zend_test/test_legacy_arginfo.h b/ext/zend_test/test_legacy_arginfo.h index d9a7709ab22f..43bd7e569393 100644 --- a/ext/zend_test/test_legacy_arginfo.h +++ b/ext/zend_test/test_legacy_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit test.stub.php instead. - * Stub hash: 4d728e740122add9d4c91f5c1abb5f5017690636 + * Stub hash: a132305dcf88ab989f0d16ae9f2a62699395e90b * Has decl header: yes */ #include "zend_constants.h" @@ -221,6 +221,10 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_zend_test_is_string_marked_as_valid_utf8, 0, 0, 1 ZEND_ARG_INFO(0, string) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_INFO_EX(arginfo_zend_test_poll_handle_descriptor, 0, 0, 1) + ZEND_ARG_INFO(0, handle) +ZEND_END_ARG_INFO() + #define arginfo_zend_get_map_ptr_last arginfo_zend_trigger_bailout ZEND_BEGIN_ARG_INFO_EX(arginfo_zend_test_crash, 0, 0, 0) @@ -429,6 +433,7 @@ static ZEND_FUNCTION(zend_test_zend_call_stack_get); static ZEND_FUNCTION(zend_test_zend_call_stack_use_all); #endif static ZEND_FUNCTION(zend_test_is_string_marked_as_valid_utf8); +static ZEND_FUNCTION(zend_test_poll_handle_descriptor); static ZEND_FUNCTION(zend_get_map_ptr_last); static ZEND_FUNCTION(zend_test_crash); static ZEND_FUNCTION(zend_test_fill_packed_array); @@ -577,6 +582,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(zend_test_zend_call_stack_use_all, arginfo_zend_test_zend_call_stack_use_all) #endif ZEND_FE(zend_test_is_string_marked_as_valid_utf8, arginfo_zend_test_is_string_marked_as_valid_utf8) + ZEND_FE(zend_test_poll_handle_descriptor, arginfo_zend_test_poll_handle_descriptor) ZEND_FE(zend_get_map_ptr_last, arginfo_zend_get_map_ptr_last) ZEND_FE(zend_test_crash, arginfo_zend_test_crash) ZEND_FE(zend_test_fill_packed_array, arginfo_zend_test_fill_packed_array) diff --git a/ext/zend_test/tests/io_poll_handle_api.phpt b/ext/zend_test/tests/io_poll_handle_api.phpt new file mode 100644 index 000000000000..053bb69b0bd3 --- /dev/null +++ b/ext/zend_test/tests/io_poll_handle_api.phpt @@ -0,0 +1,25 @@ +--TEST-- +An extension resolves any Io\Poll\Handle through php_poll_handle_from_zval() +--EXTENSIONS-- +zend_test +--FILE-- += 0); + +// anything that is not a handle resolves to nothing +var_dump(zend_test_poll_handle_descriptor($r)); +var_dump(zend_test_poll_handle_descriptor(new stdClass())); +var_dump(zend_test_poll_handle_descriptor('nope')); + +fclose($r); +fclose($w); +?> +--EXPECT-- +bool(true) +bool(false) +bool(false) +bool(false) diff --git a/main/php_poll.h b/main/php_poll.h index 9b878d44bd20..cae104f0039d 100644 --- a/main/php_poll.h +++ b/main/php_poll.h @@ -163,13 +163,19 @@ struct php_poll_handle_object { #define PHP_POLL_HANDLE_OBJ_FROM_ZV(zv) PHP_POLL_HANDLE_OBJ_FROM_ZOBJ(Z_OBJ_P(zv)) /* Default operations */ -extern php_poll_handle_ops php_poll_handle_default_ops; +PHPAPI extern php_poll_handle_ops php_poll_handle_default_ops; + +/* The Io\Poll\Handle interface, to declare a handle type and to accept one */ +PHPAPI extern zend_class_entry *php_poll_handle_ce; /* Utility functions for extensions */ PHPAPI php_poll_handle_object *php_poll_handle_object_create( size_t obj_size, zend_class_entry *ce, php_poll_handle_ops *ops); PHPAPI void php_poll_handle_object_free(zend_object *obj); +/* The handle a zval holds, or NULL when it holds anything else */ +PHPAPI php_poll_handle_object *php_poll_handle_from_zval(const zval *zv); + /* Get file descriptor from any poll handle */ PHPAPI php_socket_t php_poll_handle_get_fd(php_poll_handle_object *handle); diff --git a/main/poll/poll_handle.c b/main/poll/poll_handle.c index 228d44e1e420..728ee906f038 100644 --- a/main/poll/poll_handle.c +++ b/main/poll/poll_handle.c @@ -51,10 +51,23 @@ static void php_poll_handle_default_cleanup(php_poll_handle_object *handle) } /* Default operations that call PHP userspace methods */ -php_poll_handle_ops php_poll_handle_default_ops = { .get_fd = php_poll_handle_default_get_fd, +PHPAPI php_poll_handle_ops php_poll_handle_default_ops = { .get_fd = php_poll_handle_default_get_fd, .is_valid = php_poll_handle_default_is_valid, .cleanup = php_poll_handle_default_cleanup }; +/* Set when ext/standard registers the interface */ +PHPAPI zend_class_entry *php_poll_handle_ce = NULL; + +PHPAPI php_poll_handle_object *php_poll_handle_from_zval(const zval *zv) +{ + if (Z_TYPE_P(zv) != IS_OBJECT || php_poll_handle_ce == NULL + || !instanceof_function(Z_OBJCE_P(zv), php_poll_handle_ce)) { + return NULL; + } + + return PHP_POLL_HANDLE_OBJ_FROM_ZV(zv); +} + /* Allocate a new poll handle object */ PHPAPI php_poll_handle_object *php_poll_handle_object_create( size_t obj_size, zend_class_entry *ce, php_poll_handle_ops *ops)