Commit 951680712b4 for php.net

commit 951680712b460be92c89f56046ae68db61fad490
Author: Tim Düsterhus <tim@bastelstu.be>
Date:   Sun Jun 14 16:18:01 2026 +0200

    zend_object_handlers: Replace function-like macros by (inline) functions (#22297)

    `zend_free_trampoline()` requires access to the globals and thus
    cannot easily be made an inline function without moving it to a
    different (less-appropriate) header. Since trampolines should be
    comparatively rare, having an extra function call here should be okay.

diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c
index e7ddd466a51..9e993627a74 100644
--- a/Zend/zend_object_handlers.c
+++ b/Zend/zend_object_handlers.c
@@ -1825,6 +1825,18 @@ ZEND_API ZEND_ATTRIBUTE_NONNULL zend_function *zend_get_call_trampoline_func(
 }
 /* }}} */

+ZEND_API void zend_free_trampoline(zend_function *func)
+{
+	ZEND_ASSERT(func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE);
+
+	if (func == &EG(trampoline)) {
+		EG(trampoline).common.attributes = NULL;
+		EG(trampoline).common.function_name = NULL;
+	} else {
+		efree(func);
+	}
+}
+
 static ZEND_FUNCTION(zend_parent_hook_get_trampoline)
 {
 	zend_object *obj = Z_PTR_P(ZEND_THIS);
diff --git a/Zend/zend_object_handlers.h b/Zend/zend_object_handlers.h
index d0dd804e8a4..8ed4803c3f0 100644
--- a/Zend/zend_object_handlers.h
+++ b/Zend/zend_object_handlers.h
@@ -236,11 +236,10 @@ struct _zend_object_handlers {
 BEGIN_EXTERN_C()
 extern const ZEND_API zend_object_handlers std_object_handlers;

-#define zend_get_std_object_handlers() \
-	(&std_object_handlers)
-
-#define zend_get_function_root_class(fbc) \
-	((fbc)->common.prototype ? (fbc)->common.prototype->common.scope : (fbc)->common.scope)
+static zend_always_inline const zend_object_handlers *zend_get_std_object_handlers(void)
+{
+	return &std_object_handlers;
+}

 #define ZEND_PROPERTY_ISSET     0x0          /* Property exists and is not NULL */
 #define ZEND_PROPERTY_NOT_EMPTY ZEND_ISEMPTY /* Property is not empty */
@@ -290,18 +289,20 @@ static zend_always_inline HashTable *zend_std_get_properties_ex(zend_object *obj
 /* Implements the fast path for array cast */
 ZEND_API HashTable *zend_std_build_object_properties_array(zend_object *zobj);

-#define ZEND_STD_BUILD_OBJECT_PROPERTIES_ARRAY_COMPATIBLE(object) (            \
-		/* We can use zend_std_build_object_properties_array() for objects     \
-		 * without properties ht and with standard handlers */                 \
-		Z_OBJ_P(object)->properties == NULL                                    \
-		&& Z_OBJ_HT_P(object)->get_properties_for == NULL                      \
-		&& Z_OBJ_HT_P(object)->get_properties == zend_std_get_properties       \
-		/* For initialized proxies we need to forward to the real instance */  \
-		&& (                                                                   \
-			!zend_object_is_lazy_proxy(Z_OBJ_P(object))                        \
-			|| !zend_lazy_object_initialized(Z_OBJ_P(object))                  \
-		)                                                                      \
-)
+static zend_always_inline bool ZEND_STD_BUILD_OBJECT_PROPERTIES_ARRAY_COMPATIBLE(const zval *zv) {
+	/* We can use zend_std_build_object_properties_array() for objects
+	 * without properties ht and with standard handlers */
+	const zend_object *obj = Z_OBJ_P(zv);
+
+	return obj->properties == NULL
+		&& obj->handlers->get_properties_for == NULL
+		&& obj->handlers->get_properties == zend_std_get_properties
+		/* For initialized proxies we need to forward to the real instance */
+		&& (
+			!zend_object_is_lazy_proxy(obj)
+			|| !zend_lazy_object_initialized(obj)
+		);
+}

 /* Handler for objects that cannot be meaningfully compared.
  * Only objects with the same identity will be considered equal. */
@@ -312,6 +313,7 @@ ZEND_API bool zend_check_protected(const zend_class_entry *ce, const zend_class_
 ZEND_API zend_result zend_check_property_access(const zend_object *zobj, zend_string *prop_info_name, bool is_dynamic);

 ZEND_API ZEND_ATTRIBUTE_NONNULL zend_function *zend_get_call_trampoline_func(const zend_function *fbc, zend_string *method_name);
+ZEND_API void zend_free_trampoline(zend_function *func);

 ZEND_API uint32_t *zend_get_property_guard(zend_object *zobj, zend_string *member);

@@ -335,20 +337,12 @@ ZEND_API bool ZEND_FASTCALL zend_asymmetric_property_has_set_access(const zend_p

 void zend_object_handlers_startup(void);

-#define zend_release_properties(ht) do { \
-	if (ht) { \
-		zend_array_release(ht); \
-	} \
-} while (0)
-
-#define zend_free_trampoline(func) do { \
-		if ((func) == &EG(trampoline)) { \
-			EG(trampoline).common.attributes = NULL; \
-			EG(trampoline).common.function_name = NULL; \
-		} else { \
-			efree(func); \
-		} \
-	} while (0)
+static zend_always_inline void zend_release_properties(HashTable *ht)
+{
+	if (ht) {
+		zend_array_release(ht);
+	}
+}

 /* Fallback to default comparison implementation if the arguments aren't both objects
  * and have the same compare() handler. You'll likely want to use this unless you
diff --git a/Zend/zend_objects_API.h b/Zend/zend_objects_API.h
index 694ef398e37..434ac4499e7 100644
--- a/Zend/zend_objects_API.h
+++ b/Zend/zend_objects_API.h
@@ -136,6 +136,11 @@ static inline zend_property_info *zend_get_typed_property_info_for_slot(zend_obj
 	return NULL;
 }

+static zend_always_inline zend_class_entry *zend_get_function_root_class(const zend_function *fbc)
+{
+	return fbc->common.prototype ? fbc->common.prototype->common.scope : fbc->common.scope;
+}
+
 static zend_always_inline bool zend_check_method_accessible(const zend_function *fn, const zend_class_entry *scope)
 {
 	if (!(fn->common.fn_flags & ZEND_ACC_PUBLIC)