Skip to content

Commit 978c01c

Browse files
committed
JIT: Check exception on exit
Add a new exit flag (ZEND_JIT_EXIT_CHECK_EXCEPTION) that enables exception checking during exit/deoptimization. We already checked for exceptions during exit/deoptimization, but only when ZEND_JIT_EXIT_FREE_OP1 or ZEND_JIT_EXIT_FREE_OP2 were set (presumably to handle exceptions thrown during dtor). The new flag makes it possible to request it explicitly. This also fixes two issues in zend_jit_trace_exit(): - By returning 1, we were telling the caller (zend_jit_trace_exit_stub()) to execute the original op handler of EG(current_execute_data)->opline, but in reality we want to execute EX(opline), which should be EG(exception_op). - EX(opline) is set to the value of %r15 in zend_jit_trace_exit_stub() before calling zend_jit_trace_exit(), but this may be the address of a zend_execute_data when the register is being reused to cache EX(call). Fixes GH-18262 Closes GH-18297
1 parent c620fee commit 978c01c

File tree

8 files changed

+170
-17
lines changed

8 files changed

+170
-17
lines changed

NEWS

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ PHP NEWS
1515
- Opcache:
1616
. Fixed bug GH-18417 (Windows SHM reattachment fails when increasing
1717
memory_consumption or jit_buffer_size). (nielsdos)
18+
. Fixed bug GH-18297 (Exception not handled when jit guard is triggered).
19+
(Arnaud)
1820

1921
- SPL:
2022
. Fixed bug GH-18421 (Integer overflow with large numbers in LimitIterator).

ext/opcache/jit/zend_jit_internal.h

+12-11
Original file line numberDiff line numberDiff line change
@@ -311,17 +311,18 @@ typedef enum _zend_jit_trace_stop {
311311

312312
#define ZEND_JIT_TRACE_SUPPORTED 0
313313

314-
#define ZEND_JIT_EXIT_JITED (1<<0)
315-
#define ZEND_JIT_EXIT_BLACKLISTED (1<<1)
316-
#define ZEND_JIT_EXIT_TO_VM (1<<2) /* exit to VM without attempt to create a side trace */
317-
#define ZEND_JIT_EXIT_RESTORE_CALL (1<<3) /* deoptimizer should restore EX(call) chain */
318-
#define ZEND_JIT_EXIT_POLYMORPHISM (1<<4) /* exit because of polymorphic call */
319-
#define ZEND_JIT_EXIT_FREE_OP1 (1<<5)
320-
#define ZEND_JIT_EXIT_FREE_OP2 (1<<6)
321-
#define ZEND_JIT_EXIT_PACKED_GUARD (1<<7)
322-
#define ZEND_JIT_EXIT_CLOSURE_CALL (1<<8) /* exit because of polymorphic INIT_DYNAMIC_CALL call */
323-
#define ZEND_JIT_EXIT_METHOD_CALL (1<<9) /* exit because of polymorphic INIT_METHOD_CALL call */
324-
#define ZEND_JIT_EXIT_INVALIDATE (1<<10) /* invalidate current trace */
314+
#define ZEND_JIT_EXIT_JITED (1<<0)
315+
#define ZEND_JIT_EXIT_BLACKLISTED (1<<1)
316+
#define ZEND_JIT_EXIT_TO_VM (1<<2) /* exit to VM without attempt to create a side trace */
317+
#define ZEND_JIT_EXIT_RESTORE_CALL (1<<3) /* deoptimizer should restore EX(call) chain */
318+
#define ZEND_JIT_EXIT_POLYMORPHISM (1<<4) /* exit because of polymorphic call */
319+
#define ZEND_JIT_EXIT_FREE_OP1 (1<<5)
320+
#define ZEND_JIT_EXIT_FREE_OP2 (1<<6)
321+
#define ZEND_JIT_EXIT_PACKED_GUARD (1<<7)
322+
#define ZEND_JIT_EXIT_CLOSURE_CALL (1<<8) /* exit because of polymorphic INIT_DYNAMIC_CALL call */
323+
#define ZEND_JIT_EXIT_METHOD_CALL (1<<9) /* exit because of polymorphic INIT_METHOD_CALL call */
324+
#define ZEND_JIT_EXIT_INVALIDATE (1<<10) /* invalidate current trace */
325+
#define ZEND_JIT_EXIT_CHECK_EXCEPTION (1<<11)
325326

326327
#define ZEND_JIT_EXIT_FIXED (1U<<31) /* the exit_info can't be changed by zend_jit_snapshot_handler() */
327328

ext/opcache/jit/zend_jit_ir.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -14483,12 +14483,12 @@ static int zend_jit_fetch_obj(zend_jit_ctx *jit,
1448314483
ZEND_ASSERT(end_inputs == IR_UNUSED);
1448414484
if ((res_info & MAY_BE_GUARD) && JIT_G(current_frame)) {
1448514485
uint8_t type = concrete_type(res_info);
14486-
uint32_t flags = 0;
14486+
uint32_t flags = ZEND_JIT_EXIT_CHECK_EXCEPTION;
1448714487

1448814488
if ((opline->op1_type & (IS_VAR|IS_TMP_VAR))
1448914489
&& !delayed_fetch_this
1449014490
&& !op1_avoid_refcounting) {
14491-
flags = ZEND_JIT_EXIT_FREE_OP1;
14491+
flags |= ZEND_JIT_EXIT_FREE_OP1;
1449214492
}
1449314493

1449414494
if ((opline->result_type & (IS_VAR|IS_TMP_VAR))

ext/opcache/jit/zend_jit_trace.c

+12-4
Original file line numberDiff line numberDiff line change
@@ -3474,7 +3474,7 @@ static int zend_jit_trace_exit_needs_deoptimization(uint32_t trace_num, uint32_t
34743474
uint32_t stack_size;
34753475
zend_jit_trace_stack *stack;
34763476

3477-
if (opline || (flags & (ZEND_JIT_EXIT_RESTORE_CALL|ZEND_JIT_EXIT_FREE_OP1|ZEND_JIT_EXIT_FREE_OP2))) {
3477+
if (opline || (flags & (ZEND_JIT_EXIT_RESTORE_CALL|ZEND_JIT_EXIT_FREE_OP1|ZEND_JIT_EXIT_FREE_OP2|ZEND_JIT_EXIT_CHECK_EXCEPTION))) {
34783478
return 1;
34793479
}
34803480

@@ -3634,7 +3634,7 @@ static int zend_jit_trace_deoptimization(
36343634
}
36353635
}
36363636

3637-
if (flags & (ZEND_JIT_EXIT_FREE_OP1|ZEND_JIT_EXIT_FREE_OP2)) {
3637+
if (flags & (ZEND_JIT_EXIT_FREE_OP1|ZEND_JIT_EXIT_FREE_OP2|ZEND_JIT_EXIT_CHECK_EXCEPTION)) {
36383638
zend_jit_check_exception(jit);
36393639
}
36403640

@@ -7943,6 +7943,9 @@ static void zend_jit_dump_exit_info(zend_jit_trace_info *t)
79437943
if (t->exit_info[i].flags & ZEND_JIT_EXIT_FREE_OP2) {
79447944
fprintf(stderr, "/FREE_OP2");
79457945
}
7946+
if (t->exit_info[i].flags & ZEND_JIT_EXIT_CHECK_EXCEPTION) {
7947+
fprintf(stderr, "/CHK_EXC");
7948+
}
79467949
for (j = 0; j < stack_size; j++) {
79477950
uint8_t type = STACK_TYPE(stack, j);
79487951
if (type != IS_UNKNOWN) {
@@ -8654,9 +8657,14 @@ int ZEND_FASTCALL zend_jit_trace_exit(uint32_t exit_num, zend_jit_registers_buf
86548657
EX(opline) = opline-1;
86558658
zval_ptr_dtor_nogc(EX_VAR((opline-1)->op1.var));
86568659
}
8657-
if (t->exit_info[exit_num].flags & (ZEND_JIT_EXIT_FREE_OP1|ZEND_JIT_EXIT_FREE_OP2)) {
8660+
if (t->exit_info[exit_num].flags & (ZEND_JIT_EXIT_FREE_OP1|ZEND_JIT_EXIT_FREE_OP2|ZEND_JIT_EXIT_CHECK_EXCEPTION)) {
86588661
if (EG(exception)) {
8659-
return 1;
8662+
/* EX(opline) was overridden in zend_jit_trace_exit_stub(),
8663+
* and may be wrong when IP is reused. */
8664+
if (GCC_GLOBAL_REGS) {
8665+
EX(opline) = EG(exception_op);
8666+
}
8667+
return 0;
86608668
}
86618669
}
86628670
if (t->exit_info[exit_num].flags & ZEND_JIT_EXIT_METHOD_CALL) {
+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
--TEST--
2+
GH-18262 001 (Assertion failure Zend/zend_vm_execute.h JIT)
3+
--CREDITS--
4+
YuanchengJiang
5+
--FILE--
6+
<?php
7+
#[AllowDynamicProperties]
8+
class B {
9+
public int $fusion;
10+
}
11+
class C extends B {
12+
}
13+
class D extends C {
14+
public function __destruct() {
15+
}
16+
}
17+
$tests = [
18+
[C::class, new C()],
19+
[C::class, new B()],
20+
[D::class, new B()],
21+
];
22+
foreach ($tests as [$class, $instance]) {
23+
$obj = (new ReflectionClass($class))->newLazyProxy(function ($obj) use ($instance) {
24+
$instance->b = 1;
25+
return $instance;
26+
});
27+
var_dump($obj->b);
28+
}
29+
?>
30+
--EXPECTF--
31+
int(1)
32+
int(1)
33+
34+
Fatal error: Uncaught TypeError: %s in %s:%d
35+
Stack trace:
36+
#0 {main}
37+
thrown in %s on line %d
+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
--TEST--
2+
GH-18262 002 (Assertion failure Zend/zend_vm_execute.h JIT)
3+
--FILE--
4+
<?php
5+
#[AllowDynamicProperties]
6+
class B {
7+
public function __construct($init) {
8+
if ($init) {
9+
$this->b = $init;
10+
}
11+
}
12+
}
13+
14+
$tests = [
15+
new B(1),
16+
new B(0),
17+
];
18+
19+
set_error_handler(function ($_, $errstr) {
20+
throw new \Exception($errstr);
21+
});
22+
23+
foreach ($tests as $obj) {
24+
var_dump($obj->b);
25+
}
26+
?>
27+
--EXPECTF--
28+
int(1)
29+
30+
Fatal error: Uncaught Exception: Undefined property: B::$b in %s:%d
31+
Stack trace:
32+
#0 %s(%d): {closure:%s:%d}(2, 'Undefined prope...', '%s', %d)
33+
#1 {main}
34+
thrown in %s on line %d
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
--TEST--
2+
GH-18262 003 (Assertion failure Zend/zend_vm_execute.h JIT)
3+
--FILE--
4+
<?php
5+
#[AllowDynamicProperties]
6+
class B {
7+
public function __construct($init) {
8+
if ($init) {
9+
$this->b = $init;
10+
}
11+
}
12+
}
13+
14+
$tests = [
15+
new B(1),
16+
new B('str'), // slow deoptimization, create linked side trace
17+
new B(0), // jump to side trace with fast deoptimization
18+
];
19+
20+
set_error_handler(function ($_, $errstr) {
21+
throw new \Exception($errstr);
22+
});
23+
24+
foreach ($tests as $obj) {
25+
try {
26+
var_dump($obj->b);
27+
} catch (Exception $e) {
28+
printf("%s: %s\n", $e::class, $e->getMessage());
29+
}
30+
}
31+
?>
32+
--EXPECT--
33+
int(1)
34+
string(3) "str"
35+
Exception: Undefined property: B::$b
+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
--TEST--
2+
GH-18262 004 (Assertion failure Zend/zend_vm_execute.h JIT)
3+
--FILE--
4+
<?php
5+
class B {
6+
public function __construct(
7+
public $throw,
8+
) { }
9+
public function __get($name) {
10+
return $this->throw === '1' ? 'str' : 1;
11+
}
12+
public function __destruct() {
13+
if ($this->throw === '1') {
14+
throw new Exception(__METHOD__);
15+
}
16+
}
17+
}
18+
19+
$tests = [
20+
'0',
21+
'1',
22+
];
23+
24+
foreach ($tests as $test) {
25+
// Second iteration exits, and free op1 throws
26+
var_dump((new B($test))->b);
27+
}
28+
?>
29+
--EXPECTF--
30+
int(1)
31+
32+
Fatal error: Uncaught Exception: B::__destruct in %s:%d
33+
Stack trace:
34+
#0 %s(%d): B->__destruct()
35+
#1 {main}
36+
thrown in %s on line %d

0 commit comments

Comments
 (0)