From f38f74521b61f4a7e74d0124f557fa1d898bf0e2 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Mon, 19 Jan 2026 15:47:03 +0100 Subject: [PATCH 01/14] Fix lazy proxy bailing __clone assertion When __clone of the underlying object fails with a bailout, ZEND_ASSERT(res == SUCCESS) in zend_lazy_object_del_info() will fail because the info has not been registered yet. Only copy OBJ_EXTRA_FLAGS once the info has been successfully registered. Fixes GH-20905 Closes GH-20975 --- NEWS | 1 + Zend/tests/lazy_objects/gh20905.phpt | 22 ++++++++++++++++++++++ Zend/zend_lazy_objects.c | 3 +-- 3 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 Zend/tests/lazy_objects/gh20905.phpt diff --git a/NEWS b/NEWS index 06ebe601478e..80b69c11cf88 100644 --- a/NEWS +++ b/NEWS @@ -14,6 +14,7 @@ PHP NEWS backing value). (ilutov) . Fix OSS-Fuzz #438780145 (Nested finally with repeated return type check may uaf). (ilutov) + . Fixed bug GH-20905 (Lazy proxy bailing __clone assertion). (ilutov) - Date: . Update timelib to 2022.16. (Derick) diff --git a/Zend/tests/lazy_objects/gh20905.phpt b/Zend/tests/lazy_objects/gh20905.phpt new file mode 100644 index 000000000000..48a1360c0a94 --- /dev/null +++ b/Zend/tests/lazy_objects/gh20905.phpt @@ -0,0 +1,22 @@ +--TEST-- +GH-20905: Lazy proxy bailing __clone assertion +--FILE-- +newLazyProxy(fn() => new A); + +?> +--EXPECTF-- +Fatal error: Cannot redeclare function f() (previously declared in %s:%d) in %s on line %d diff --git a/Zend/zend_lazy_objects.c b/Zend/zend_lazy_objects.c index d1b950160e1c..bf76f6e88fe7 100644 --- a/Zend/zend_lazy_objects.c +++ b/Zend/zend_lazy_objects.c @@ -744,12 +744,11 @@ zend_object *zend_lazy_object_clone(zend_object *old_obj) } } - OBJ_EXTRA_FLAGS(new_proxy) = OBJ_EXTRA_FLAGS(old_obj); - zend_lazy_object_info *new_info = emalloc(sizeof(*info)); *new_info = *info; new_info->u.instance = zend_objects_clone_obj(info->u.instance); + OBJ_EXTRA_FLAGS(new_proxy) = OBJ_EXTRA_FLAGS(old_obj); zend_lazy_object_set_info(new_proxy, new_info); return new_proxy; From 6a21a41b4ad1b086e6748d6521cd04d3518e4dd2 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Tue, 20 Jan 2026 18:00:58 +0100 Subject: [PATCH 02/14] [skip ci] Fix missing test attribution --- Zend/tests/lazy_objects/gh20905.phpt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Zend/tests/lazy_objects/gh20905.phpt b/Zend/tests/lazy_objects/gh20905.phpt index 48a1360c0a94..318b44e6b82a 100644 --- a/Zend/tests/lazy_objects/gh20905.phpt +++ b/Zend/tests/lazy_objects/gh20905.phpt @@ -1,5 +1,7 @@ --TEST-- GH-20905: Lazy proxy bailing __clone assertion +--CREDITS-- +Viet Hoang Luu (@vi3tL0u1s) --FILE-- Date: Sat, 10 Jan 2026 17:42:01 +0100 Subject: [PATCH 03/14] Fix GH-20890: Segfault in zval_undefined_cv with non-simple property hook with minimal tracing JIT This is similar to f6c2e40a11 but for minimal JIT + tracing JIT. Most of the times the tracing JIT shouldn't rely on going to the VM, but in some cases, like in minimal JIT, it can and then it hits the same bug. Closes GH-20897. --- NEWS | 4 ++++ ext/opcache/jit/zend_jit_trace.c | 8 +++++++ ext/opcache/tests/jit/gh20890.phpt | 37 ++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+) create mode 100644 ext/opcache/tests/jit/gh20890.phpt diff --git a/NEWS b/NEWS index 80b69c11cf88..4a2dbd152499 100644 --- a/NEWS +++ b/NEWS @@ -25,6 +25,10 @@ PHP NEWS . Fixed bug GH-20836 (Stack overflow in mb_convert_variables with recursive array references). (alexandre-daubois) +- Opcache: + . Fixed bug GH-20890 (Segfault in zval_undefined_cv with non-simple + property hook with minimal tracing JIT). (ndossche) + - Phar: . Fixed bug GH-20882 (buildFromIterator breaks with missing base directory). (ndossche) diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c index 9d2de55e2949..41393f721cfc 100644 --- a/ext/opcache/jit/zend_jit_trace.c +++ b/ext/opcache/jit/zend_jit_trace.c @@ -328,6 +328,14 @@ static int zend_jit_trace_may_exit(const zend_op_array *op_array, const zend_op // TODO: recompilation may change target ??? return 0; #endif + case ZEND_FETCH_OBJ_R: + if (opline->op2_type == IS_CONST) { + const zend_class_entry *ce = opline->op1_type == IS_UNUSED ? op_array->scope : NULL; + if (!ce || !(ce->ce_flags & ZEND_ACC_FINAL) || ce->num_hooked_props > 0) { + return 1; + } + } + break; case ZEND_RETURN_BY_REF: case ZEND_RETURN: /* return */ diff --git a/ext/opcache/tests/jit/gh20890.phpt b/ext/opcache/tests/jit/gh20890.phpt new file mode 100644 index 000000000000..c375c379fcc7 --- /dev/null +++ b/ext/opcache/tests/jit/gh20890.phpt @@ -0,0 +1,37 @@ +--TEST-- +GH-20890 (Segfault in zval_undefined_cv with non-simple property hook with minimal tracing JIT) +--CREDITS-- +Moonster8282 +--EXTENSIONS-- +opcache +--INI-- +opcache.jit=1251 +--FILE-- +readCount++; + return $this->readCount * 2; + } + } +} + +function hook_hot_path($obj, $iterations) { + $sum = 0; + for ($i = 0; $i < $iterations; $i++) { + $sum += $obj->computed; + } + return $sum; +} + +echo "Testing property hook in hot path...\n"; +$obj = new HookJIT(); +$result = hook_hot_path($obj, 100); +echo "Result: $result\n"; +?> +--EXPECT-- +Testing property hook in hot path... +Result: 10100 From 32c02455312ed03cfa726ecffa982977ca6e954f Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+ndossche@users.noreply.github.com> Date: Tue, 20 Jan 2026 21:05:26 +0100 Subject: [PATCH 04/14] Revert "Fix GH-20890: Segfault in zval_undefined_cv with non-simple property hook with minimal tracing JIT" This reverts commit 57c62eb2b34385a9c2ab4e7ab802c4a361483ba1. --- NEWS | 4 ---- ext/opcache/jit/zend_jit_trace.c | 8 ------- ext/opcache/tests/jit/gh20890.phpt | 37 ------------------------------ 3 files changed, 49 deletions(-) delete mode 100644 ext/opcache/tests/jit/gh20890.phpt diff --git a/NEWS b/NEWS index 4a2dbd152499..80b69c11cf88 100644 --- a/NEWS +++ b/NEWS @@ -25,10 +25,6 @@ PHP NEWS . Fixed bug GH-20836 (Stack overflow in mb_convert_variables with recursive array references). (alexandre-daubois) -- Opcache: - . Fixed bug GH-20890 (Segfault in zval_undefined_cv with non-simple - property hook with minimal tracing JIT). (ndossche) - - Phar: . Fixed bug GH-20882 (buildFromIterator breaks with missing base directory). (ndossche) diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c index 41393f721cfc..9d2de55e2949 100644 --- a/ext/opcache/jit/zend_jit_trace.c +++ b/ext/opcache/jit/zend_jit_trace.c @@ -328,14 +328,6 @@ static int zend_jit_trace_may_exit(const zend_op_array *op_array, const zend_op // TODO: recompilation may change target ??? return 0; #endif - case ZEND_FETCH_OBJ_R: - if (opline->op2_type == IS_CONST) { - const zend_class_entry *ce = opline->op1_type == IS_UNUSED ? op_array->scope : NULL; - if (!ce || !(ce->ce_flags & ZEND_ACC_FINAL) || ce->num_hooked_props > 0) { - return 1; - } - } - break; case ZEND_RETURN_BY_REF: case ZEND_RETURN: /* return */ diff --git a/ext/opcache/tests/jit/gh20890.phpt b/ext/opcache/tests/jit/gh20890.phpt deleted file mode 100644 index c375c379fcc7..000000000000 --- a/ext/opcache/tests/jit/gh20890.phpt +++ /dev/null @@ -1,37 +0,0 @@ ---TEST-- -GH-20890 (Segfault in zval_undefined_cv with non-simple property hook with minimal tracing JIT) ---CREDITS-- -Moonster8282 ---EXTENSIONS-- -opcache ---INI-- -opcache.jit=1251 ---FILE-- -readCount++; - return $this->readCount * 2; - } - } -} - -function hook_hot_path($obj, $iterations) { - $sum = 0; - for ($i = 0; $i < $iterations; $i++) { - $sum += $obj->computed; - } - return $sum; -} - -echo "Testing property hook in hot path...\n"; -$obj = new HookJIT(); -$result = hook_hot_path($obj, 100); -echo "Result: $result\n"; -?> ---EXPECT-- -Testing property hook in hot path... -Result: 10100 From f957571d6600861481bbf0560deaba2926dcdc8e Mon Sep 17 00:00:00 2001 From: ndossche Date: Tue, 20 Jan 2026 14:11:39 +0100 Subject: [PATCH 05/14] Remove dead code from zend_test internal execute handler The internal execute handler is used for internal functions, so by definition it cannot be a user function. --- ext/zend_test/observer.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/zend_test/observer.c b/ext/zend_test/observer.c index 31052ec830f7..a6b43912a14f 100644 --- a/ext/zend_test/observer.c +++ b/ext/zend_test/observer.c @@ -288,14 +288,14 @@ static void (*zend_test_prev_execute_internal)(zend_execute_data *execute_data, static void zend_test_execute_internal(zend_execute_data *execute_data, zval *return_value) { zend_function *fbc = execute_data->func; + ZEND_ASSERT(!ZEND_USER_CODE(fbc->type)); + if (fbc->common.function_name) { if (fbc->common.scope) { php_printf("%*s\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(fbc->common.scope->name), ZSTR_VAL(fbc->common.function_name)); } else { php_printf("%*s\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(fbc->common.function_name)); } - } else if (ZEND_USER_CODE(fbc->type)) { - php_printf("%*s\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(fbc->op_array.filename)); } if (zend_test_prev_execute_internal) { From 8776c7ead1758850aa3d1c9fcfd00062262dd8ee Mon Sep 17 00:00:00 2001 From: ndossche Date: Tue, 20 Jan 2026 14:13:33 +0100 Subject: [PATCH 06/14] Extend zend_test internal function handler to also print leave (with return info if requested) and exception state --- Zend/tests/attributes/nodiscard/007.phpt | 2 ++ ext/zend_test/observer.c | 15 +++++++++++++++ ext/zend_test/tests/execute_internal.phpt | 6 +++++- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Zend/tests/attributes/nodiscard/007.phpt b/Zend/tests/attributes/nodiscard/007.phpt index 1b72de8c22a0..de4604e15e48 100644 --- a/Zend/tests/attributes/nodiscard/007.phpt +++ b/Zend/tests/attributes/nodiscard/007.phpt @@ -12,6 +12,8 @@ zend_test_nodiscard(); ?> --EXPECTF-- + Warning: The return value of function zend_test_nodiscard() should either be used or intentionally ignored by casting it as (void), custom message in %s on line %d + diff --git a/ext/zend_test/observer.c b/ext/zend_test/observer.c index a6b43912a14f..9c6bf6b674c6 100644 --- a/ext/zend_test/observer.c +++ b/ext/zend_test/observer.c @@ -303,6 +303,21 @@ static void zend_test_execute_internal(zend_execute_data *execute_data, zval *re } else { fbc->internal_function.handler(execute_data, return_value); } + + if (fbc->common.function_name) { + if (EG(exception)) { + php_printf("%*s\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(EG(exception)->ce->name)); + } + + smart_str retval_info = {0}; + get_retval_info(return_value, &retval_info); + if (fbc->common.scope) { + php_printf("%*s\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(fbc->common.scope->name), ZSTR_VAL(fbc->common.function_name), retval_info.s ? ZSTR_VAL(retval_info.s) : ""); + } else { + php_printf("%*s\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(fbc->common.function_name), retval_info.s ? ZSTR_VAL(retval_info.s) : ""); + } + smart_str_free(&retval_info); + } } static ZEND_INI_MH(zend_test_observer_OnUpdateCommaList) diff --git a/ext/zend_test/tests/execute_internal.phpt b/ext/zend_test/tests/execute_internal.phpt index ce6cb851aed6..3983e6f2b8d3 100644 --- a/ext/zend_test/tests/execute_internal.phpt +++ b/ext/zend_test/tests/execute_internal.phpt @@ -4,6 +4,7 @@ Test zend_execute_internal being called zend_test --INI-- zend_test.observer.execute_internal=1 +zend_test.observer.show_return_value=1 --FILE-- 0 ? [1, 2, 3] : []); ?> ---EXPECT-- +--EXPECTF-- + + int(6) + From 6a4d0d9456bf872286722ad5f043ad4928e92c9a Mon Sep 17 00:00:00 2001 From: ndossche Date: Tue, 20 Jan 2026 15:45:53 +0100 Subject: [PATCH 07/14] Do nesting for internal execute hook --- ext/zend_test/observer.c | 4 ++++ ext/zend_test/tests/execute_internal.phpt | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/ext/zend_test/observer.c b/ext/zend_test/observer.c index 9c6bf6b674c6..348996b7ac13 100644 --- a/ext/zend_test/observer.c +++ b/ext/zend_test/observer.c @@ -298,12 +298,16 @@ static void zend_test_execute_internal(zend_execute_data *execute_data, zval *re } } + ZT_G(observer_nesting_depth)++; + if (zend_test_prev_execute_internal) { zend_test_prev_execute_internal(execute_data, return_value); } else { fbc->internal_function.handler(execute_data, return_value); } + ZT_G(observer_nesting_depth)--; + if (fbc->common.function_name) { if (EG(exception)) { php_printf("%*s\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(EG(exception)->ce->name)); diff --git a/ext/zend_test/tests/execute_internal.phpt b/ext/zend_test/tests/execute_internal.phpt index 3983e6f2b8d3..6c7fc7577591 100644 --- a/ext/zend_test/tests/execute_internal.phpt +++ b/ext/zend_test/tests/execute_internal.phpt @@ -14,6 +14,8 @@ function f($a) { f(time() > 0 ? [1, 2, 3] : []); +array_map("count", [[], [1, 2]]); + ?> --EXPECTF-- @@ -23,3 +25,13 @@ f(time() > 0 ? [1, 2, 3] : []); int(6) + + + + + + From 2d10ae2f9899b160909456034505558f5eb4b82f Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 5 Jan 2026 18:39:07 +0100 Subject: [PATCH 08/14] ext/spl: move autoloading tests to subfolder --- ext/spl/tests/{ => autoloading}/bug38325.phpt | 0 ext/spl/tests/{ => autoloading}/bug40091.phpt | 0 ext/spl/tests/{ => autoloading}/bug44144.phpt | 0 ext/spl/tests/{ => autoloading}/bug48023.phpt | 0 ext/spl/tests/{ => autoloading}/bug48493.phpt | 0 ext/spl/tests/{ => autoloading}/bug52339.phpt | 0 ext/spl/tests/{ => autoloading}/bug61697.phpt | 0 ext/spl/tests/{ => autoloading}/bug65006.phpt | 0 ext/spl/tests/{ => autoloading}/bug71202.phpt | 0 ext/spl/tests/{ => autoloading}/bug71204.phpt | 0 ext/spl/tests/{ => autoloading}/bug71236.phpt | 0 ext/spl/tests/{ => autoloading}/bug73896.phpt | 0 ext/spl/tests/{ => autoloading}/bug74372.phpt | 0 ext/spl/tests/{ => autoloading}/bug75049.phpt | 0 ext/spl/tests/{ => autoloading}/gh10011.phpt | 0 ext/spl/tests/{ => autoloading}/spl_autoload_001.phpt | 0 ext/spl/tests/{ => autoloading}/spl_autoload_002.phpt | 0 ext/spl/tests/{ => autoloading}/spl_autoload_003.phpt | 0 ext/spl/tests/{ => autoloading}/spl_autoload_004.phpt | 0 ext/spl/tests/{ => autoloading}/spl_autoload_005.phpt | 0 ext/spl/tests/{ => autoloading}/spl_autoload_006.phpt | 0 ext/spl/tests/{ => autoloading}/spl_autoload_007.phpt | 0 ext/spl/tests/{ => autoloading}/spl_autoload_008.phpt | 0 ext/spl/tests/{ => autoloading}/spl_autoload_009.phpt | 0 ext/spl/tests/{ => autoloading}/spl_autoload_010.phpt | 0 ext/spl/tests/{ => autoloading}/spl_autoload_011.phpt | 0 ext/spl/tests/{ => autoloading}/spl_autoload_012.phpt | 0 ext/spl/tests/{ => autoloading}/spl_autoload_013.phpt | 0 ext/spl/tests/{ => autoloading}/spl_autoload_014.phpt | 0 ext/spl/tests/{ => autoloading}/spl_autoload_bug48541.phpt | 0 ext/spl/tests/{ => autoloading}/spl_autoload_call_basic.phpt | 0 ext/spl/tests/{ => autoloading}/spl_autoload_called_scope.phpt | 0 ...spl_autoload_throw_with_spl_autoloader_call_as_autoloader.phpt | 0 .../spl_autoload_unregister_without_registrations.phpt | 0 .../{ => autoloading}/spl_autoload_warn_on_false_do_throw.phpt | 0 ext/spl/tests/{ => autoloading}/testclass | 0 ext/spl/tests/{ => autoloading}/testclass.class.inc | 0 ext/spl/tests/{ => autoloading}/testclass.inc | 0 ext/spl/tests/{ => autoloading}/testclass.php.inc | 0 39 files changed, 0 insertions(+), 0 deletions(-) rename ext/spl/tests/{ => autoloading}/bug38325.phpt (100%) rename ext/spl/tests/{ => autoloading}/bug40091.phpt (100%) rename ext/spl/tests/{ => autoloading}/bug44144.phpt (100%) rename ext/spl/tests/{ => autoloading}/bug48023.phpt (100%) rename ext/spl/tests/{ => autoloading}/bug48493.phpt (100%) rename ext/spl/tests/{ => autoloading}/bug52339.phpt (100%) rename ext/spl/tests/{ => autoloading}/bug61697.phpt (100%) rename ext/spl/tests/{ => autoloading}/bug65006.phpt (100%) rename ext/spl/tests/{ => autoloading}/bug71202.phpt (100%) rename ext/spl/tests/{ => autoloading}/bug71204.phpt (100%) rename ext/spl/tests/{ => autoloading}/bug71236.phpt (100%) rename ext/spl/tests/{ => autoloading}/bug73896.phpt (100%) rename ext/spl/tests/{ => autoloading}/bug74372.phpt (100%) rename ext/spl/tests/{ => autoloading}/bug75049.phpt (100%) rename ext/spl/tests/{ => autoloading}/gh10011.phpt (100%) rename ext/spl/tests/{ => autoloading}/spl_autoload_001.phpt (100%) rename ext/spl/tests/{ => autoloading}/spl_autoload_002.phpt (100%) rename ext/spl/tests/{ => autoloading}/spl_autoload_003.phpt (100%) rename ext/spl/tests/{ => autoloading}/spl_autoload_004.phpt (100%) rename ext/spl/tests/{ => autoloading}/spl_autoload_005.phpt (100%) rename ext/spl/tests/{ => autoloading}/spl_autoload_006.phpt (100%) rename ext/spl/tests/{ => autoloading}/spl_autoload_007.phpt (100%) rename ext/spl/tests/{ => autoloading}/spl_autoload_008.phpt (100%) rename ext/spl/tests/{ => autoloading}/spl_autoload_009.phpt (100%) rename ext/spl/tests/{ => autoloading}/spl_autoload_010.phpt (100%) rename ext/spl/tests/{ => autoloading}/spl_autoload_011.phpt (100%) rename ext/spl/tests/{ => autoloading}/spl_autoload_012.phpt (100%) rename ext/spl/tests/{ => autoloading}/spl_autoload_013.phpt (100%) rename ext/spl/tests/{ => autoloading}/spl_autoload_014.phpt (100%) rename ext/spl/tests/{ => autoloading}/spl_autoload_bug48541.phpt (100%) rename ext/spl/tests/{ => autoloading}/spl_autoload_call_basic.phpt (100%) rename ext/spl/tests/{ => autoloading}/spl_autoload_called_scope.phpt (100%) rename ext/spl/tests/{ => autoloading}/spl_autoload_throw_with_spl_autoloader_call_as_autoloader.phpt (100%) rename ext/spl/tests/{ => autoloading}/spl_autoload_unregister_without_registrations.phpt (100%) rename ext/spl/tests/{ => autoloading}/spl_autoload_warn_on_false_do_throw.phpt (100%) rename ext/spl/tests/{ => autoloading}/testclass (100%) rename ext/spl/tests/{ => autoloading}/testclass.class.inc (100%) rename ext/spl/tests/{ => autoloading}/testclass.inc (100%) rename ext/spl/tests/{ => autoloading}/testclass.php.inc (100%) diff --git a/ext/spl/tests/bug38325.phpt b/ext/spl/tests/autoloading/bug38325.phpt similarity index 100% rename from ext/spl/tests/bug38325.phpt rename to ext/spl/tests/autoloading/bug38325.phpt diff --git a/ext/spl/tests/bug40091.phpt b/ext/spl/tests/autoloading/bug40091.phpt similarity index 100% rename from ext/spl/tests/bug40091.phpt rename to ext/spl/tests/autoloading/bug40091.phpt diff --git a/ext/spl/tests/bug44144.phpt b/ext/spl/tests/autoloading/bug44144.phpt similarity index 100% rename from ext/spl/tests/bug44144.phpt rename to ext/spl/tests/autoloading/bug44144.phpt diff --git a/ext/spl/tests/bug48023.phpt b/ext/spl/tests/autoloading/bug48023.phpt similarity index 100% rename from ext/spl/tests/bug48023.phpt rename to ext/spl/tests/autoloading/bug48023.phpt diff --git a/ext/spl/tests/bug48493.phpt b/ext/spl/tests/autoloading/bug48493.phpt similarity index 100% rename from ext/spl/tests/bug48493.phpt rename to ext/spl/tests/autoloading/bug48493.phpt diff --git a/ext/spl/tests/bug52339.phpt b/ext/spl/tests/autoloading/bug52339.phpt similarity index 100% rename from ext/spl/tests/bug52339.phpt rename to ext/spl/tests/autoloading/bug52339.phpt diff --git a/ext/spl/tests/bug61697.phpt b/ext/spl/tests/autoloading/bug61697.phpt similarity index 100% rename from ext/spl/tests/bug61697.phpt rename to ext/spl/tests/autoloading/bug61697.phpt diff --git a/ext/spl/tests/bug65006.phpt b/ext/spl/tests/autoloading/bug65006.phpt similarity index 100% rename from ext/spl/tests/bug65006.phpt rename to ext/spl/tests/autoloading/bug65006.phpt diff --git a/ext/spl/tests/bug71202.phpt b/ext/spl/tests/autoloading/bug71202.phpt similarity index 100% rename from ext/spl/tests/bug71202.phpt rename to ext/spl/tests/autoloading/bug71202.phpt diff --git a/ext/spl/tests/bug71204.phpt b/ext/spl/tests/autoloading/bug71204.phpt similarity index 100% rename from ext/spl/tests/bug71204.phpt rename to ext/spl/tests/autoloading/bug71204.phpt diff --git a/ext/spl/tests/bug71236.phpt b/ext/spl/tests/autoloading/bug71236.phpt similarity index 100% rename from ext/spl/tests/bug71236.phpt rename to ext/spl/tests/autoloading/bug71236.phpt diff --git a/ext/spl/tests/bug73896.phpt b/ext/spl/tests/autoloading/bug73896.phpt similarity index 100% rename from ext/spl/tests/bug73896.phpt rename to ext/spl/tests/autoloading/bug73896.phpt diff --git a/ext/spl/tests/bug74372.phpt b/ext/spl/tests/autoloading/bug74372.phpt similarity index 100% rename from ext/spl/tests/bug74372.phpt rename to ext/spl/tests/autoloading/bug74372.phpt diff --git a/ext/spl/tests/bug75049.phpt b/ext/spl/tests/autoloading/bug75049.phpt similarity index 100% rename from ext/spl/tests/bug75049.phpt rename to ext/spl/tests/autoloading/bug75049.phpt diff --git a/ext/spl/tests/gh10011.phpt b/ext/spl/tests/autoloading/gh10011.phpt similarity index 100% rename from ext/spl/tests/gh10011.phpt rename to ext/spl/tests/autoloading/gh10011.phpt diff --git a/ext/spl/tests/spl_autoload_001.phpt b/ext/spl/tests/autoloading/spl_autoload_001.phpt similarity index 100% rename from ext/spl/tests/spl_autoload_001.phpt rename to ext/spl/tests/autoloading/spl_autoload_001.phpt diff --git a/ext/spl/tests/spl_autoload_002.phpt b/ext/spl/tests/autoloading/spl_autoload_002.phpt similarity index 100% rename from ext/spl/tests/spl_autoload_002.phpt rename to ext/spl/tests/autoloading/spl_autoload_002.phpt diff --git a/ext/spl/tests/spl_autoload_003.phpt b/ext/spl/tests/autoloading/spl_autoload_003.phpt similarity index 100% rename from ext/spl/tests/spl_autoload_003.phpt rename to ext/spl/tests/autoloading/spl_autoload_003.phpt diff --git a/ext/spl/tests/spl_autoload_004.phpt b/ext/spl/tests/autoloading/spl_autoload_004.phpt similarity index 100% rename from ext/spl/tests/spl_autoload_004.phpt rename to ext/spl/tests/autoloading/spl_autoload_004.phpt diff --git a/ext/spl/tests/spl_autoload_005.phpt b/ext/spl/tests/autoloading/spl_autoload_005.phpt similarity index 100% rename from ext/spl/tests/spl_autoload_005.phpt rename to ext/spl/tests/autoloading/spl_autoload_005.phpt diff --git a/ext/spl/tests/spl_autoload_006.phpt b/ext/spl/tests/autoloading/spl_autoload_006.phpt similarity index 100% rename from ext/spl/tests/spl_autoload_006.phpt rename to ext/spl/tests/autoloading/spl_autoload_006.phpt diff --git a/ext/spl/tests/spl_autoload_007.phpt b/ext/spl/tests/autoloading/spl_autoload_007.phpt similarity index 100% rename from ext/spl/tests/spl_autoload_007.phpt rename to ext/spl/tests/autoloading/spl_autoload_007.phpt diff --git a/ext/spl/tests/spl_autoload_008.phpt b/ext/spl/tests/autoloading/spl_autoload_008.phpt similarity index 100% rename from ext/spl/tests/spl_autoload_008.phpt rename to ext/spl/tests/autoloading/spl_autoload_008.phpt diff --git a/ext/spl/tests/spl_autoload_009.phpt b/ext/spl/tests/autoloading/spl_autoload_009.phpt similarity index 100% rename from ext/spl/tests/spl_autoload_009.phpt rename to ext/spl/tests/autoloading/spl_autoload_009.phpt diff --git a/ext/spl/tests/spl_autoload_010.phpt b/ext/spl/tests/autoloading/spl_autoload_010.phpt similarity index 100% rename from ext/spl/tests/spl_autoload_010.phpt rename to ext/spl/tests/autoloading/spl_autoload_010.phpt diff --git a/ext/spl/tests/spl_autoload_011.phpt b/ext/spl/tests/autoloading/spl_autoload_011.phpt similarity index 100% rename from ext/spl/tests/spl_autoload_011.phpt rename to ext/spl/tests/autoloading/spl_autoload_011.phpt diff --git a/ext/spl/tests/spl_autoload_012.phpt b/ext/spl/tests/autoloading/spl_autoload_012.phpt similarity index 100% rename from ext/spl/tests/spl_autoload_012.phpt rename to ext/spl/tests/autoloading/spl_autoload_012.phpt diff --git a/ext/spl/tests/spl_autoload_013.phpt b/ext/spl/tests/autoloading/spl_autoload_013.phpt similarity index 100% rename from ext/spl/tests/spl_autoload_013.phpt rename to ext/spl/tests/autoloading/spl_autoload_013.phpt diff --git a/ext/spl/tests/spl_autoload_014.phpt b/ext/spl/tests/autoloading/spl_autoload_014.phpt similarity index 100% rename from ext/spl/tests/spl_autoload_014.phpt rename to ext/spl/tests/autoloading/spl_autoload_014.phpt diff --git a/ext/spl/tests/spl_autoload_bug48541.phpt b/ext/spl/tests/autoloading/spl_autoload_bug48541.phpt similarity index 100% rename from ext/spl/tests/spl_autoload_bug48541.phpt rename to ext/spl/tests/autoloading/spl_autoload_bug48541.phpt diff --git a/ext/spl/tests/spl_autoload_call_basic.phpt b/ext/spl/tests/autoloading/spl_autoload_call_basic.phpt similarity index 100% rename from ext/spl/tests/spl_autoload_call_basic.phpt rename to ext/spl/tests/autoloading/spl_autoload_call_basic.phpt diff --git a/ext/spl/tests/spl_autoload_called_scope.phpt b/ext/spl/tests/autoloading/spl_autoload_called_scope.phpt similarity index 100% rename from ext/spl/tests/spl_autoload_called_scope.phpt rename to ext/spl/tests/autoloading/spl_autoload_called_scope.phpt diff --git a/ext/spl/tests/spl_autoload_throw_with_spl_autoloader_call_as_autoloader.phpt b/ext/spl/tests/autoloading/spl_autoload_throw_with_spl_autoloader_call_as_autoloader.phpt similarity index 100% rename from ext/spl/tests/spl_autoload_throw_with_spl_autoloader_call_as_autoloader.phpt rename to ext/spl/tests/autoloading/spl_autoload_throw_with_spl_autoloader_call_as_autoloader.phpt diff --git a/ext/spl/tests/spl_autoload_unregister_without_registrations.phpt b/ext/spl/tests/autoloading/spl_autoload_unregister_without_registrations.phpt similarity index 100% rename from ext/spl/tests/spl_autoload_unregister_without_registrations.phpt rename to ext/spl/tests/autoloading/spl_autoload_unregister_without_registrations.phpt diff --git a/ext/spl/tests/spl_autoload_warn_on_false_do_throw.phpt b/ext/spl/tests/autoloading/spl_autoload_warn_on_false_do_throw.phpt similarity index 100% rename from ext/spl/tests/spl_autoload_warn_on_false_do_throw.phpt rename to ext/spl/tests/autoloading/spl_autoload_warn_on_false_do_throw.phpt diff --git a/ext/spl/tests/testclass b/ext/spl/tests/autoloading/testclass similarity index 100% rename from ext/spl/tests/testclass rename to ext/spl/tests/autoloading/testclass diff --git a/ext/spl/tests/testclass.class.inc b/ext/spl/tests/autoloading/testclass.class.inc similarity index 100% rename from ext/spl/tests/testclass.class.inc rename to ext/spl/tests/autoloading/testclass.class.inc diff --git a/ext/spl/tests/testclass.inc b/ext/spl/tests/autoloading/testclass.inc similarity index 100% rename from ext/spl/tests/testclass.inc rename to ext/spl/tests/autoloading/testclass.inc diff --git a/ext/spl/tests/testclass.php.inc b/ext/spl/tests/autoloading/testclass.php.inc similarity index 100% rename from ext/spl/tests/testclass.php.inc rename to ext/spl/tests/autoloading/testclass.php.inc From 0cd648a42e760489bf12a6042c84d4dd8631f494 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 5 Jan 2026 17:38:47 +0100 Subject: [PATCH 09/14] ext/spl: refactor spl_autoload() Remove unused parameter Use zend_string_concat2() API Use size_t for ext_len parameter type Return bool instead of int --- ext/spl/php_spl.c | 37 ++++++++++++++----------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/ext/spl/php_spl.c b/ext/spl/php_spl.c index 0acfcbc5c16d..834058a993f9 100644 --- a/ext/spl/php_spl.c +++ b/ext/spl/php_spl.c @@ -34,7 +34,6 @@ #include "spl_heap.h" #include "zend_exceptions.h" #include "zend_interfaces.h" -#include "main/snprintf.h" ZEND_TLS zend_string *spl_autoload_extensions; ZEND_TLS HashTable *spl_autoload_functions; @@ -224,21 +223,19 @@ PHP_FUNCTION(spl_classes) } /* }}} */ -static int spl_autoload(zend_string *class_name, zend_string *lc_name, const char *ext, int ext_len) /* {{{ */ +static bool spl_autoload(zend_string *lc_name, const char *ext, size_t ext_len) /* {{{ */ { zend_string *class_file; zval dummy; zend_file_handle file_handle; - zend_op_array *new_op_array; zval result; - int ret; - class_file = zend_strpprintf(0, "%s%.*s", ZSTR_VAL(lc_name), ext_len, ext); + class_file = zend_string_concat2(ZSTR_VAL(lc_name), ZSTR_LEN(lc_name), ext, ext_len); #if DEFAULT_SLASH != '\\' { char *ptr = ZSTR_VAL(class_file); - char *end = ptr + ZSTR_LEN(class_file); + const char *end = ptr + ZSTR_LEN(class_file); while ((ptr = memchr(ptr, '\\', (end - ptr))) != NULL) { *ptr = DEFAULT_SLASH; @@ -246,22 +243,20 @@ static int spl_autoload(zend_string *class_name, zend_string *lc_name, const cha } #endif + bool ret = false; zend_stream_init_filename_ex(&file_handle, class_file); - ret = php_stream_open_for_zend_ex(&file_handle, USE_PATH|STREAM_OPEN_FOR_INCLUDE); - - if (ret == SUCCESS) { + if (php_stream_open_for_zend_ex(&file_handle, USE_PATH|STREAM_OPEN_FOR_INCLUDE) == SUCCESS) { zend_string *opened_path; if (!file_handle.opened_path) { file_handle.opened_path = zend_string_copy(class_file); } opened_path = zend_string_copy(file_handle.opened_path); ZVAL_NULL(&dummy); + zend_op_array *new_op_array = NULL; if (zend_hash_add(&EG(included_files), opened_path, &dummy)) { new_op_array = zend_compile_file(&file_handle, ZEND_REQUIRE); - } else { - new_op_array = NULL; } - zend_string_release_ex(opened_path, 0); + zend_string_release_ex(opened_path, false); if (new_op_array) { uint32_t orig_jit_trace_num = EG(jit_trace_num); @@ -271,24 +266,20 @@ static int spl_autoload(zend_string *class_name, zend_string *lc_name, const cha destroy_op_array(new_op_array); efree(new_op_array); - if (!EG(exception)) { - zval_ptr_dtor(&result); - } + zval_ptr_dtor(&result); - zend_destroy_file_handle(&file_handle); - zend_string_release(class_file); - return zend_hash_exists(EG(class_table), lc_name); + ret = zend_hash_exists(EG(class_table), lc_name); } } zend_destroy_file_handle(&file_handle); zend_string_release(class_file); - return 0; + return ret; } /* }}} */ /* {{{ Default autoloader implementation */ PHP_FUNCTION(spl_autoload) { - int pos_len, pos1_len; + size_t pos_len, pos1_len; char *pos, *pos1; zend_string *class_name, *lc_name, *file_exts = NULL; @@ -305,18 +296,18 @@ PHP_FUNCTION(spl_autoload) pos_len = sizeof(SPL_DEFAULT_FILE_EXTENSIONS) - 1; } else { pos = ZSTR_VAL(file_exts); - pos_len = (int)ZSTR_LEN(file_exts); + pos_len = ZSTR_LEN(file_exts); } lc_name = zend_string_tolower(class_name); while (pos && *pos && !EG(exception)) { pos1 = strchr(pos, ','); if (pos1) { - pos1_len = (int)(pos1 - pos); + pos1_len = (size_t)(pos1 - pos); } else { pos1_len = pos_len; } - if (spl_autoload(class_name, lc_name, pos, pos1_len)) { + if (spl_autoload(lc_name, pos, pos1_len)) { break; /* loaded */ } pos = pos1 ? pos1 + 1 : NULL; From 8326ad029d5220d8d413d8e59a15b8a44f39bec0 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 5 Jan 2026 18:39:30 +0100 Subject: [PATCH 10/14] ext/spl: refactor autoloading to use newer FCC APIs --- ext/spl/php_spl.c | 170 +++++++++++----------------------------------- 1 file changed, 41 insertions(+), 129 deletions(-) diff --git a/ext/spl/php_spl.c b/ext/spl/php_spl.c index 834058a993f9..bfe8e6f29017 100644 --- a/ext/spl/php_spl.c +++ b/ext/spl/php_spl.c @@ -340,67 +340,11 @@ PHP_FUNCTION(spl_autoload_extensions) } } /* }}} */ -typedef struct { - zend_function *func_ptr; - zend_object *obj; - zend_object *closure; - zend_class_entry *ce; -} autoload_func_info; - -static void autoload_func_info_destroy(autoload_func_info *alfi) { - if (alfi->obj) { - zend_object_release(alfi->obj); - } - if (alfi->func_ptr && - UNEXPECTED(alfi->func_ptr->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { - zend_string_release_ex(alfi->func_ptr->common.function_name, 0); - zend_free_trampoline(alfi->func_ptr); - } - if (alfi->closure) { - zend_object_release(alfi->closure); - } - efree(alfi); -} - static void autoload_func_info_zval_dtor(zval *element) { - autoload_func_info_destroy(Z_PTR_P(element)); -} - -static autoload_func_info *autoload_func_info_from_fci( - zend_fcall_info *fci, zend_fcall_info_cache *fcc) { - autoload_func_info *alfi = emalloc(sizeof(autoload_func_info)); - alfi->ce = fcc->calling_scope; - alfi->func_ptr = fcc->function_handler; - alfi->obj = fcc->object; - if (alfi->obj) { - GC_ADDREF(alfi->obj); - } - if (Z_TYPE(fci->function_name) == IS_OBJECT) { - alfi->closure = Z_OBJ(fci->function_name); - GC_ADDREF(alfi->closure); - } else { - alfi->closure = NULL; - } - return alfi; -} - -static bool autoload_func_info_equals( - const autoload_func_info *alfi1, const autoload_func_info *alfi2) { - if (UNEXPECTED( - (alfi1->func_ptr->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) && - (alfi2->func_ptr->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) - )) { - return alfi1->obj == alfi2->obj - && alfi1->ce == alfi2->ce - && alfi1->closure == alfi2->closure - && zend_string_equals(alfi1->func_ptr->common.function_name, alfi2->func_ptr->common.function_name) - ; - } - return alfi1->func_ptr == alfi2->func_ptr - && alfi1->obj == alfi2->obj - && alfi1->ce == alfi2->ce - && alfi1->closure == alfi2->closure; + zend_fcall_info_cache *fcc = Z_PTR_P(element); + zend_fcc_dtor(fcc); + efree(fcc); } static zend_class_entry *spl_perform_autoload(zend_string *class_name, zend_string *lc_name) { @@ -412,34 +356,27 @@ static zend_class_entry *spl_perform_autoload(zend_string *class_name, zend_stri * because autoloaders may be added/removed during autoloading. */ HashPosition pos; zend_hash_internal_pointer_reset_ex(spl_autoload_functions, &pos); - while (1) { - autoload_func_info *alfi = + + zval zname; + ZVAL_STR(&zname, class_name); + while (true) { + zend_fcall_info_cache *fcc = zend_hash_get_current_data_ptr_ex(spl_autoload_functions, &pos); - if (!alfi) { + if (!fcc) { break; } - zend_function *func = alfi->func_ptr; - if (UNEXPECTED(func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { - func = emalloc(sizeof(zend_op_array)); - memcpy(func, alfi->func_ptr, sizeof(zend_op_array)); - zend_string_addref(func->op_array.function_name); - } - - zval param; - ZVAL_STR(¶m, class_name); - zend_call_known_function(func, alfi->obj, alfi->ce, NULL, 1, ¶m, NULL); - if (EG(exception)) { + zend_call_known_fcc(fcc, NULL, 1, &zname, NULL); + if (UNEXPECTED(EG(exception))) { break; } - if (ZSTR_HAS_CE_CACHE(class_name) && ZSTR_GET_CE_CACHE(class_name)) { + if (ZSTR_HAS_CE_CACHE(class_name) && ZSTR_GET_CE_CACHE(class_name)) { return (zend_class_entry*)ZSTR_GET_CE_CACHE(class_name); - } else { - zend_class_entry *ce = zend_hash_find_ptr(EG(class_table), lc_name); - if (ce) { - return ce; - } + } + zend_class_entry *ce = zend_hash_find_ptr(EG(class_table), lc_name); + if (ce != NULL) { + return ce; } zend_hash_move_forward_ex(spl_autoload_functions, &pos); @@ -471,14 +408,14 @@ PHP_FUNCTION(spl_autoload_call) zend_hash_rehash(ht); \ } while (0) -static Bucket *spl_find_registered_function(autoload_func_info *find_alfi) { +static Bucket *spl_find_registered_function(const zend_fcall_info_cache *find_fcc) { if (!spl_autoload_functions) { return NULL; } - autoload_func_info *alfi; - ZEND_HASH_MAP_FOREACH_PTR(spl_autoload_functions, alfi) { - if (autoload_func_info_equals(alfi, find_alfi)) { + zend_fcall_info_cache *fcc; + ZEND_HASH_MAP_FOREACH_PTR(spl_autoload_functions, fcc) { + if (zend_fcc_equals(fcc, find_fcc)) { return _p; } } ZEND_HASH_FOREACH_END(); @@ -492,7 +429,6 @@ PHP_FUNCTION(spl_autoload_register) bool prepend = 0; zend_fcall_info fci = {0}; zend_fcall_info_cache fcc; - autoload_func_info *alfi; ZEND_PARSE_PARAMETERS_START(0, 3) Z_PARAM_OPTIONAL @@ -508,14 +444,14 @@ PHP_FUNCTION(spl_autoload_register) if (!spl_autoload_functions) { ALLOC_HASHTABLE(spl_autoload_functions); - zend_hash_init(spl_autoload_functions, 1, NULL, autoload_func_info_zval_dtor, 0); + zend_hash_init(spl_autoload_functions, 1, NULL, autoload_func_info_zval_dtor, false); /* Initialize as non-packed hash table for prepend functionality. */ zend_hash_real_init_mixed(spl_autoload_functions); } /* If first arg is not null */ if (ZEND_FCI_INITIALIZED(fci)) { - if (!fcc.function_handler) { + if (!ZEND_FCC_INITIALIZED(fcc)) { /* Call trampoline has been cleared by zpp. Refetch it, because we want to deal * with it ourselves. It is important that it is not refetched on every call, * because calls may occur from different scopes. */ @@ -527,30 +463,19 @@ PHP_FUNCTION(spl_autoload_register) zend_argument_value_error(1, "must not be the spl_autoload_call() function"); RETURN_THROWS(); } - - alfi = autoload_func_info_from_fci(&fci, &fcc); - if (UNEXPECTED(alfi->func_ptr == &EG(trampoline))) { - zend_function *copy = emalloc(sizeof(zend_op_array)); - - memcpy(copy, alfi->func_ptr, sizeof(zend_op_array)); - alfi->func_ptr->common.function_name = NULL; - alfi->func_ptr = copy; - } } else { - alfi = emalloc(sizeof(autoload_func_info)); - alfi->func_ptr = zend_hash_str_find_ptr( - CG(function_table), "spl_autoload", sizeof("spl_autoload") - 1); - alfi->obj = NULL; - alfi->ce = NULL; - alfi->closure = NULL; + memset(&fcc, 0, sizeof(fcc)); + fcc.function_handler = zend_hash_str_find_ptr(CG(function_table), ZEND_STRL("spl_autoload")); } - if (spl_find_registered_function(alfi)) { - autoload_func_info_destroy(alfi); + if (spl_find_registered_function(&fcc)) { + /* Release call trampoline */ + zend_release_fcall_info_cache(&fcc); RETURN_TRUE; } - zend_hash_next_index_insert_ptr(spl_autoload_functions, alfi); + zend_fcc_addref(&fcc); + zend_hash_next_index_insert_mem(spl_autoload_functions, &fcc, sizeof(zend_fcall_info_cache)); if (prepend && spl_autoload_functions->nNumOfElements > 1) { /* Move the newly created element to the head of the hashtable */ HT_MOVE_TAIL_TO_HEAD(spl_autoload_functions); @@ -592,9 +517,9 @@ PHP_FUNCTION(spl_autoload_unregister) zend_is_callable_ex(&fci.function_name, NULL, 0, NULL, &fcc, NULL); } - autoload_func_info *alfi = autoload_func_info_from_fci(&fci, &fcc); - Bucket *p = spl_find_registered_function(alfi); - autoload_func_info_destroy(alfi); + Bucket *p = spl_find_registered_function(&fcc); + /* Release trampoline */ + zend_release_fcall_info_cache(&fcc); if (p) { zend_hash_del_bucket(spl_autoload_functions, p); RETURN_TRUE; @@ -606,32 +531,19 @@ PHP_FUNCTION(spl_autoload_unregister) /* {{{ Return all registered autoloader functions */ PHP_FUNCTION(spl_autoload_functions) { - autoload_func_info *alfi; - ZEND_PARSE_PARAMETERS_NONE(); - array_init(return_value); if (spl_autoload_functions) { - ZEND_HASH_MAP_FOREACH_PTR(spl_autoload_functions, alfi) { - if (alfi->closure) { - GC_ADDREF(alfi->closure); - add_next_index_object(return_value, alfi->closure); - } else if (alfi->func_ptr->common.scope) { - zval tmp; - - array_init(&tmp); - if (alfi->obj) { - GC_ADDREF(alfi->obj); - add_next_index_object(&tmp, alfi->obj); - } else { - add_next_index_str(&tmp, zend_string_copy(alfi->ce->name)); - } - add_next_index_str(&tmp, zend_string_copy(alfi->func_ptr->common.function_name)); - add_next_index_zval(return_value, &tmp); - } else { - add_next_index_str(return_value, zend_string_copy(alfi->func_ptr->common.function_name)); - } + zend_fcall_info_cache *fcc; + + array_init_size(return_value, zend_hash_num_elements(spl_autoload_functions)); + ZEND_HASH_MAP_FOREACH_PTR(spl_autoload_functions, fcc) { + zval tmp; + zend_get_callable_zval_from_fcc(fcc, &tmp); + add_next_index_zval(return_value, &tmp); } ZEND_HASH_FOREACH_END(); + } else { + RETURN_EMPTY_ARRAY(); } } /* }}} */ From ab2cd2d6b6947923b3d75e0f5c7ef55d9a44a0dc Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 5 Jan 2026 18:43:11 +0100 Subject: [PATCH 11/14] ext/spl: inline HT_MOVE_TAIL_TO_HEAD macro This is used only once anyway --- ext/spl/php_spl.c | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/ext/spl/php_spl.c b/ext/spl/php_spl.c index bfe8e6f29017..873f1e3a83ec 100644 --- a/ext/spl/php_spl.c +++ b/ext/spl/php_spl.c @@ -398,16 +398,6 @@ PHP_FUNCTION(spl_autoload_call) zend_string_release(lc_name); } /* }}} */ -#define HT_MOVE_TAIL_TO_HEAD(ht) \ - ZEND_ASSERT(!HT_IS_PACKED(ht)); \ - do { \ - Bucket tmp = (ht)->arData[(ht)->nNumUsed-1]; \ - memmove((ht)->arData + 1, (ht)->arData, \ - sizeof(Bucket) * ((ht)->nNumUsed - 1)); \ - (ht)->arData[0] = tmp; \ - zend_hash_rehash(ht); \ - } while (0) - static Bucket *spl_find_registered_function(const zend_fcall_info_cache *find_fcc) { if (!spl_autoload_functions) { return NULL; @@ -478,7 +468,11 @@ PHP_FUNCTION(spl_autoload_register) zend_hash_next_index_insert_mem(spl_autoload_functions, &fcc, sizeof(zend_fcall_info_cache)); if (prepend && spl_autoload_functions->nNumOfElements > 1) { /* Move the newly created element to the head of the hashtable */ - HT_MOVE_TAIL_TO_HEAD(spl_autoload_functions); + ZEND_ASSERT(!HT_IS_PACKED(spl_autoload_functions)); + Bucket tmp = spl_autoload_functions->arData[spl_autoload_functions->nNumUsed-1]; + memmove(spl_autoload_functions->arData + 1, spl_autoload_functions->arData, sizeof(Bucket) * (spl_autoload_functions->nNumUsed - 1)); + spl_autoload_functions->arData[0] = tmp; + zend_hash_rehash(spl_autoload_functions); } RETURN_TRUE; From 0f4fd2d03c301c3bac1646cf03355f5054cf0b30 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sun, 11 Jan 2026 19:57:07 +0000 Subject: [PATCH 12/14] ext/spl: use ZPP specifier that doesn't free trampoline for spl_autoload_unregister() --- ext/spl/php_spl.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/ext/spl/php_spl.c b/ext/spl/php_spl.c index 873f1e3a83ec..35cc7d426911 100644 --- a/ext/spl/php_spl.c +++ b/ext/spl/php_spl.c @@ -485,11 +485,12 @@ PHP_FUNCTION(spl_autoload_unregister) zend_fcall_info_cache fcc; ZEND_PARSE_PARAMETERS_START(1, 1) - Z_PARAM_FUNC(fci, fcc) + Z_PARAM_FUNC_NO_TRAMPOLINE_FREE(fci, fcc) ZEND_PARSE_PARAMETERS_END(); - if (fcc.function_handler && zend_string_equals_literal( - fcc.function_handler->common.function_name, "spl_autoload_call")) { + if (zend_string_equals_literal(fcc.function_handler->common.function_name, "spl_autoload_call")) { + /* Release trampoline */ + zend_release_fcall_info_cache(&fcc); php_error_docref(NULL, E_DEPRECATED, "Using spl_autoload_call() as a callback for spl_autoload_unregister() is deprecated," " to remove all registered autoloaders, call spl_autoload_unregister()" @@ -504,13 +505,6 @@ PHP_FUNCTION(spl_autoload_unregister) RETURN_TRUE; } - if (!fcc.function_handler) { - /* Call trampoline has been cleared by zpp. Refetch it, because we want to deal - * with it ourselves. It is important that it is not refetched on every call, - * because calls may occur from different scopes. */ - zend_is_callable_ex(&fci.function_name, NULL, 0, NULL, &fcc, NULL); - } - Bucket *p = spl_find_registered_function(&fcc); /* Release trampoline */ zend_release_fcall_info_cache(&fcc); From 44d6417bd7c12ae2e7fadd7cf950529743e5bb42 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Wed, 19 Nov 2025 21:19:33 +0000 Subject: [PATCH 13/14] ext/sockets: GH-20532 socket_addrinfo_lookup() sets EAI error code on resolution failures with a new optional argument. close GH-20534 --- NEWS | 2 + UPGRADING | 22 ++++++ ext/sockets/sockets.c | 11 ++- ext/sockets/sockets.stub.php | 133 ++++++++++++++++++++++++++++++++- ext/sockets/sockets_arginfo.h | 57 +++++++++++++- ext/sockets/tests/gh20532.phpt | 16 ++++ 6 files changed, 236 insertions(+), 5 deletions(-) create mode 100644 ext/sockets/tests/gh20532.phpt diff --git a/NEWS b/NEWS index 74816c94a290..86a2174a986f 100644 --- a/NEWS +++ b/NEWS @@ -86,6 +86,8 @@ PHP NEWS transmitted data can remain unacknowledged. (James Lucas) . Added AF_UNSPEC support for sock_addrinfo_lookup() as a sole umbrella for AF_INET* family only. (David Carlier) + . Fixed GH-20532 (socket_addrinfo_lookup gives the error code with a new optional + parameter). (David Carlier) - SPL: . DirectoryIterator key can now work better with filesystem supporting larger diff --git a/UPGRADING b/UPGRADING index 3d0a56756cdc..b72a63796647 100644 --- a/UPGRADING +++ b/UPGRADING @@ -77,6 +77,11 @@ PHP 8.6 UPGRADE NOTES - Phar: . Phar::mungServer() now supports reference values. +- Sockets: + . socket_addrinfo_lookup() now has an additional optional argument $error + when not null, and on failure, gives the error code (one of the EAI_* + constants). + - Zip: . ZipArchive::extractTo now raises a TypeError for the files argument if one or more of the entries is not @@ -112,6 +117,23 @@ PHP 8.6 UPGRADE NOTES - Sockets: . TCP_USER_TIMEOUT (Linux only). . AF_UNSPEC. + . EAI_BADFLAGS. + . EAI_NONAME. + . EAI_AGAIN. + . EAI_FAIL. + . EAI_NODATA. + . EAI_FAMILY. + . EAI_SOCKTYPE. + . EAI_SERVICE. + . EAI_ADDRFAMILY. + . EAI_SYSTEM. + . EAI_OVERFLOW + . EAI_INPROGRESS. + . EAI_CANCELED. + . EAI_NOTCANCELED. + . EAI_ALLDONE. + . EAI_INTR. + . EAI_IDN_ENCODE. ======================================== 11. Changes to INI File Handling diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 54f862d55366..accaf4bbbcfd 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -2751,16 +2751,18 @@ PHP_FUNCTION(socket_addrinfo_lookup) { zend_string *service = NULL; zend_string *hostname, *key; - zval *hint, *zhints = NULL; + zval *hint, *zhints = NULL, *error_code = NULL; + int ret = 0; struct addrinfo hints, *result, *rp; php_addrinfo *res; - ZEND_PARSE_PARAMETERS_START(1, 3) + ZEND_PARSE_PARAMETERS_START(1, 4) Z_PARAM_STR(hostname) Z_PARAM_OPTIONAL Z_PARAM_STR_OR_NULL(service) Z_PARAM_ARRAY(zhints) + Z_PARAM_ZVAL_OR_NULL(error_code) ZEND_PARSE_PARAMETERS_END(); memset(&hints, 0, sizeof(hints)); @@ -2848,7 +2850,10 @@ PHP_FUNCTION(socket_addrinfo_lookup) } ZEND_HASH_FOREACH_END(); } - if (getaddrinfo(ZSTR_VAL(hostname), service ? ZSTR_VAL(service) : NULL, &hints, &result) != 0) { + if ((ret = getaddrinfo(ZSTR_VAL(hostname), service ? ZSTR_VAL(service) : NULL, &hints, &result)) != 0) { + if (error_code) { + ZEND_TRY_ASSIGN_REF_LONG(error_code, ret); + } RETURN_FALSE; } diff --git a/ext/sockets/sockets.stub.php b/ext/sockets/sockets.stub.php index 0f645da25ce5..56b2ac07e868 100644 --- a/ext/sockets/sockets.stub.php +++ b/ext/sockets/sockets.stub.php @@ -2067,6 +2067,136 @@ const SHUT_RDWR = UNKNOWN; #endif + +#ifdef EAI_BADFLAGS +/** + * @var int + * @cvalue EAI_BADFLAGS + */ +const EAI_BADFLAGS = UNKNOWN; +#endif +#ifdef EAI_NONAME +/** + * @var int + * @cvalue EAI_NONAME + */ +const EAI_NONAME = UNKNOWN; +#endif +#ifdef EAI_AGAIN +/** + * @var int + * @cvalue EAI_AGAIN + */ +const EAI_AGAIN = UNKNOWN; +#endif +#ifdef EAI_FAIL +/** + * @var int + * @cvalue EAI_FAIL + */ +const EAI_FAIL = UNKNOWN; +#endif +#ifdef EAI_NODATA +/** + * @var int + * @cvalue EAI_NODATA + */ +const EAI_NODATA = UNKNOWN; +#endif +#ifdef EAI_FAMILY +/** + * @var int + * @cvalue EAI_FAMILY + */ +const EAI_FAMILY = UNKNOWN; +#endif +#ifdef EAI_SOCKTYPE +/** + * @var int + * @cvalue EAI_SOCKTYPE + */ +const EAI_SOCKTYPE = UNKNOWN; +#endif +#ifdef EAI_SERVICE +/** + * @var int + * @cvalue EAI_SERVICE + */ +const EAI_SERVICE = UNKNOWN; +#endif +#ifdef EAI_ADDRFAMILY +/** + * @var int + * @cvalue EAI_ADDRFAMILY + */ +const EAI_ADDRFAMILY = UNKNOWN; +#else +#ifdef EAI_FAMILY +/** + * @var int + * @cvalue EAI_FAMILY + */ +const EAI_ADDRFAMILY = UNKNOWN; +#else +#endif +#endif +#ifdef EAI_SYSTEM +/** + * @var int + * @cvalue EAI_SYSTEM + */ +const EAI_SYSTEM = UNKNOWN; +#endif +#ifdef EAI_OVERFLOW +/** + * @var int + * @cvalue EAI_OVERFLOW + */ +const EAI_OVERFLOW = UNKNOWN; +#endif +#ifdef EAI_INPROGRESS +/** + * @var int + * @cvalue EAI_INPROGRESS + */ +const EAI_INPROGRESS = UNKNOWN; +#endif +#ifdef EAI_CANCELED +/** + * @var int + * @cvalue EAI_CANCELED + */ +const EAI_CANCELED = UNKNOWN; +#endif +#ifdef EAI_NOTCANCELED +/** + * @var int + * @cvalue EAI_NOTCANCELED + */ +const EAI_NOTCANCELED = UNKNOWN; +#endif +#ifdef EAI_ALLDONE +/** + * @var int + * @cvalue EAI_ALLDONE + */ +const EAI_ALLDONE = UNKNOWN; +#endif +#ifdef EAI_INTR +/** + * @var int + * @cvalue EAI_INTR + */ +const EAI_INTR = UNKNOWN; +#endif +#ifdef EAI_IDN_ENCODE +/** + * @var int + * @cvalue EAI_IDN_ENCODE + */ +const EAI_IDN_ENCODE = UNKNOWN; +#endif + /** * @strict-properties * @not-serializable @@ -2187,9 +2317,10 @@ function socket_cmsg_space(int $level, int $type, int $num = 0): ?int {} /** * @return array|false + * @param int $error_code * @refcount 1 */ -function socket_addrinfo_lookup(string $host, ?string $service = null, array $hints = []): array|false {} +function socket_addrinfo_lookup(string $host, ?string $service = null, array $hints = [], &$error_code = null): array|false {} function socket_addrinfo_connect(AddressInfo $address): Socket|false {} diff --git a/ext/sockets/sockets_arginfo.h b/ext/sockets/sockets_arginfo.h index 50a81c5ee3a7..522f356f853b 100644 --- a/ext/sockets/sockets_arginfo.h +++ b/ext/sockets/sockets_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: a89c4e54ab913728e10baf8f32b45323495d685b */ + * Stub hash: 5e71ef16f2121bd6c75794673d0e0a394759ff8b */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_socket_select, 0, 4, MAY_BE_LONG|MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(1, read, IS_ARRAY, 1) @@ -184,6 +184,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_socket_addrinfo_lookup, 0, 1, MA ZEND_ARG_TYPE_INFO(0, host, IS_STRING, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, service, IS_STRING, 1, "null") ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, hints, IS_ARRAY, 0, "[]") + ZEND_ARG_INFO_WITH_DEFAULT_VALUE(1, error_code, "null") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_socket_addrinfo_connect, 0, 1, Socket, MAY_BE_FALSE) @@ -1054,6 +1055,60 @@ static void register_sockets_symbols(int module_number) REGISTER_LONG_CONSTANT("SHUT_WR", SHUT_WR, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SHUT_RDWR", SHUT_RDWR, CONST_PERSISTENT); #endif +#if defined(EAI_BADFLAGS) + REGISTER_LONG_CONSTANT("EAI_BADFLAGS", EAI_BADFLAGS, CONST_PERSISTENT); +#endif +#if defined(EAI_NONAME) + REGISTER_LONG_CONSTANT("EAI_NONAME", EAI_NONAME, CONST_PERSISTENT); +#endif +#if defined(EAI_AGAIN) + REGISTER_LONG_CONSTANT("EAI_AGAIN", EAI_AGAIN, CONST_PERSISTENT); +#endif +#if defined(EAI_FAIL) + REGISTER_LONG_CONSTANT("EAI_FAIL", EAI_FAIL, CONST_PERSISTENT); +#endif +#if defined(EAI_NODATA) + REGISTER_LONG_CONSTANT("EAI_NODATA", EAI_NODATA, CONST_PERSISTENT); +#endif +#if defined(EAI_FAMILY) + REGISTER_LONG_CONSTANT("EAI_FAMILY", EAI_FAMILY, CONST_PERSISTENT); +#endif +#if defined(EAI_SOCKTYPE) + REGISTER_LONG_CONSTANT("EAI_SOCKTYPE", EAI_SOCKTYPE, CONST_PERSISTENT); +#endif +#if defined(EAI_SERVICE) + REGISTER_LONG_CONSTANT("EAI_SERVICE", EAI_SERVICE, CONST_PERSISTENT); +#endif +#if defined(EAI_ADDRFAMILY) + REGISTER_LONG_CONSTANT("EAI_ADDRFAMILY", EAI_ADDRFAMILY, CONST_PERSISTENT); +#endif +#if !(defined(EAI_ADDRFAMILY)) && defined(EAI_FAMILY) + REGISTER_LONG_CONSTANT("EAI_ADDRFAMILY", EAI_FAMILY, CONST_PERSISTENT); +#endif +#if defined(EAI_SYSTEM) + REGISTER_LONG_CONSTANT("EAI_SYSTEM", EAI_SYSTEM, CONST_PERSISTENT); +#endif +#if defined(EAI_OVERFLOW) + REGISTER_LONG_CONSTANT("EAI_OVERFLOW", EAI_OVERFLOW, CONST_PERSISTENT); +#endif +#if defined(EAI_INPROGRESS) + REGISTER_LONG_CONSTANT("EAI_INPROGRESS", EAI_INPROGRESS, CONST_PERSISTENT); +#endif +#if defined(EAI_CANCELED) + REGISTER_LONG_CONSTANT("EAI_CANCELED", EAI_CANCELED, CONST_PERSISTENT); +#endif +#if defined(EAI_NOTCANCELED) + REGISTER_LONG_CONSTANT("EAI_NOTCANCELED", EAI_NOTCANCELED, CONST_PERSISTENT); +#endif +#if defined(EAI_ALLDONE) + REGISTER_LONG_CONSTANT("EAI_ALLDONE", EAI_ALLDONE, CONST_PERSISTENT); +#endif +#if defined(EAI_INTR) + REGISTER_LONG_CONSTANT("EAI_INTR", EAI_INTR, CONST_PERSISTENT); +#endif +#if defined(EAI_IDN_ENCODE) + REGISTER_LONG_CONSTANT("EAI_IDN_ENCODE", EAI_IDN_ENCODE, CONST_PERSISTENT); +#endif } static zend_class_entry *register_class_Socket(void) diff --git a/ext/sockets/tests/gh20532.phpt b/ext/sockets/tests/gh20532.phpt new file mode 100644 index 000000000000..f3368c830369 --- /dev/null +++ b/ext/sockets/tests/gh20532.phpt @@ -0,0 +1,16 @@ +--TEST-- +GH-20562 - socket_addrinfo_lookup() returns error codes on resolution failures. +--EXTENSIONS-- +sockets +--FILE-- + AF_INET], $error_code) === false && in_array($error_code, [EAI_FAMILY, EAI_ADDRFAMILY, EAI_NONAME, EAI_NODATA])); +var_dump(socket_addrinfo_lookup("example.com", "http", ['ai_socktype' => SOCK_RAW, 'ai_flags' => 2147483647], $error_code) === false && in_array($error_code, [EAI_SOCKTYPE, EAI_SERVICE, EAI_BADFLAGS, EAI_NONAME])); +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) + From 8f55b0850fd3ab030fffff3d3764d58d06ea744a Mon Sep 17 00:00:00 2001 From: David CARLIER Date: Tue, 20 Jan 2026 21:34:50 +0000 Subject: [PATCH 14/14] ext/sqlite3: Sqlite3::openBlob() code path simplification. (#20969) * ext/sqlite3: Sqlite3::openBlob() code path simplification. since the stream is opened in non persistent mode, the failure code path is dead (so are the missing leaks fixes). --- ext/sqlite3/sqlite3.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/ext/sqlite3/sqlite3.c b/ext/sqlite3/sqlite3.c index da24b037861a..6d55409ca45a 100644 --- a/ext/sqlite3/sqlite3.c +++ b/ext/sqlite3/sqlite3.c @@ -1270,13 +1270,10 @@ PHP_METHOD(SQLite3, openBlob) mode = "r+b"; } + // since it is not persistent, php_stream_alloc can't fail stream = php_stream_alloc(&php_stream_sqlite3_ops, sqlite3_stream, 0, mode); - - if (stream) { - php_stream_to_zval(stream, return_value); - } else { - RETURN_FALSE; - } + ZEND_ASSERT(stream != NULL); + php_stream_to_zval(stream, return_value); } /* }}} */