diff --git a/NEWS b/NEWS index 1d13c108d845..e9bdc328903b 100644 --- a/NEWS +++ b/NEWS @@ -145,6 +145,10 @@ PHP NEWS . Fixed JIT segfault on switch over a string reference holding a non-string value. (jorgsowa) . Fixed AArch64 JIT encoding of invalid logical immediates. (jorgsowa) + . Fixed JIT crashes when handling a VM interrupt with a stale saved opline + in the tailcall VM. (Levi Morrison) + . Fixed function JIT misinterpreting VM-enter results after an interrupt + callback in the CALL and TAILCALL VMs. (Levi Morrison) - PDO: . Fixed invalid ATTR_ORACLE_NULLS values being accepted and truncated. diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index df733f525d50..2ffa11f9ec3f 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -445,7 +445,7 @@ static zend_vm_opcode_handler_func_t zend_vm_get_opcode_handler_func(uint8_t opc # define ZEND_VM_ENTER() execute_data = EG(current_execute_data); LOAD_OPLINE(); ZEND_VM_ENTER_EX() # define ZEND_VM_LEAVE() return (zend_op*)((uintptr_t)opline | ZEND_VM_ENTER_BIT) #endif -#define ZEND_VM_INTERRUPT() ZEND_VM_TAIL_CALL(zend_interrupt_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)); +#define ZEND_VM_INTERRUPT() ZEND_VM_KIND_TAILCALL_SAVE_OPLINE(); ZEND_VM_TAIL_CALL(zend_interrupt_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)); #ifdef ZEND_VM_FP_GLOBAL_REG #define ZEND_VM_LOOP_INTERRUPT() zend_interrupt_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); #else diff --git a/Zend/zend_vm_gen.php b/Zend/zend_vm_gen.php index 0b9c47a330ef..0101dc8ad628 100755 --- a/Zend/zend_vm_gen.php +++ b/Zend/zend_vm_gen.php @@ -2045,7 +2045,7 @@ function gen_executor($f, $skl, $spec, $kind, $executor_name, $initializer_name) out($f,"# define ZEND_VM_ENTER() execute_data = EG(current_execute_data); LOAD_OPLINE(); ZEND_VM_ENTER_EX()\n"); out($f,"# define ZEND_VM_LEAVE() return (zend_op*)((uintptr_t)opline | ZEND_VM_ENTER_BIT)\n"); out($f,"#endif\n"); - out($f,"#define ZEND_VM_INTERRUPT() ZEND_VM_TAIL_CALL(zend_interrupt_helper".($spec?"_SPEC":"")."(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU));\n"); + out($f,"#define ZEND_VM_INTERRUPT() ZEND_VM_KIND_TAILCALL_SAVE_OPLINE(); ZEND_VM_TAIL_CALL(zend_interrupt_helper".($spec?"_SPEC":"")."(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU));\n"); out($f,"#ifdef ZEND_VM_FP_GLOBAL_REG\n"); out($f,"#define ZEND_VM_LOOP_INTERRUPT() zend_interrupt_helper".($spec?"_SPEC":"")."(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);\n"); out($f,"#else\n"); diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 7400ea70a71f..5c5353775a2f 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -4165,10 +4165,16 @@ static int zend_jit_handler(zend_jit_ctx *jit, const zend_op *opline, int may_th } else if (ZEND_VM_KIND == ZEND_VM_KIND_TAILCALL) { zend_vm_opcode_handler_func_t handler = (zend_vm_opcode_handler_func_t)zend_get_opcode_handler_func(opline); ir_ref ip = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(handler), jit_FP(jit), jit_IP(jit)); + if (zend_interrupt_function) { + ip = ir_AND_A(ip, ir_CONST_ADDR(~ZEND_VM_ENTER_BIT)); + } jit_STORE_IP(jit, ip); } else { zend_vm_opcode_handler_t handler = opline->handler; ir_ref ip = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(handler), jit_FP(jit), jit_IP(jit)); + if (zend_interrupt_function) { + ip = ir_AND_A(ip, ir_CONST_ADDR(~ZEND_VM_ENTER_BIT)); + } jit_STORE_IP(jit, ip); } if (may_throw) { diff --git a/ext/opcache/tests/jit/function_vm_interrupt_enter_001.phpt b/ext/opcache/tests/jit/function_vm_interrupt_enter_001.phpt new file mode 100644 index 000000000000..2651180fde8f --- /dev/null +++ b/ext/opcache/tests/jit/function_vm_interrupt_enter_001.phpt @@ -0,0 +1,25 @@ +--TEST-- +Function JIT: decode the VM-enter bit after an interrupt callback +--EXTENSIONS-- +opcache +zend_test +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_update_protection=0 +opcache.jit=1201 +opcache.jit_buffer_size=64M +zend_test.observer.set_vm_interrupt_on_begin=1 +--FILE-- + +--EXPECT-- +int(22) diff --git a/ext/opcache/tests/jit/tailcall_vm_interrupt_opline_001.phpt b/ext/opcache/tests/jit/tailcall_vm_interrupt_opline_001.phpt new file mode 100644 index 000000000000..ed9bef7e10eb --- /dev/null +++ b/ext/opcache/tests/jit/tailcall_vm_interrupt_opline_001.phpt @@ -0,0 +1,39 @@ +--TEST-- +Tracing JIT: preserve the next opline when handling a VM interrupt +--DESCRIPTION-- +The object comparison raises an interrupt; the observer INI setting installs +an interrupt callback without enabling observer events. While recording the +caller, the backward jump must save its target before the interrupt helper +reloads the opline. Otherwise it revisits the already executed call opcode, +whose pending call frame has been consumed. + +At the time of writing this was failing on the TAILCALL VM, but this should +pass on all VMs. +--EXTENSIONS-- +opcache +zend_test +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_update_protection=0 +opcache.jit=tracing +opcache.jit_buffer_size=64M +opcache.jit_hot_func=1 +opcache.jit_hot_loop=1 +zend_test.observer.set_vm_interrupt_on_begin=1 +--FILE-- + +--EXPECT-- +Done diff --git a/ext/opcache/tests/jit/tailcall_vm_interrupt_opline_002.phpt b/ext/opcache/tests/jit/tailcall_vm_interrupt_opline_002.phpt new file mode 100644 index 000000000000..0e13b0ca3bbd --- /dev/null +++ b/ext/opcache/tests/jit/tailcall_vm_interrupt_opline_002.phpt @@ -0,0 +1,29 @@ +--TEST-- +Function JIT: preserve the next opline when handling a VM interrupt +--DESCRIPTION-- +The object comparison raises an interrupt while the minimal function JIT +executes a callable opcode handler. The interrupt helper must resume at the +branch target, not reload the saved comparison opcode. No interrupt callback +is installed, isolating the saved-opline bug from callback resume-tag handling. +--EXTENSIONS-- +opcache +zend_test +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_update_protection=0 +opcache.jit=1201 +opcache.jit_buffer_size=64M +--FILE-- + +--EXPECT-- +int(22)