Skip to content

Commit 369c2ea

Browse files
committed
Fix nullsafe operator on delayed oplines
1 parent bfeb2f6 commit 369c2ea

File tree

8 files changed

+178
-11
lines changed

8 files changed

+178
-11
lines changed

Zend/tests/nullsafe_operator/034.phpt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
Test nullsafe operator on delayed dim
3+
--FILE--
4+
<?php
5+
6+
$arr = [
7+
'foo' => null,
8+
'bar' => [
9+
'baz' => null,
10+
],
11+
];
12+
13+
var_dump($arr['foo']?->something);
14+
var_dump($arr['invalid']?->something);
15+
16+
var_dump($arr['bar']['baz']?->something);
17+
var_dump($arr['bar']['invalid']?->something);
18+
19+
?>
20+
--EXPECTF--
21+
NULL
22+
23+
Warning: Undefined array key "invalid" in %s.php on line 11
24+
NULL
25+
NULL
26+
27+
Warning: Undefined array key "invalid" in %s.php on line 14
28+
NULL

Zend/tests/nullsafe_operator/035.phpt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
Test nullsafe operator on delayed var
3+
--FILE--
4+
<?php
5+
6+
class Foo {
7+
public ?Bar $bar;
8+
}
9+
10+
class Bar {
11+
public string $baz;
12+
}
13+
14+
$foo = new Foo();
15+
16+
$foo->bar = null;
17+
var_dump($foo->bar?->baz);
18+
19+
$bar = new Bar();
20+
$bar->baz = 'baz';
21+
$foo->bar = $bar;
22+
var_dump($foo->bar?->baz);
23+
24+
?>
25+
--EXPECT--
26+
NULL
27+
string(3) "baz"

Zend/tests/nullsafe_operator/036.phpt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
Test nullsafe method call on delayed var
3+
--FILE--
4+
<?php
5+
6+
class Foo {
7+
public ?Bar $bar;
8+
}
9+
10+
class Bar {
11+
public function baz() {
12+
return 'baz';
13+
}
14+
}
15+
16+
$foo = new Foo();
17+
18+
$foo->bar = null;
19+
var_dump($foo->bar?->baz());
20+
21+
$bar = new Bar();
22+
$foo->bar = $bar;
23+
var_dump($foo->bar?->baz());
24+
25+
?>
26+
--EXPECT--
27+
NULL
28+
string(3) "baz"

Zend/tests/nullsafe_operator/037.phpt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
--TEST--
2+
Test nullsafe operator in nested delayed dims
3+
--FILE--
4+
<?php
5+
6+
$foo = new stdClass();
7+
$foo->bar = 'bar';
8+
9+
$array = ['foo' => ['bar' => 'baz']];
10+
11+
var_dump($array['foo'][$foo?->bar]);
12+
13+
?>
14+
--EXPECT--
15+
string(3) "baz"

Zend/tests/nullsafe_operator/038.phpt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Test nullsafe operator in nested delayed dims 2
3+
--FILE--
4+
<?php
5+
6+
$foo = (object) ['bar' => 0];
7+
$array = [[null]];
8+
var_dump($array[0][$foo->bar]?->baz);
9+
10+
?>
11+
--EXPECT--
12+
NULL

Zend/zend.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1303,6 +1303,7 @@ static ZEND_COLD void zend_error_impl(
13031303
zend_class_entry *saved_class_entry;
13041304
zend_stack loop_var_stack;
13051305
zend_stack delayed_oplines_stack;
1306+
zend_stack delayed_oplines_offset_stack;
13061307
int type = orig_type & E_ALL;
13071308

13081309
/* Report about uncaught exception in case of fatal errors */
@@ -1381,6 +1382,7 @@ static ZEND_COLD void zend_error_impl(
13811382
CG(active_class_entry) = NULL;
13821383
SAVE_STACK(loop_var_stack);
13831384
SAVE_STACK(delayed_oplines_stack);
1385+
SAVE_STACK(delayed_oplines_offset_stack);
13841386
CG(in_compilation) = 0;
13851387
}
13861388

@@ -1400,6 +1402,7 @@ static ZEND_COLD void zend_error_impl(
14001402
CG(active_class_entry) = saved_class_entry;
14011403
RESTORE_STACK(loop_var_stack);
14021404
RESTORE_STACK(delayed_oplines_stack);
1405+
RESTORE_STACK(delayed_oplines_offset_stack);
14031406
CG(in_compilation) = 1;
14041407
}
14051408

Zend/zend_compile.c

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ typedef struct _zend_loop_var {
6060
uint32_t try_catch_offset;
6161
} zend_loop_var;
6262

63+
typedef struct {
64+
uint32_t opnum;
65+
bool delayed;
66+
uint32_t delayed_oplines_stack_level;
67+
} zend_jmp_null_opnum;
68+
6369
static inline uint32_t zend_alloc_cache_slots(unsigned count) {
6470
if (count == 0) {
6571
return (uint32_t) -1;
@@ -368,10 +374,12 @@ void zend_init_compiler_data_structures(void) /* {{{ */
368374
{
369375
zend_stack_init(&CG(loop_var_stack), sizeof(zend_loop_var));
370376
zend_stack_init(&CG(delayed_oplines_stack), sizeof(zend_op));
371-
zend_stack_init(&CG(short_circuiting_opnums), sizeof(uint32_t));
377+
zend_stack_init(&CG(delayed_oplines_offset_stack), sizeof(uint32_t));
378+
zend_stack_init(&CG(short_circuiting_opnums), sizeof(zend_jmp_null_opnum));
372379
CG(active_class_entry) = NULL;
373380
CG(in_compilation) = 0;
374381
CG(skip_shebang) = 0;
382+
CG(delayed_oplines_stack_level) = 0;
375383

376384
CG(encoding_declared) = 0;
377385
CG(memoized_exprs) = NULL;
@@ -422,6 +430,7 @@ void shutdown_compiler(void) /* {{{ */
422430
{
423431
zend_stack_destroy(&CG(loop_var_stack));
424432
zend_stack_destroy(&CG(delayed_oplines_stack));
433+
zend_stack_destroy(&CG(delayed_oplines_offset_stack));
425434
zend_stack_destroy(&CG(short_circuiting_opnums));
426435
zend_hash_destroy(&CG(filenames_table));
427436
zend_arena_destroy(CG(arena));
@@ -2208,21 +2217,45 @@ static inline zend_op *zend_delayed_emit_op(znode *result, zend_uchar opcode, zn
22082217

22092218
static inline uint32_t zend_delayed_compile_begin(void) /* {{{ */
22102219
{
2211-
return zend_stack_count(&CG(delayed_oplines_stack));
2220+
uint32_t offset = zend_stack_count(&CG(delayed_oplines_stack));
2221+
zend_stack_push(&CG(delayed_oplines_offset_stack), &offset);
2222+
++CG(delayed_oplines_stack_level);
2223+
return offset;
22122224
}
22132225
/* }}} */
22142226

22152227
static zend_op *zend_delayed_compile_end(uint32_t offset) /* {{{ */
22162228
{
22172229
zend_op *opline = NULL, *oplines = zend_stack_base(&CG(delayed_oplines_stack));
2218-
uint32_t i, count = zend_stack_count(&CG(delayed_oplines_stack));
2230+
uint32_t count, first_delayed_opnum = get_next_op_number();
22192231

2232+
count = zend_stack_count(&CG(delayed_oplines_stack));
22202233
ZEND_ASSERT(count >= offset);
2221-
for (i = offset; i < count; ++i) {
2234+
for (uint32_t i = offset; i < count; ++i) {
22222235
opline = get_next_op();
22232236
memcpy(opline, &oplines[i], sizeof(zend_op));
22242237
}
22252238
CG(delayed_oplines_stack).top = offset;
2239+
2240+
uint32_t delayed_oplines_stack_level = CG(delayed_oplines_stack_level);
2241+
int32_t i = zend_stack_count(&CG(short_circuiting_opnums)) - 1;
2242+
while (i >= 0) {
2243+
zend_jmp_null_opnum *opnum = &((zend_jmp_null_opnum *) zend_stack_base(&CG(short_circuiting_opnums)))[i];
2244+
2245+
if (opnum->delayed_oplines_stack_level != delayed_oplines_stack_level) {
2246+
break;
2247+
}
2248+
2249+
if (opnum->delayed) {
2250+
opnum->opnum += first_delayed_opnum;
2251+
opnum->delayed = 0;
2252+
}
2253+
2254+
--i;
2255+
}
2256+
2257+
--CG(delayed_oplines_offset_stack).top;
2258+
--CG(delayed_oplines_stack_level);
22262259
return opline;
22272260
}
22282261
/* }}} */
@@ -2293,8 +2326,15 @@ static void zend_short_circuiting_commit(uint32_t checkpoint, znode *result, zen
22932326
}
22942327

22952328
while (zend_stack_count(&CG(short_circuiting_opnums)) != checkpoint) {
2296-
uint32_t opnum = *(uint32_t *) zend_stack_top(&CG(short_circuiting_opnums));
2297-
zend_op *opline = &CG(active_op_array)->opcodes[opnum];
2329+
zend_jmp_null_opnum *opnum = zend_stack_top(&CG(short_circuiting_opnums));
2330+
zend_op *opline;
2331+
if (opnum->delayed) {
2332+
uint32_t offset = *((uint32_t *) zend_stack_top(&CG(delayed_oplines_offset_stack))) + opnum->opnum;
2333+
opline = &((zend_op *) zend_stack_base(&CG(delayed_oplines_stack)))[offset];
2334+
} else {
2335+
opline = &CG(active_op_array)->opcodes[opnum->opnum];
2336+
}
2337+
ZEND_ASSERT(opline->opcode == ZEND_JMP_NULL);
22982338
opline->op2.opline_num = get_next_op_number();
22992339
SET_NODE(opline->result, result);
23002340
opline->extended_value =
@@ -2305,10 +2345,22 @@ static void zend_short_circuiting_commit(uint32_t checkpoint, znode *result, zen
23052345
}
23062346
}
23072347

2308-
static void zend_emit_jmp_null(znode *obj_node)
2348+
static void zend_emit_jmp_null(znode *obj_node, bool delay)
23092349
{
2310-
uint32_t jmp_null_opnum = get_next_op_number();
2311-
zend_op *opline = zend_emit_op(NULL, ZEND_JMP_NULL, obj_node, NULL);
2350+
zend_jmp_null_opnum jmp_null_opnum;
2351+
jmp_null_opnum.delayed = delay;
2352+
jmp_null_opnum.delayed_oplines_stack_level = CG(delayed_oplines_stack_level);
2353+
zend_op *opline;
2354+
2355+
if (delay) {
2356+
uint32_t delayed_oplines_count = zend_stack_count(&CG(delayed_oplines_stack));
2357+
jmp_null_opnum.opnum = delayed_oplines_count - *((uint32_t *) zend_stack_top(&CG(delayed_oplines_offset_stack)));
2358+
opline = zend_delayed_emit_op(NULL, ZEND_JMP_NULL, obj_node, NULL);
2359+
} else {
2360+
jmp_null_opnum.opnum = get_next_op_number();
2361+
opline = zend_emit_op(NULL, ZEND_JMP_NULL, obj_node, NULL);
2362+
}
2363+
23122364
if (opline->op1_type == IS_CONST) {
23132365
Z_TRY_ADDREF_P(CT_CONSTANT(opline->op1));
23142366
}
@@ -2796,7 +2848,7 @@ static zend_op *zend_delayed_compile_prop(znode *result, zend_ast *ast, uint32_t
27962848
opline = zend_delayed_compile_var(&obj_node, obj_ast, type, 0);
27972849
zend_separate_if_call_and_write(&obj_node, obj_ast, type);
27982850
if (nullsafe) {
2799-
zend_emit_jmp_null(&obj_node);
2851+
zend_emit_jmp_null(&obj_node, 1);
28002852
}
28012853
}
28022854

@@ -4356,7 +4408,7 @@ void zend_compile_method_call(znode *result, zend_ast *ast, uint32_t type) /* {{
43564408
zend_short_circuiting_mark_inner(obj_ast);
43574409
zend_compile_expr(&obj_node, obj_ast);
43584410
if (nullsafe) {
4359-
zend_emit_jmp_null(&obj_node);
4411+
zend_emit_jmp_null(&obj_node, 0);
43604412
}
43614413
}
43624414

Zend/zend_globals.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ struct _zend_compiler_globals {
119119
zend_arena *ast_arena;
120120

121121
zend_stack delayed_oplines_stack;
122+
zend_stack delayed_oplines_offset_stack;
123+
uint32_t delayed_oplines_stack_level;
122124
HashTable *memoized_exprs;
123125
int memoize_mode;
124126

0 commit comments

Comments
 (0)