Commit 616e11d3d42 for php.net
commit 616e11d3d4202cc0184ce9207b136fd55c85741e
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date: Mon Jul 27 06:40:52 2026 -0400
Fix GH-18847: save EX(opline) before extending the VM stack on all VMs (#22542)
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
diff --git a/NEWS b/NEWS
index 91ac10efd3b..cc2eed87ccf 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 f5b70d74aa6..91cab1b63d2 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 0e5efa6f826..cd74145799b 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 6b675837d3a..8d2e0670ee3 100644
Binary files a/Zend/zend_vm_execute.h and b/Zend/zend_vm_execute.h differ
diff --git a/ext/opcache/tests/jit/gh18847.phpt b/ext/opcache/tests/jit/gh18847.phpt
new file mode 100644
index 00000000000..b6dd5a7d658
--- /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--
+<?php
+if (getenv("SKIP_SLOW_TESTS")) die("skip slow test");
+?>
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.jit=tracing
+opcache.jit_buffer_size=8M
+fatal_error_backtraces=1
+memory_limit=8M
+--FILE--
+<?php
+function f() {
+ static $x = f();
+}
+f();
+?>
+--EXPECTREGEX--
+Fatal error: Allowed memory size of \d+ bytes exhausted.*#\d+ \{main\}