From 4c71cf27b0485667b9190a07cb7b91747bde8503 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Fri, 10 Apr 2026 08:42:12 -0400 Subject: [PATCH] Fix GH-21368 crash: runtime lookup for orig_handler in escape_if_undef PR #21368 replaced the trace_escape stub dispatch in zend_jit_escape_if_undef with a compile-time constant load of orig_handler, computed from the exit info's op_array pointer. That pointer can be NULL (when current_frame is NULL at exit-point creation) or stale (when the underlying op_array is freed before the side trace compiles), producing an access violation inside zend_jit_escape_if_undef. Reported on PHP 8.5.5 Windows NTS. Drop the op_array parameter and emit a runtime lookup via zend_jit_orig_opline_handler() instead. That helper resolves the jit_extension through EX(func) at dispatch time, which is valid regardless of the compile-time op_array state. The gh21267 tests still pass, confirming the infinite-loop fix is preserved. --- ext/opcache/jit/zend_jit_ir.c | 13 +++++++------ ext/opcache/jit/zend_jit_trace.c | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 4251d6b891c94..09d3aa0dd3d92 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -8068,7 +8068,7 @@ static int zend_jit_defined(zend_jit_ctx *jit, const zend_op *opline, uint8_t sm return 1; } -static int zend_jit_escape_if_undef(zend_jit_ctx *jit, int var, uint32_t flags, const zend_op *opline, const zend_op_array *op_array, int8_t reg) +static int zend_jit_escape_if_undef(zend_jit_ctx *jit, int var, uint32_t flags, const zend_op *opline, int8_t reg) { zend_jit_addr reg_addr = ZEND_ADDR_REF_ZVAL(zend_jit_deopt_rload(jit, IR_ADDR, reg)); ir_ref if_def = ir_IF(jit_Z_TYPE(jit, reg_addr)); @@ -8092,11 +8092,12 @@ static int zend_jit_escape_if_undef(zend_jit_ctx *jit, int var, uint32_t flags, jit_LOAD_IP_ADDR(jit, opline - 1); - /* We can't use trace_escape() because opcode handler may be overridden by JIT */ - zend_jit_op_array_trace_extension *jit_extension = - (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(op_array); - size_t offset = jit_extension->offset; - ir_ref ref = ir_CONST_ADDR(ZEND_OP_TRACE_INFO((opline - 1), offset)->orig_handler); + /* We can't use trace_escape() because the current opline handler may + * have been overridden by JIT code. Dispatch to orig_handler via the + * runtime lookup (EX(func) -> jit_extension -> offset), which always + * resolves even when the exit_info's op_array reference is unavailable + * or stale. */ + ir_ref ref = zend_jit_orig_opline_handler(jit); if (GCC_GLOBAL_REGS) { ir_TAILCALL(IR_VOID, ref); } else { diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c index 320967419055f..ef4131abd6fc4 100644 --- a/ext/opcache/jit/zend_jit_trace.c +++ b/ext/opcache/jit/zend_jit_trace.c @@ -3603,7 +3603,7 @@ static int zend_jit_trace_deoptimization( ZEND_ASSERT(STACK_FLAGS(parent_stack, check2) == ZREG_ZVAL_COPY); ZEND_ASSERT(reg != ZREG_NONE); - if (!zend_jit_escape_if_undef(jit, check2, flags, opline, exit_info->op_array, reg)) { + if (!zend_jit_escape_if_undef(jit, check2, flags, opline, reg)) { return 0; } if (!zend_jit_restore_zval(jit, EX_NUM_TO_VAR(check2), reg)) {