Skip to content

Commit c5d3a9c

Browse files
committed
Check VM interrupt while internal frame is on top
1 parent e63f822 commit c5d3a9c

File tree

5 files changed

+146
-17
lines changed

5 files changed

+146
-17
lines changed

UPGRADING.INTERNALS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,11 @@ PHP 8.4 INTERNALS UPGRADE NOTES
333333
4. OpCode changes
334334
========================
335335

336+
* DO_ICALL, DO_FCALL, and DO_FCALL_BY_NAME now call zend_interrupt_function
337+
while the internal frame is still on the stack. This means interrupt handlers
338+
will now see the internal call. If your interrupt handler does something like
339+
switching EG(current_execute_data), it should not do so if an internal func
340+
is on top.
336341
* New FRAMELESS_ICALL_[0,3] opcodes for faster internal function calls have been
337342
added. These opcodes don't create a stack frame, but pass arguments via opcode
338343
operands. They only work for functions that are known at compile-time, and

Zend/tests/fibers/signal-async.phpt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pcntl_signal(SIGUSR1, function (): void {
1717
$fiber = new Fiber(function (): void {
1818
echo "Fiber start\n";
1919
posix_kill(posix_getpid(), SIGUSR1);
20-
time_nanosleep(1);
20+
time_nanosleep(1, 0);
2121
echo "Fiber end\n";
2222
});
2323

@@ -30,8 +30,9 @@ Fiber start
3030
Fatal error: Uncaught FiberError: Cannot switch fibers in current execution context in %ssignal-async.php:%d
3131
Stack trace:
3232
#0 %ssignal-async.php(%d): Fiber::suspend()
33-
#1 %ssignal-async.php(%d): {closure:%s:%d}(%d, Array)
34-
#2 [internal function]: {closure:%s:%d}()
35-
#3 %ssignal-async.php(%d): Fiber->start()
36-
#4 {main}
33+
#1 [internal function]: {closure:%s:%d}(%d, Array)
34+
#2 %ssignal-async.php(%d): posix_kill(%d, %d)
35+
#3 [internal function]: {closure:%s:%d}()
36+
#4 %ssignal-async.php(%d): Fiber->start()
37+
#5 {main}
3738
thrown in %ssignal-async.php on line %d

Zend/zend.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,22 @@ extern ZEND_API size_t (*zend_printf)(const char *format, ...) ZEND_ATTRIBUTE_PT
341341
extern ZEND_API zend_write_func_t zend_write;
342342
extern ZEND_API FILE *(*zend_fopen)(zend_string *filename, zend_string **opened_path);
343343
extern ZEND_API void (*zend_ticks_function)(int ticks);
344+
345+
/* Called by the VM in certain places like at the loop header, user function
346+
* entry, and after internal function calls, if EG(vm_interrupt) has been set.
347+
*
348+
* If this is used to switch the EG(current_execute_data), such as implementing
349+
* a coroutine scheduler, then it needs to check the top frame to see if it's
350+
* an internal function. If an internal function is on top, then the frame
351+
* shouldn't be switched away.
352+
*
353+
* Prior to PHP 8.0, this check was not necessary. In PHP 8.0,
354+
* zend_call_function started calling zend_interrupt_function, and in 8.4 the
355+
* DO_*CALL* opcodes started calling the zend_interrupt_function while the
356+
* internal frame is still on top.
357+
*/
344358
extern ZEND_API void (*zend_interrupt_function)(zend_execute_data *execute_data);
359+
345360
extern ZEND_API void (*zend_error_cb)(int type, zend_string *error_filename, const uint32_t error_lineno, zend_string *message);
346361
extern ZEND_API void (*zend_on_timeout)(int seconds);
347362
extern ZEND_API zend_result (*zend_stream_open_function)(zend_file_handle *handle);

Zend/zend_vm_def.h

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4075,6 +4075,14 @@ ZEND_VM_HOT_HANDLER(129, ZEND_DO_ICALL, ANY, ANY, SPEC(RETVAL,OBSERVER))
40754075
#endif
40764076
ZEND_OBSERVER_FCALL_END(call, EG(exception) ? NULL : ret);
40774077

4078+
if (UNEXPECTED(zend_atomic_bool_load_ex(&EG(vm_interrupt)))) {
4079+
if (zend_atomic_bool_load_ex(&EG(timed_out))) {
4080+
zend_timeout();
4081+
} else if (zend_interrupt_function) {
4082+
zend_interrupt_function(execute_data);
4083+
}
4084+
}
4085+
40784086
EG(current_execute_data) = execute_data;
40794087
zend_vm_stack_free_args(call);
40804088

@@ -4097,7 +4105,8 @@ ZEND_VM_HOT_HANDLER(129, ZEND_DO_ICALL, ANY, ANY, SPEC(RETVAL,OBSERVER))
40974105
HANDLE_EXCEPTION();
40984106
}
40994107

4100-
ZEND_VM_SET_OPCODE(opline + 1);
4108+
CHECK_SYMBOL_TABLES()
4109+
OPLINE = opline + 1;
41014110
ZEND_VM_CONTINUE();
41024111
}
41034112

@@ -4196,6 +4205,14 @@ ZEND_VM_HOT_HANDLER(131, ZEND_DO_FCALL_BY_NAME, ANY, ANY, SPEC(RETVAL,OBSERVER))
41964205
#endif
41974206
ZEND_OBSERVER_FCALL_END(call, EG(exception) ? NULL : ret);
41984207

4208+
if (UNEXPECTED(zend_atomic_bool_load_ex(&EG(vm_interrupt)))) {
4209+
if (zend_atomic_bool_load_ex(&EG(timed_out))) {
4210+
zend_timeout();
4211+
} else if (zend_interrupt_function) {
4212+
zend_interrupt_function(execute_data);
4213+
}
4214+
}
4215+
41994216
EG(current_execute_data) = execute_data;
42004217

42014218
ZEND_VM_C_GOTO(fcall_by_name_end);
@@ -4225,7 +4242,8 @@ ZEND_VM_C_LABEL(fcall_by_name_end):
42254242
zend_rethrow_exception(execute_data);
42264243
HANDLE_EXCEPTION();
42274244
}
4228-
ZEND_VM_SET_OPCODE(opline + 1);
4245+
CHECK_SYMBOL_TABLES()
4246+
OPLINE = opline + 1;
42294247
ZEND_VM_CONTINUE();
42304248
}
42314249

@@ -4316,6 +4334,14 @@ ZEND_VM_HOT_HANDLER(60, ZEND_DO_FCALL, ANY, ANY, SPEC(RETVAL,OBSERVER))
43164334
#endif
43174335
ZEND_OBSERVER_FCALL_END(call, EG(exception) ? NULL : ret);
43184336

4337+
if (UNEXPECTED(zend_atomic_bool_load_ex(&EG(vm_interrupt)))) {
4338+
if (zend_atomic_bool_load_ex(&EG(timed_out))) {
4339+
zend_timeout();
4340+
} else if (zend_interrupt_function) {
4341+
zend_interrupt_function(execute_data);
4342+
}
4343+
}
4344+
43194345
EG(current_execute_data) = execute_data;
43204346

43214347
ZEND_VM_C_GOTO(fcall_end);
@@ -4344,7 +4370,8 @@ ZEND_VM_C_LABEL(fcall_end):
43444370
HANDLE_EXCEPTION();
43454371
}
43464372

4347-
ZEND_VM_SET_OPCODE(opline + 1);
4373+
CHECK_SYMBOL_TABLES()
4374+
OPLINE = opline + 1;
43484375
ZEND_VM_CONTINUE();
43494376
}
43504377

Zend/zend_vm_execute.h

Lines changed: 90 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)