From 453223babf8339c31add47da77bbc94ef20ce74a Mon Sep 17 00:00:00 2001 From: Lothar Serra Mari Date: Fri, 7 Aug 2026 15:53:03 +0200 Subject: [PATCH 1/4] tests: Allow overriding TEST_PHP_EXECUTABLE for FPM SAPI (#23087) The test suite derives the path to php-fpm from TEST_PHP_EXECUTABLE. This fails on custom build directory layouts, leading to skipping all php-fpm related tests. Users can provide TEST_PHP_FPM_EXECUTABLE for a custom binary path; if this is unset, we'll fall back to TEST_PHP_EXECUTABLE instead. --- sapi/fpm/tests/tester.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sapi/fpm/tests/tester.inc b/sapi/fpm/tests/tester.inc index 907988654337..557f8c25e193 100644 --- a/sapi/fpm/tests/tester.inc +++ b/sapi/fpm/tests/tester.inc @@ -225,7 +225,7 @@ class Tester */ static public function findExecutable(): bool|string { - $phpPath = getenv("TEST_PHP_EXECUTABLE"); + $phpPath = getenv("TEST_PHP_FPM_EXECUTABLE") ?: getenv("TEST_PHP_EXECUTABLE"); for ($i = 0; $i < 2; $i++) { $slashPosition = strrpos($phpPath, "/"); if ($slashPosition) { From 909f7229b00c3a0ece9885de78299b8d7dbc657a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Fri, 7 Aug 2026 16:08:46 +0200 Subject: [PATCH 2/4] tree-wide: Generate nicer code in `Z_PARAM_*()` helpers wrapping `Z_PARAM_*()` (#23100) Instead of using a fixed variable name for the temporary, it is now derived from the name of the target variable. --- Zend/zend_API.h | 6 +++--- ext/date/php_time.h | 12 ++++++------ ext/date/time_duration.c | 8 ++++---- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Zend/zend_API.h b/Zend/zend_API.h index a3e4e1690d6c..aff9846d21b8 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -2057,9 +2057,9 @@ ZEND_API ZEND_COLD void zend_class_redeclaration_error_ex(int type, zend_string #define Z_PARAM_ENUM(dest, _ce) \ { \ - zend_object *_tmp = NULL; \ - Z_PARAM_OBJ_OF_CLASS(_tmp, _ce); \ - dest = zend_enum_fetch_case_id(_tmp); \ + zend_object *__##dest = NULL; \ + Z_PARAM_OBJ_OF_CLASS(__##dest, _ce); \ + dest = zend_enum_fetch_case_id(__##dest); \ } /* old "p" */ diff --git a/ext/date/php_time.h b/ext/date/php_time.h index a0425fa9ac5e..e6bc7658e389 100644 --- a/ext/date/php_time.h +++ b/ext/date/php_time.h @@ -29,15 +29,15 @@ typedef struct php_date_time_duration { # define Z_DATE_TIME_DURATION_P(zv) php_date_time_duration_from_obj(Z_OBJ_P((zv))) # define Z_PARAM_DATE_TIME_DURATION(d) { \ - zend_object *__d; \ - Z_PARAM_OBJ_OF_CLASS(__d, php_date_ce_time_duration); \ - d = php_date_time_duration_from_obj(__d); \ + zend_object *__##d; \ + Z_PARAM_OBJ_OF_CLASS(__##d, php_date_ce_time_duration); \ + d = php_date_time_duration_from_obj(__##d); \ } # define Z_PARAM_DATE_TIME_DURATION_OR_NULL(d) { \ - zend_object *__d; \ - Z_PARAM_OBJ_OF_CLASS_OR_NULL(__d, php_date_ce_time_duration); \ - d = __d ? php_date_time_duration_from_obj(__d) : NULL; \ + zend_object *__##d; \ + Z_PARAM_OBJ_OF_CLASS_OR_NULL(__##d, php_date_ce_time_duration); \ + d = __##d ? php_date_time_duration_from_obj(__##d) : NULL; \ } PHPAPI extern zend_class_entry *php_date_ce_time_duration; diff --git a/ext/date/time_duration.c b/ext/date/time_duration.c index 14c121ff29bb..0dab1ccb22f3 100644 --- a/ext/date/time_duration.c +++ b/ext/date/time_duration.c @@ -29,14 +29,14 @@ ZEND_STATIC_ASSERT(NANOS_IN_MICRO * MICROS_IN_SEC == NANOS_IN_SEC, ""); ZEND_STATIC_ASSERT(NANOS_IN_MILLI * MILLIS_IN_SEC == NANOS_IN_SEC, ""); #define Z_PARAM_ULONG(l) { \ - zend_long __l; \ - Z_PARAM_LONG(__l); \ - if (__l < 0) { \ + zend_long __##l; \ + Z_PARAM_LONG(__##l); \ + if (__##l < 0) { \ zend_argument_value_error(_i, "must be greater than or equal to 0"); \ _error_code = ZPP_ERROR_FAILURE; \ break; \ } \ - l = __l; \ + l = __##l; \ } ZEND_COLD static void throw_out_of_range_exception(void) From 905afb536542079157a50048a85871890f746f5d Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc <365207+arnaud-lb@users.noreply.github.com> Date: Fri, 7 Aug 2026 16:15:21 +0200 Subject: [PATCH 3/4] JIT: Preserve parent regs in zend_jit_deoptimizer_start() (#22916) Fixes GH-22915 --- NEWS | 4 ++ ext/opcache/jit/zend_jit_ir.c | 28 +++++++++-- ext/opcache/jit/zend_jit_trace.c | 2 +- ext/opcache/tests/jit/gh22915.phpt | 75 ++++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+), 6 deletions(-) create mode 100644 ext/opcache/tests/jit/gh22915.phpt diff --git a/NEWS b/NEWS index 474db936ef15..ba832ef05888 100644 --- a/NEWS +++ b/NEWS @@ -25,6 +25,10 @@ PHP NEWS - OpenSSL: . Fix missing error check on invalid alpn protocols. (ndossche) +- Opcache: + . Fixed bug GH-22916 (Preserve parent regs in zend_jit_deoptimizer_start()). + (Arnaud) + - PCRE: . Fixed bug GH-21134 (Crash with \C + UTF-8). Using \C in UTF-8 patterns is now forbidden. (Arnaud) diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index b48058196c95..4c20c115b848 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -338,6 +338,11 @@ static int zend_jit_assign_to_variable(zend_jit_ctx *jit, zend_jit_addr ref_addr, bool check_exception); +static void zend_jit_preserve_parent_regs(zend_jit_ctx *jit, + zend_ssa *ssa, + zend_jit_trace_info *parent, + uint32_t exit_num); + typedef struct _zend_jit_stub { const char *name; int (*stub)(zend_jit_ctx *jit); @@ -17043,6 +17048,7 @@ static int zend_jit_trace_handler(zend_jit_ctx *jit, const zend_op_array *op_arr static int zend_jit_deoptimizer_start(zend_jit_ctx *jit, zend_string *name, uint32_t trace_num, + zend_jit_trace_info *parent, uint32_t exit_num) { zend_jit_init_ctx(jit, (zend_jit_vm_kind == ZEND_VM_KIND_CALL) ? 0 : IR_START_BR_TARGET); @@ -17055,6 +17061,8 @@ static int zend_jit_deoptimizer_start(zend_jit_ctx *jit, jit->ctx.flags |= IR_SKIP_PROLOGUE; + zend_jit_preserve_parent_regs(jit, NULL, parent, exit_num); + return 1; } @@ -17087,6 +17095,21 @@ static int zend_jit_trace_start(zend_jit_ctx *jit, jit->ctx.flags |= IR_SKIP_PROLOGUE; } + zend_jit_preserve_parent_regs(jit, ssa, parent, exit_num); + + ir_STORE(jit_EG(jit_trace_num), ir_CONST_U32(trace_num)); + + return 1; +} + +static void zend_jit_preserve_parent_regs(zend_jit_ctx *jit, + zend_ssa *ssa, + zend_jit_trace_info *parent, + uint32_t exit_num) +{ + /* Emit early RLOADs of registers used for deoptimization to prevent + * clobbering. zend_jit_deopt_rload() will reference these. */ + if (parent) { int i; int parent_vars_count = parent->exit_info[exit_num].stack_size; @@ -17094,7 +17117,6 @@ static int zend_jit_trace_start(zend_jit_ctx *jit, parent->stack_map + parent->exit_info[exit_num].stack_offset; - /* prevent clobbering of registers used for deoptimization */ for (i = 0; i < parent_vars_count; i++) { if (STACK_FLAGS(parent_stack, i) != ZREG_CONST && STACK_REG(parent_stack, i) != ZREG_NONE) { @@ -17138,10 +17160,6 @@ static int zend_jit_trace_start(zend_jit_ctx *jit, ir_RLOAD_A(parent->exit_info[exit_num].poly_this.reg); } } - - ir_STORE(jit_EG(jit_trace_num), ir_CONST_U32(trace_num)); - - return 1; } static int zend_jit_trace_begin_loop(zend_jit_ctx *jit) diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c index da97d102f202..225257ecd6a4 100644 --- a/ext/opcache/jit/zend_jit_trace.c +++ b/ext/opcache/jit/zend_jit_trace.c @@ -7364,7 +7364,7 @@ static const void *zend_jit_trace_exit_to_vm(uint32_t trace_num, uint32_t exit_n name = zend_jit_trace_escape_name(trace_num, exit_num); - if (!zend_jit_deoptimizer_start(&ctx, name, trace_num, exit_num)) { + if (!zend_jit_deoptimizer_start(&ctx, name, trace_num, &zend_jit_traces[trace_num], exit_num)) { zend_string_release(name); return NULL; } diff --git a/ext/opcache/tests/jit/gh22915.phpt b/ext/opcache/tests/jit/gh22915.phpt new file mode 100644 index 000000000000..cea291d311b1 --- /dev/null +++ b/ext/opcache/tests/jit/gh22915.phpt @@ -0,0 +1,75 @@ +--TEST-- +GH-22915: compiled exit clobbers registers before saving +--EXTENSIONS-- +opcache +--INI-- +opcache.jit_max_side_traces=0 +opcache.jit_blacklist_side_trace=0 +--ENV-- +F=iter +--FILE-- +values = $values; + } + + public function rewind(): void {} + + public function valid(): bool { + return $this->position === 0; + } + + public function current(): mixed { + if (!isset($this->values[$this->position])) { + throw new Exception(); + } + + return $this->values[$this->position]; + } + + public function key(): mixed { + return $this->position; + } + + public function next(): void { + $this->position++; + } +} + +function iter(It $it) { + foreach ($it as $value) { + var_dump($value); + if (!$value instanceof stdClass) { + continue; + } + } +} + +echo "# First run\n"; +for ($i = 0; $i < 5; $i++) { + getenv('F')(new It([getenv('F')])); // non-immutable, packed array +} + +echo "# Second run\n"; +for ($i = 0; $i < 5; $i++) { + getenv('F')(new It([getenv('F'), 'map' => true])); // non-immutable, map, triggers exit +} + +?> +--EXPECT-- +# First run +string(4) "iter" +string(4) "iter" +string(4) "iter" +string(4) "iter" +string(4) "iter" +# Second run +string(4) "iter" +string(4) "iter" +string(4) "iter" +string(4) "iter" +string(4) "iter" From b2cf4608e7bd99b044d98eebbc6467049391a23a Mon Sep 17 00:00:00 2001 From: Louis-Arnaud Date: Fri, 7 Aug 2026 19:05:37 +0200 Subject: [PATCH 4/4] Enforce zero arity in apache_get_version() and apache_get_modules() (#23102) Both functions are declared with no parameters in the stub, but neither implementation called ZEND_PARSE_PARAMETERS_NONE(), so extra positional arguments were silently accepted instead of raising ArgumentCountError. Every other zero-arity function of the same SAPI (apache_request_headers(), apache_response_headers(), getallheaders()) already enforces its arity, and so does the litespeed implementation of apache_get_modules(). No stub or arginfo change is needed. Co-authored-by: lacatoire --- sapi/apache2handler/php_functions.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sapi/apache2handler/php_functions.c b/sapi/apache2handler/php_functions.c index 1baa1f5225e0..db2dcca72bda 100644 --- a/sapi/apache2handler/php_functions.c +++ b/sapi/apache2handler/php_functions.c @@ -308,6 +308,8 @@ static const char *php_apache_get_version(void) /* {{{ Fetch Apache version */ PHP_FUNCTION(apache_get_version) { + ZEND_PARSE_PARAMETERS_NONE(); + const char *apv = php_apache_get_version(); if (apv && *apv) { @@ -324,6 +326,8 @@ PHP_FUNCTION(apache_get_modules) int n; char *p; + ZEND_PARSE_PARAMETERS_NONE(); + array_init(return_value); for (n = 0; ap_loaded_modules[n]; ++n) {