From 7de8923f77c8bd8a515a79d6dd69b99b6dee5f4c Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Fri, 24 Jul 2026 14:15:43 -0400 Subject: [PATCH] Fix GH-18847: save EX(opline) before extending the VM stack on all VMs db7193f31ea9 sets EX(opline) before zend_vm_stack_extend() so that a fatal error raised while a call frame is pushed can walk the backtrace, but the wrappers doing it were compiled only for global-register CALL/HYBRID builds. Elsewhere the save is skipped, and a frame entered by the tracing JIT still holds a NULL opline when its first INIT_FCALL hits the memory limit. Thread execute_data and opline into the wrappers so that every VM kind performs the save. Fixes GH-18847 Closes GH-22542 --- NEWS | 3 +++ Zend/zend_execute.c | 11 +++-------- Zend/zend_vm_def.h | 8 ++++---- Zend/zend_vm_execute.h | 16 ++++++++-------- ext/opcache/tests/jit/gh18847.phpt | 24 ++++++++++++++++++++++++ 5 files changed, 42 insertions(+), 20 deletions(-) create mode 100644 ext/opcache/tests/jit/gh18847.phpt diff --git a/NEWS b/NEWS index 484e462698d9..059596655b1c 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,9 @@ PHP NEWS failure path). (David Carlier) . Fixed bug GH-18985 (Wrong line numbers for match with constant arms). (ilutov) + . Fixed bug GH-18847 (SEGV in zend_fetch_debug_backtrace() when the memory + limit is reached while the tracing JIT enters a call frame). (Arnaud, + iliaal) - DOM: . Fixed bug GH-22825 (DOMElement::setAttribute() fails silently when the DTD diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index f5b70d74aa61..91cab1b63d2a 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -5729,9 +5729,8 @@ ZEND_API void ZEND_FASTCALL zend_free_extra_named_params(zend_array *extra_named zend_array_release(extra_named_params); } -#if defined(ZEND_VM_IP_GLOBAL_REG) && ((ZEND_VM_KIND == ZEND_VM_KIND_CALL) || (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID)) /* Special versions of functions that sets EX(opline) before calling zend_vm_stack_extend() */ -static zend_always_inline zend_execute_data *_zend_vm_stack_push_call_frame_ex(uint32_t used_stack, uint32_t call_info, zend_function *func, uint32_t num_args, void *object_or_called_scope) /* {{{ */ +static zend_always_inline zend_execute_data *_zend_vm_stack_push_call_frame_ex(uint32_t used_stack, uint32_t call_info, zend_function *func, uint32_t num_args, void *object_or_called_scope EXECUTE_DATA_DC OPLINE_DC) /* {{{ */ { zend_execute_data *call = (zend_execute_data*)EG(vm_stack_top); @@ -5750,17 +5749,13 @@ static zend_always_inline zend_execute_data *_zend_vm_stack_push_call_frame_ex(u } } /* }}} */ -static zend_always_inline zend_execute_data *_zend_vm_stack_push_call_frame(uint32_t call_info, zend_function *func, uint32_t num_args, void *object_or_called_scope) /* {{{ */ +static zend_always_inline zend_execute_data *_zend_vm_stack_push_call_frame(uint32_t call_info, zend_function *func, uint32_t num_args, void *object_or_called_scope EXECUTE_DATA_DC OPLINE_DC) /* {{{ */ { uint32_t used_stack = zend_vm_calc_used_stack(num_args, func); return _zend_vm_stack_push_call_frame_ex(used_stack, call_info, - func, num_args, object_or_called_scope); + func, num_args, object_or_called_scope EXECUTE_DATA_CC OPLINE_CC); } /* }}} */ -#else -# define _zend_vm_stack_push_call_frame_ex zend_vm_stack_push_call_frame_ex -# define _zend_vm_stack_push_call_frame zend_vm_stack_push_call_frame -#endif #ifdef ZEND_VM_TRACE_HANDLERS # include "zend_vm_trace_handlers.h" diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 0e5efa6f826c..cd74145799b3 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -3912,7 +3912,7 @@ ZEND_VM_HOT_HANDLER(59, ZEND_INIT_FCALL_BY_NAME, ANY, CONST, NUM|CACHE_SLOT) CACHE_PTR(opline->result.num, fbc); } call = _zend_vm_stack_push_call_frame(ZEND_CALL_NESTED_FUNCTION, - fbc, opline->extended_value, NULL); + fbc, opline->extended_value, NULL EXECUTE_DATA_CC OPLINE_CC); call->prev_execute_data = EX(call); EX(call) = call; @@ -4070,7 +4070,7 @@ ZEND_VM_HOT_HANDLER(69, ZEND_INIT_NS_FCALL_BY_NAME, ANY, CONST, NUM|CACHE_SLOT) } call = _zend_vm_stack_push_call_frame(ZEND_CALL_NESTED_FUNCTION, - fbc, opline->extended_value, NULL); + fbc, opline->extended_value, NULL EXECUTE_DATA_CC OPLINE_CC); call->prev_execute_data = EX(call); EX(call) = call; @@ -4099,7 +4099,7 @@ ZEND_VM_HOT_HANDLER(61, ZEND_INIT_FCALL, NUM, CONST, NUM|CACHE_SLOT) call = _zend_vm_stack_push_call_frame_ex( opline->op1.num, ZEND_CALL_NESTED_FUNCTION, - fbc, opline->extended_value, NULL); + fbc, opline->extended_value, NULL EXECUTE_DATA_CC OPLINE_CC); call->prev_execute_data = EX(call); EX(call) = call; @@ -4118,7 +4118,7 @@ ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_INIT_FCALL, Z_EXTRA_P(RT_CONSTANT(op, op->op2 } call = _zend_vm_stack_push_call_frame_ex( opline->op1.num, ZEND_CALL_NESTED_FUNCTION, - fbc, opline->extended_value, NULL); + fbc, opline->extended_value, NULL EXECUTE_DATA_CC OPLINE_CC); call->prev_execute_data = EX(call); EX(call) = call; ZEND_VM_NEXT_OPCODE(); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 6b675837d3a7..8d2e0670ee3b 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -4082,7 +4082,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_I CACHE_PTR(opline->result.num, fbc); } call = _zend_vm_stack_push_call_frame(ZEND_CALL_NESTED_FUNCTION, - fbc, opline->extended_value, NULL); + fbc, opline->extended_value, NULL EXECUTE_DATA_CC OPLINE_CC); call->prev_execute_data = EX(call); EX(call) = call; @@ -4169,7 +4169,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_I } call = _zend_vm_stack_push_call_frame(ZEND_CALL_NESTED_FUNCTION, - fbc, opline->extended_value, NULL); + fbc, opline->extended_value, NULL EXECUTE_DATA_CC OPLINE_CC); call->prev_execute_data = EX(call); EX(call) = call; @@ -4198,7 +4198,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_I call = _zend_vm_stack_push_call_frame_ex( opline->op1.num, ZEND_CALL_NESTED_FUNCTION, - fbc, opline->extended_value, NULL); + fbc, opline->extended_value, NULL EXECUTE_DATA_CC OPLINE_CC); call->prev_execute_data = EX(call); EX(call) = call; @@ -4217,7 +4217,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_I } call = _zend_vm_stack_push_call_frame_ex( opline->op1.num, ZEND_CALL_NESTED_FUNCTION, - fbc, opline->extended_value, NULL); + fbc, opline->extended_value, NULL EXECUTE_DATA_CC OPLINE_CC); call->prev_execute_data = EX(call); EX(call) = call; ZEND_VM_NEXT_OPCODE(); @@ -56898,7 +56898,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_F CACHE_PTR(opline->result.num, fbc); } call = _zend_vm_stack_push_call_frame(ZEND_CALL_NESTED_FUNCTION, - fbc, opline->extended_value, NULL); + fbc, opline->extended_value, NULL EXECUTE_DATA_CC OPLINE_CC); call->prev_execute_data = EX(call); EX(call) = call; @@ -56985,7 +56985,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_N } call = _zend_vm_stack_push_call_frame(ZEND_CALL_NESTED_FUNCTION, - fbc, opline->extended_value, NULL); + fbc, opline->extended_value, NULL EXECUTE_DATA_CC OPLINE_CC); call->prev_execute_data = EX(call); EX(call) = call; @@ -57014,7 +57014,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_F call = _zend_vm_stack_push_call_frame_ex( opline->op1.num, ZEND_CALL_NESTED_FUNCTION, - fbc, opline->extended_value, NULL); + fbc, opline->extended_value, NULL EXECUTE_DATA_CC OPLINE_CC); call->prev_execute_data = EX(call); EX(call) = call; @@ -57033,7 +57033,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_F } call = _zend_vm_stack_push_call_frame_ex( opline->op1.num, ZEND_CALL_NESTED_FUNCTION, - fbc, opline->extended_value, NULL); + fbc, opline->extended_value, NULL EXECUTE_DATA_CC OPLINE_CC); call->prev_execute_data = EX(call); EX(call) = call; ZEND_VM_NEXT_OPCODE(); diff --git a/ext/opcache/tests/jit/gh18847.phpt b/ext/opcache/tests/jit/gh18847.phpt new file mode 100644 index 000000000000..b6dd5a7d6587 --- /dev/null +++ b/ext/opcache/tests/jit/gh18847.phpt @@ -0,0 +1,24 @@ +--TEST-- +GH-18847 (SEGV in zend_fetch_debug_backtrace when the tracing JIT enters a frame and the memory limit is hit before opline is set) +--EXTENSIONS-- +opcache +--SKIPIF-- + +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.jit=tracing +opcache.jit_buffer_size=8M +fatal_error_backtraces=1 +memory_limit=8M +--FILE-- + +--EXPECTREGEX-- +Fatal error: Allowed memory size of \d+ bytes exhausted.*#\d+ \{main\}