Commit 53f2aa93ae2 for php.net

commit 53f2aa93ae2bfbb4291a32f27cbbce78ff6b8d13
Author: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
Date:   Mon Jun 23 23:38:41 2025 +0200

    Fix GH-18898: SEGV zend_jit_op_array_hot with property hooks and preloading

    Property hooks were not handled for JIT+trait+preloading.
    Split the existing functions that handle op arrays, and add iterations
    for property hooks.

    Closes GH-18923.

diff --git a/NEWS b/NEWS
index 3f4e3a0a4a3..1af91056238 100644
--- a/NEWS
+++ b/NEWS
@@ -28,6 +28,8 @@ PHP                                                                        NEWS
     warning and opline is not set yet). (nielsdos)
   . Fixed bug GH-14082 (Segmentation fault on unknown address 0x600000000018
     in ext/opcache/jit/zend_jit.c). (nielsdos)
+  . Fixed bug GH-18898 (SEGV zend_jit_op_array_hot with property hooks
+    and preloading). (nielsdos)

 - PCNTL:
   . Fixed bug GH-18958 (Fatal error during shutdown after pcntl_rfork() or
diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c
index 67e89d3e2e6..7b451240a38 100644
--- a/ext/opcache/jit/zend_jit.c
+++ b/ext/opcache/jit/zend_jit.c
@@ -3216,6 +3216,17 @@ int zend_jit_op_array(zend_op_array *op_array, zend_script *script)
 	return FAILURE;
 }

+static void zend_jit_link_func_info(zend_op_array *op_array)
+{
+	if (!ZEND_FUNC_INFO(op_array)) {
+		void *jit_extension = zend_shared_alloc_get_xlat_entry(op_array->opcodes);
+
+		if (jit_extension) {
+			ZEND_SET_FUNC_INFO(op_array, jit_extension);
+		}
+	}
+}
+
 int zend_jit_script(zend_script *script)
 {
 	void *checkpoint;
@@ -3303,6 +3314,7 @@ int zend_jit_script(zend_script *script)
 		zend_class_entry *ce;
 		zend_op_array *op_array;
 		zval *zv;
+		zend_property_info *prop;

 		ZEND_HASH_MAP_FOREACH_VAL(&script->class_table, zv) {
 			if (Z_TYPE_P(zv) == IS_ALIAS_PTR) {
@@ -3313,14 +3325,21 @@ int zend_jit_script(zend_script *script)
 			ZEND_ASSERT(ce->type == ZEND_USER_CLASS);

 			ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, op_array) {
-				if (!ZEND_FUNC_INFO(op_array)) {
-					void *jit_extension = zend_shared_alloc_get_xlat_entry(op_array->opcodes);
+				zend_jit_link_func_info(op_array);
+			} ZEND_HASH_FOREACH_END();

-					if (jit_extension) {
-						ZEND_SET_FUNC_INFO(op_array, jit_extension);
+			if (ce->num_hooked_props > 0) {
+				ZEND_HASH_MAP_FOREACH_PTR(&ce->properties_info, prop) {
+					if (prop->hooks) {
+						for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) {
+							if (prop->hooks[i]) {
+								op_array = &prop->hooks[i]->op_array;
+								zend_jit_link_func_info(op_array);
+							}
+						}
 					}
-				}
-			} ZEND_HASH_FOREACH_END();
+				} ZEND_HASH_FOREACH_END();
+			}
 		} ZEND_HASH_FOREACH_END();
 	}

diff --git a/ext/opcache/tests/jit/gh18898_1.phpt b/ext/opcache/tests/jit/gh18898_1.phpt
new file mode 100644
index 00000000000..6038f006f5e
--- /dev/null
+++ b/ext/opcache/tests/jit/gh18898_1.phpt
@@ -0,0 +1,23 @@
+--TEST--
+GH-18898 (SEGV zend_jit_op_array_hot with property hooks and preloading) - jit 1235
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.jit=1235
+opcache.jit_buffer_size=16M
+opcache.preload={PWD}/../gh18534_preload.inc
+--EXTENSIONS--
+opcache
+--SKIPIF--
+<?php
+if (PHP_OS_FAMILY == 'Windows') die('skip Preloading is not supported on Windows');
+?>
+--FILE--
+<?php
+$test = new DummyModel;
+var_dump($test->dummyProperty2);
+echo "ok";
+?>
+--EXPECT--
+NULL
+ok
diff --git a/ext/opcache/tests/jit/gh18898_2.phpt b/ext/opcache/tests/jit/gh18898_2.phpt
new file mode 100644
index 00000000000..0ce79b859a9
--- /dev/null
+++ b/ext/opcache/tests/jit/gh18898_2.phpt
@@ -0,0 +1,23 @@
+--TEST--
+GH-18898 (SEGV zend_jit_op_array_hot with property hooks and preloading) - jit 1233
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.jit=1233
+opcache.jit_buffer_size=16M
+opcache.preload={PWD}/../gh18534_preload.inc
+--EXTENSIONS--
+opcache
+--SKIPIF--
+<?php
+if (PHP_OS_FAMILY == 'Windows') die('skip Preloading is not supported on Windows');
+?>
+--FILE--
+<?php
+$test = new DummyModel;
+var_dump($test->dummyProperty2);
+echo "ok";
+?>
+--EXPECT--
+NULL
+ok
diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c
index 1c21e031a19..71e5bb54007 100644
--- a/ext/opcache/zend_persist.c
+++ b/ext/opcache/zend_persist.c
@@ -1262,6 +1262,39 @@ void zend_update_parent_ce(zend_class_entry *ce)
 	}
 }

+static void zend_accel_persist_jit_op_array(zend_op_array *op_array, zend_class_entry *ce)
+{
+	if (op_array->type == ZEND_USER_FUNCTION) {
+		if (op_array->scope == ce
+		 && !(op_array->fn_flags & ZEND_ACC_ABSTRACT)
+		 && !(op_array->fn_flags & ZEND_ACC_TRAIT_CLONE)) {
+			zend_jit_op_array(op_array, ZCG(current_persistent_script) ? &ZCG(current_persistent_script)->script : NULL);
+			for (uint32_t i = 0; i < op_array->num_dynamic_func_defs; i++) {
+				zend_jit_op_array(op_array->dynamic_func_defs[i], ZCG(current_persistent_script) ? &ZCG(current_persistent_script)->script : NULL);
+			}
+		}
+	}
+}
+
+static void zend_accel_persist_link_func_info(zend_op_array *op_array, zend_class_entry *ce)
+{
+	if (op_array->type == ZEND_USER_FUNCTION
+	 && !(op_array->fn_flags & ZEND_ACC_ABSTRACT)) {
+		if ((op_array->scope != ce
+		 || (op_array->fn_flags & ZEND_ACC_TRAIT_CLONE))
+		  && (JIT_G(trigger) == ZEND_JIT_ON_FIRST_EXEC
+		  || JIT_G(trigger) == ZEND_JIT_ON_PROF_REQUEST
+		  || JIT_G(trigger) == ZEND_JIT_ON_HOT_COUNTERS
+		  || JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE)) {
+			void *jit_extension = zend_shared_alloc_get_xlat_entry(op_array->opcodes);
+
+			if (jit_extension) {
+				ZEND_SET_FUNC_INFO(op_array, jit_extension);
+			}
+		}
+	}
+}
+
 static void zend_accel_persist_class_table(HashTable *class_table)
 {
 	Bucket *p;
@@ -1288,44 +1321,48 @@ static void zend_accel_persist_class_table(HashTable *class_table)
 	if (JIT_G(on) && JIT_G(opt_level) <= ZEND_JIT_LEVEL_OPT_FUNCS &&
 	    !ZCG(current_persistent_script)->corrupted) {
 	    zend_op_array *op_array;
+		zend_property_info *prop;

 	    ZEND_HASH_MAP_FOREACH_BUCKET(class_table, p) {
 			if (EXPECTED(Z_TYPE(p->val) != IS_ALIAS_PTR)) {
 				ce = Z_PTR(p->val);
 				ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, op_array) {
-					if (op_array->type == ZEND_USER_FUNCTION) {
-						if (op_array->scope == ce
-						 && !(op_array->fn_flags & ZEND_ACC_ABSTRACT)
-						 && !(op_array->fn_flags & ZEND_ACC_TRAIT_CLONE)) {
-							zend_jit_op_array(op_array, ZCG(current_persistent_script) ? &ZCG(current_persistent_script)->script : NULL);
-							for (uint32_t i = 0; i < op_array->num_dynamic_func_defs; i++) {
-								zend_jit_op_array(op_array->dynamic_func_defs[i], ZCG(current_persistent_script) ? &ZCG(current_persistent_script)->script : NULL);
+					zend_accel_persist_jit_op_array(op_array, ce);
+				} ZEND_HASH_FOREACH_END();
+
+				if (ce->num_hooked_props > 0) {
+					ZEND_HASH_MAP_FOREACH_PTR(&ce->properties_info, prop) {
+						if (prop->hooks) {
+							for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) {
+								if (prop->hooks[i]) {
+									op_array = &prop->hooks[i]->op_array;
+									zend_accel_persist_jit_op_array(op_array, ce);
+								}
 							}
 						}
-					}
-				} ZEND_HASH_FOREACH_END();
+					} ZEND_HASH_FOREACH_END();
+				}
 			}
 		} ZEND_HASH_FOREACH_END();
 	    ZEND_HASH_MAP_FOREACH_BUCKET(class_table, p) {
 			if (EXPECTED(Z_TYPE(p->val) != IS_ALIAS_PTR)) {
 				ce = Z_PTR(p->val);
 				ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, op_array) {
-					if (op_array->type == ZEND_USER_FUNCTION
-					 && !(op_array->fn_flags & ZEND_ACC_ABSTRACT)) {
-						if ((op_array->scope != ce
-						 || (op_array->fn_flags & ZEND_ACC_TRAIT_CLONE))
-						  && (JIT_G(trigger) == ZEND_JIT_ON_FIRST_EXEC
-						   || JIT_G(trigger) == ZEND_JIT_ON_PROF_REQUEST
-						   || JIT_G(trigger) == ZEND_JIT_ON_HOT_COUNTERS
-						   || JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE)) {
-							void *jit_extension = zend_shared_alloc_get_xlat_entry(op_array->opcodes);
-
-							if (jit_extension) {
-								ZEND_SET_FUNC_INFO(op_array, jit_extension);
+					zend_accel_persist_link_func_info(op_array, ce);
+				} ZEND_HASH_FOREACH_END();
+
+				if (ce->num_hooked_props > 0) {
+					ZEND_HASH_MAP_FOREACH_PTR(&ce->properties_info, prop) {
+						if (prop->hooks) {
+							for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) {
+								if (prop->hooks[i]) {
+									op_array = &prop->hooks[i]->op_array;
+									zend_accel_persist_link_func_info(op_array, ce);
+								}
 							}
 						}
-					}
-				} ZEND_HASH_FOREACH_END();
+					} ZEND_HASH_FOREACH_END();
+				}
 			}
 		} ZEND_HASH_FOREACH_END();
 	}