From 369c2ea84faddb5bcc06371baebef2543bf2a09d Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Sat, 15 Aug 2020 17:43:34 +0200 Subject: [PATCH] Fix nullsafe operator on delayed oplines --- Zend/tests/nullsafe_operator/034.phpt | 28 ++++++++++ Zend/tests/nullsafe_operator/035.phpt | 27 ++++++++++ Zend/tests/nullsafe_operator/036.phpt | 28 ++++++++++ Zend/tests/nullsafe_operator/037.phpt | 15 ++++++ Zend/tests/nullsafe_operator/038.phpt | 12 +++++ Zend/zend.c | 3 ++ Zend/zend_compile.c | 74 +++++++++++++++++++++++---- Zend/zend_globals.h | 2 + 8 files changed, 178 insertions(+), 11 deletions(-) create mode 100644 Zend/tests/nullsafe_operator/034.phpt create mode 100644 Zend/tests/nullsafe_operator/035.phpt create mode 100644 Zend/tests/nullsafe_operator/036.phpt create mode 100644 Zend/tests/nullsafe_operator/037.phpt create mode 100644 Zend/tests/nullsafe_operator/038.phpt diff --git a/Zend/tests/nullsafe_operator/034.phpt b/Zend/tests/nullsafe_operator/034.phpt new file mode 100644 index 0000000000000..a82cec6b221b5 --- /dev/null +++ b/Zend/tests/nullsafe_operator/034.phpt @@ -0,0 +1,28 @@ +--TEST-- +Test nullsafe operator on delayed dim +--FILE-- + null, + 'bar' => [ + 'baz' => null, + ], +]; + +var_dump($arr['foo']?->something); +var_dump($arr['invalid']?->something); + +var_dump($arr['bar']['baz']?->something); +var_dump($arr['bar']['invalid']?->something); + +?> +--EXPECTF-- +NULL + +Warning: Undefined array key "invalid" in %s.php on line 11 +NULL +NULL + +Warning: Undefined array key "invalid" in %s.php on line 14 +NULL diff --git a/Zend/tests/nullsafe_operator/035.phpt b/Zend/tests/nullsafe_operator/035.phpt new file mode 100644 index 0000000000000..684c8c5d5f9fa --- /dev/null +++ b/Zend/tests/nullsafe_operator/035.phpt @@ -0,0 +1,27 @@ +--TEST-- +Test nullsafe operator on delayed var +--FILE-- +bar = null; +var_dump($foo->bar?->baz); + +$bar = new Bar(); +$bar->baz = 'baz'; +$foo->bar = $bar; +var_dump($foo->bar?->baz); + +?> +--EXPECT-- +NULL +string(3) "baz" diff --git a/Zend/tests/nullsafe_operator/036.phpt b/Zend/tests/nullsafe_operator/036.phpt new file mode 100644 index 0000000000000..1874f733aa857 --- /dev/null +++ b/Zend/tests/nullsafe_operator/036.phpt @@ -0,0 +1,28 @@ +--TEST-- +Test nullsafe method call on delayed var +--FILE-- +bar = null; +var_dump($foo->bar?->baz()); + +$bar = new Bar(); +$foo->bar = $bar; +var_dump($foo->bar?->baz()); + +?> +--EXPECT-- +NULL +string(3) "baz" diff --git a/Zend/tests/nullsafe_operator/037.phpt b/Zend/tests/nullsafe_operator/037.phpt new file mode 100644 index 0000000000000..5ce49ae0298c1 --- /dev/null +++ b/Zend/tests/nullsafe_operator/037.phpt @@ -0,0 +1,15 @@ +--TEST-- +Test nullsafe operator in nested delayed dims +--FILE-- +bar = 'bar'; + +$array = ['foo' => ['bar' => 'baz']]; + +var_dump($array['foo'][$foo?->bar]); + +?> +--EXPECT-- +string(3) "baz" diff --git a/Zend/tests/nullsafe_operator/038.phpt b/Zend/tests/nullsafe_operator/038.phpt new file mode 100644 index 0000000000000..422de0b891eec --- /dev/null +++ b/Zend/tests/nullsafe_operator/038.phpt @@ -0,0 +1,12 @@ +--TEST-- +Test nullsafe operator in nested delayed dims 2 +--FILE-- + 0]; +$array = [[null]]; +var_dump($array[0][$foo->bar]?->baz); + +?> +--EXPECT-- +NULL diff --git a/Zend/zend.c b/Zend/zend.c index 2aa37b0536acc..0d247df117688 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -1303,6 +1303,7 @@ static ZEND_COLD void zend_error_impl( zend_class_entry *saved_class_entry; zend_stack loop_var_stack; zend_stack delayed_oplines_stack; + zend_stack delayed_oplines_offset_stack; int type = orig_type & E_ALL; /* Report about uncaught exception in case of fatal errors */ @@ -1381,6 +1382,7 @@ static ZEND_COLD void zend_error_impl( CG(active_class_entry) = NULL; SAVE_STACK(loop_var_stack); SAVE_STACK(delayed_oplines_stack); + SAVE_STACK(delayed_oplines_offset_stack); CG(in_compilation) = 0; } @@ -1400,6 +1402,7 @@ static ZEND_COLD void zend_error_impl( CG(active_class_entry) = saved_class_entry; RESTORE_STACK(loop_var_stack); RESTORE_STACK(delayed_oplines_stack); + RESTORE_STACK(delayed_oplines_offset_stack); CG(in_compilation) = 1; } diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 9c4853ae4eb11..f9079fae09315 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -60,6 +60,12 @@ typedef struct _zend_loop_var { uint32_t try_catch_offset; } zend_loop_var; +typedef struct { + uint32_t opnum; + bool delayed; + uint32_t delayed_oplines_stack_level; +} zend_jmp_null_opnum; + static inline uint32_t zend_alloc_cache_slots(unsigned count) { if (count == 0) { return (uint32_t) -1; @@ -368,10 +374,12 @@ void zend_init_compiler_data_structures(void) /* {{{ */ { zend_stack_init(&CG(loop_var_stack), sizeof(zend_loop_var)); zend_stack_init(&CG(delayed_oplines_stack), sizeof(zend_op)); - zend_stack_init(&CG(short_circuiting_opnums), sizeof(uint32_t)); + zend_stack_init(&CG(delayed_oplines_offset_stack), sizeof(uint32_t)); + zend_stack_init(&CG(short_circuiting_opnums), sizeof(zend_jmp_null_opnum)); CG(active_class_entry) = NULL; CG(in_compilation) = 0; CG(skip_shebang) = 0; + CG(delayed_oplines_stack_level) = 0; CG(encoding_declared) = 0; CG(memoized_exprs) = NULL; @@ -422,6 +430,7 @@ void shutdown_compiler(void) /* {{{ */ { zend_stack_destroy(&CG(loop_var_stack)); zend_stack_destroy(&CG(delayed_oplines_stack)); + zend_stack_destroy(&CG(delayed_oplines_offset_stack)); zend_stack_destroy(&CG(short_circuiting_opnums)); zend_hash_destroy(&CG(filenames_table)); zend_arena_destroy(CG(arena)); @@ -2208,21 +2217,45 @@ static inline zend_op *zend_delayed_emit_op(znode *result, zend_uchar opcode, zn static inline uint32_t zend_delayed_compile_begin(void) /* {{{ */ { - return zend_stack_count(&CG(delayed_oplines_stack)); + uint32_t offset = zend_stack_count(&CG(delayed_oplines_stack)); + zend_stack_push(&CG(delayed_oplines_offset_stack), &offset); + ++CG(delayed_oplines_stack_level); + return offset; } /* }}} */ static zend_op *zend_delayed_compile_end(uint32_t offset) /* {{{ */ { zend_op *opline = NULL, *oplines = zend_stack_base(&CG(delayed_oplines_stack)); - uint32_t i, count = zend_stack_count(&CG(delayed_oplines_stack)); + uint32_t count, first_delayed_opnum = get_next_op_number(); + count = zend_stack_count(&CG(delayed_oplines_stack)); ZEND_ASSERT(count >= offset); - for (i = offset; i < count; ++i) { + for (uint32_t i = offset; i < count; ++i) { opline = get_next_op(); memcpy(opline, &oplines[i], sizeof(zend_op)); } CG(delayed_oplines_stack).top = offset; + + uint32_t delayed_oplines_stack_level = CG(delayed_oplines_stack_level); + int32_t i = zend_stack_count(&CG(short_circuiting_opnums)) - 1; + while (i >= 0) { + zend_jmp_null_opnum *opnum = &((zend_jmp_null_opnum *) zend_stack_base(&CG(short_circuiting_opnums)))[i]; + + if (opnum->delayed_oplines_stack_level != delayed_oplines_stack_level) { + break; + } + + if (opnum->delayed) { + opnum->opnum += first_delayed_opnum; + opnum->delayed = 0; + } + + --i; + } + + --CG(delayed_oplines_offset_stack).top; + --CG(delayed_oplines_stack_level); return opline; } /* }}} */ @@ -2293,8 +2326,15 @@ static void zend_short_circuiting_commit(uint32_t checkpoint, znode *result, zen } while (zend_stack_count(&CG(short_circuiting_opnums)) != checkpoint) { - uint32_t opnum = *(uint32_t *) zend_stack_top(&CG(short_circuiting_opnums)); - zend_op *opline = &CG(active_op_array)->opcodes[opnum]; + zend_jmp_null_opnum *opnum = zend_stack_top(&CG(short_circuiting_opnums)); + zend_op *opline; + if (opnum->delayed) { + uint32_t offset = *((uint32_t *) zend_stack_top(&CG(delayed_oplines_offset_stack))) + opnum->opnum; + opline = &((zend_op *) zend_stack_base(&CG(delayed_oplines_stack)))[offset]; + } else { + opline = &CG(active_op_array)->opcodes[opnum->opnum]; + } + ZEND_ASSERT(opline->opcode == ZEND_JMP_NULL); opline->op2.opline_num = get_next_op_number(); SET_NODE(opline->result, result); opline->extended_value = @@ -2305,10 +2345,22 @@ static void zend_short_circuiting_commit(uint32_t checkpoint, znode *result, zen } } -static void zend_emit_jmp_null(znode *obj_node) +static void zend_emit_jmp_null(znode *obj_node, bool delay) { - uint32_t jmp_null_opnum = get_next_op_number(); - zend_op *opline = zend_emit_op(NULL, ZEND_JMP_NULL, obj_node, NULL); + zend_jmp_null_opnum jmp_null_opnum; + jmp_null_opnum.delayed = delay; + jmp_null_opnum.delayed_oplines_stack_level = CG(delayed_oplines_stack_level); + zend_op *opline; + + if (delay) { + uint32_t delayed_oplines_count = zend_stack_count(&CG(delayed_oplines_stack)); + jmp_null_opnum.opnum = delayed_oplines_count - *((uint32_t *) zend_stack_top(&CG(delayed_oplines_offset_stack))); + opline = zend_delayed_emit_op(NULL, ZEND_JMP_NULL, obj_node, NULL); + } else { + jmp_null_opnum.opnum = get_next_op_number(); + opline = zend_emit_op(NULL, ZEND_JMP_NULL, obj_node, NULL); + } + if (opline->op1_type == IS_CONST) { Z_TRY_ADDREF_P(CT_CONSTANT(opline->op1)); } @@ -2796,7 +2848,7 @@ static zend_op *zend_delayed_compile_prop(znode *result, zend_ast *ast, uint32_t opline = zend_delayed_compile_var(&obj_node, obj_ast, type, 0); zend_separate_if_call_and_write(&obj_node, obj_ast, type); if (nullsafe) { - zend_emit_jmp_null(&obj_node); + zend_emit_jmp_null(&obj_node, 1); } } @@ -4356,7 +4408,7 @@ void zend_compile_method_call(znode *result, zend_ast *ast, uint32_t type) /* {{ zend_short_circuiting_mark_inner(obj_ast); zend_compile_expr(&obj_node, obj_ast); if (nullsafe) { - zend_emit_jmp_null(&obj_node); + zend_emit_jmp_null(&obj_node, 0); } } diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 652e0ef1e1329..b9c2338fa471a 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -119,6 +119,8 @@ struct _zend_compiler_globals { zend_arena *ast_arena; zend_stack delayed_oplines_stack; + zend_stack delayed_oplines_offset_stack; + uint32_t delayed_oplines_stack_level; HashTable *memoized_exprs; int memoize_mode;