diff --git a/NEWS b/NEWS index 1746e364db46c..a7091727a8b1d 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,8 @@ PHP NEWS - Core: . Fixed bug GH-8655 (Casting an object to array does not unwrap refcount=1 references). (Nicolas Grekas) + . Fixed bug GH-8661 (Nullsafe in coalesce triggers undefined variable + warning). (ilutov) - MBString: . Backwards-compatible mappings for 0x5C/0x7E in Shift-JIS are restored, diff --git a/Zend/Optimizer/sccp.c b/Zend/Optimizer/sccp.c index ba4bdab6c0a7a..8317f9700ca6d 100644 --- a/Zend/Optimizer/sccp.c +++ b/Zend/Optimizer/sccp.c @@ -1563,7 +1563,7 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o SET_RESULT(result, op1); break; case ZEND_JMP_NULL: - switch (opline->extended_value) { + switch (opline->extended_value & ZEND_SHORT_CIRCUITING_CHAIN_MASK) { case ZEND_SHORT_CIRCUITING_CHAIN_EXPR: ZVAL_NULL(&zv); break; diff --git a/Zend/Optimizer/zend_inference.c b/Zend/Optimizer/zend_inference.c index c17c73ad0efcd..1b8abd71aee0e 100644 --- a/Zend/Optimizer/zend_inference.c +++ b/Zend/Optimizer/zend_inference.c @@ -2457,16 +2457,19 @@ static zend_always_inline zend_result _zend_update_type_info( COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->result_def); break; case ZEND_JMP_NULL: - if (opline->extended_value == ZEND_SHORT_CIRCUITING_CHAIN_EXPR) { + { + uint32_t short_circuiting_type = opline->extended_value & ZEND_SHORT_CIRCUITING_CHAIN_MASK; + if (short_circuiting_type == ZEND_SHORT_CIRCUITING_CHAIN_EXPR) { tmp = MAY_BE_NULL; - } else if (opline->extended_value == ZEND_SHORT_CIRCUITING_CHAIN_ISSET) { + } else if (short_circuiting_type == ZEND_SHORT_CIRCUITING_CHAIN_ISSET) { tmp = MAY_BE_FALSE; } else { - ZEND_ASSERT(opline->extended_value == ZEND_SHORT_CIRCUITING_CHAIN_EMPTY); + ZEND_ASSERT(short_circuiting_type == ZEND_SHORT_CIRCUITING_CHAIN_EMPTY); tmp = MAY_BE_TRUE; } UPDATE_SSA_TYPE(tmp, ssa_op->result_def); break; + } case ZEND_ASSIGN_OP: case ZEND_ASSIGN_DIM_OP: case ZEND_ASSIGN_OBJ_OP: diff --git a/Zend/tests/nullsafe_operator/gh8661.phpt b/Zend/tests/nullsafe_operator/gh8661.phpt new file mode 100644 index 0000000000000..9c4d47bec13c4 --- /dev/null +++ b/Zend/tests/nullsafe_operator/gh8661.phpt @@ -0,0 +1,10 @@ +--TEST-- +GH-8661: Nullsafe in coalesce triggers undefined variable error +--FILE-- +foo ?? null); + +?> +--EXPECT-- +NULL diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 0ec50dc58922c..b272a95ee3d64 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -2261,7 +2261,7 @@ static void zend_short_circuiting_commit(uint32_t checkpoint, znode *result, zen zend_op *opline = &CG(active_op_array)->opcodes[opnum]; opline->op2.opline_num = get_next_op_number(); SET_NODE(opline->result, result); - opline->extended_value = + opline->extended_value |= ast->kind == ZEND_AST_ISSET ? ZEND_SHORT_CIRCUITING_CHAIN_ISSET : ast->kind == ZEND_AST_EMPTY ? ZEND_SHORT_CIRCUITING_CHAIN_EMPTY : ZEND_SHORT_CIRCUITING_CHAIN_EXPR; @@ -2269,13 +2269,16 @@ 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, uint32_t bp_type) { uint32_t jmp_null_opnum = get_next_op_number(); zend_op *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)); } + if (bp_type == BP_VAR_IS) { + opline->extended_value |= ZEND_JMP_NULL_BP_VAR_IS; + } zend_stack_push(&CG(short_circuiting_opnums), &jmp_null_opnum); } @@ -2850,7 +2853,7 @@ static zend_op *zend_delayed_compile_prop(znode *result, zend_ast *ast, uint32_t } } } - zend_emit_jmp_null(&obj_node); + zend_emit_jmp_null(&obj_node, type); } } @@ -4461,7 +4464,7 @@ static 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, type); } } diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 72726bdab02af..89c1b1f6579b4 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -372,10 +372,14 @@ typedef struct _zend_oparray_context { /* call through internal function handler. e.g. Closure::invoke() */ #define ZEND_ACC_CALL_VIA_HANDLER ZEND_ACC_CALL_VIA_TRAMPOLINE +#define ZEND_SHORT_CIRCUITING_CHAIN_MASK 0x3 #define ZEND_SHORT_CIRCUITING_CHAIN_EXPR 0 #define ZEND_SHORT_CIRCUITING_CHAIN_ISSET 1 #define ZEND_SHORT_CIRCUITING_CHAIN_EMPTY 2 +// Must not clash with ZEND_SHORT_CIRCUITING_CHAIN_MASK +#define ZEND_JMP_NULL_BP_VAR_IS 4 + char *zend_visibility_string(uint32_t fn_flags); typedef struct _zend_property_info { diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 0dc7f4d5f4d90..206d8ae733665 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -7574,19 +7574,23 @@ ZEND_VM_HOT_NOCONST_HANDLER(198, ZEND_JMP_NULL, CONST|TMP|VAR|CV, JMP_ADDR) } result = EX_VAR(opline->result.var); - if (EXPECTED(opline->extended_value == ZEND_SHORT_CIRCUITING_CHAIN_EXPR)) { + uint32_t short_circuiting_type = opline->extended_value & ZEND_SHORT_CIRCUITING_CHAIN_MASK; + if (EXPECTED(short_circuiting_type == ZEND_SHORT_CIRCUITING_CHAIN_EXPR)) { ZVAL_NULL(result); - if (OP1_TYPE == IS_CV && UNEXPECTED(Z_TYPE_P(val) == IS_UNDEF)) { + if (OP1_TYPE == IS_CV + && UNEXPECTED(Z_TYPE_P(val) == IS_UNDEF) + && (opline->extended_value & ZEND_JMP_NULL_BP_VAR_IS) == 0 + ) { SAVE_OPLINE(); ZVAL_UNDEFINED_OP1(); if (UNEXPECTED(EG(exception) != NULL)) { HANDLE_EXCEPTION(); } } - } else if (opline->extended_value == ZEND_SHORT_CIRCUITING_CHAIN_ISSET) { + } else if (short_circuiting_type == ZEND_SHORT_CIRCUITING_CHAIN_ISSET) { ZVAL_FALSE(result); } else { - ZEND_ASSERT(opline->extended_value == ZEND_SHORT_CIRCUITING_CHAIN_EMPTY); + ZEND_ASSERT(short_circuiting_type == ZEND_SHORT_CIRCUITING_CHAIN_EMPTY); ZVAL_TRUE(result); } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 87480a0c8ec16..5400dd3466e1f 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -5244,19 +5244,23 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_JMP_NULL_SPEC_CON } result = EX_VAR(opline->result.var); - if (EXPECTED(opline->extended_value == ZEND_SHORT_CIRCUITING_CHAIN_EXPR)) { + uint32_t short_circuiting_type = opline->extended_value & ZEND_SHORT_CIRCUITING_CHAIN_MASK; + if (EXPECTED(short_circuiting_type == ZEND_SHORT_CIRCUITING_CHAIN_EXPR)) { ZVAL_NULL(result); - if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_P(val) == IS_UNDEF)) { + if (IS_CONST == IS_CV + && UNEXPECTED(Z_TYPE_P(val) == IS_UNDEF) + && (opline->extended_value & ZEND_JMP_NULL_BP_VAR_IS) == 0 + ) { SAVE_OPLINE(); ZVAL_UNDEFINED_OP1(); if (UNEXPECTED(EG(exception) != NULL)) { HANDLE_EXCEPTION(); } } - } else if (opline->extended_value == ZEND_SHORT_CIRCUITING_CHAIN_ISSET) { + } else if (short_circuiting_type == ZEND_SHORT_CIRCUITING_CHAIN_ISSET) { ZVAL_FALSE(result); } else { - ZEND_ASSERT(opline->extended_value == ZEND_SHORT_CIRCUITING_CHAIN_EMPTY); + ZEND_ASSERT(short_circuiting_type == ZEND_SHORT_CIRCUITING_CHAIN_EMPTY); ZVAL_TRUE(result); } @@ -19344,19 +19348,23 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_JMP_NULL_SPEC_TMP_ } result = EX_VAR(opline->result.var); - if (EXPECTED(opline->extended_value == ZEND_SHORT_CIRCUITING_CHAIN_EXPR)) { + uint32_t short_circuiting_type = opline->extended_value & ZEND_SHORT_CIRCUITING_CHAIN_MASK; + if (EXPECTED(short_circuiting_type == ZEND_SHORT_CIRCUITING_CHAIN_EXPR)) { ZVAL_NULL(result); - if (IS_TMP_VAR == IS_CV && UNEXPECTED(Z_TYPE_P(val) == IS_UNDEF)) { + if (IS_TMP_VAR == IS_CV + && UNEXPECTED(Z_TYPE_P(val) == IS_UNDEF) + && (opline->extended_value & ZEND_JMP_NULL_BP_VAR_IS) == 0 + ) { SAVE_OPLINE(); ZVAL_UNDEFINED_OP1(); if (UNEXPECTED(EG(exception) != NULL)) { HANDLE_EXCEPTION(); } } - } else if (opline->extended_value == ZEND_SHORT_CIRCUITING_CHAIN_ISSET) { + } else if (short_circuiting_type == ZEND_SHORT_CIRCUITING_CHAIN_ISSET) { ZVAL_FALSE(result); } else { - ZEND_ASSERT(opline->extended_value == ZEND_SHORT_CIRCUITING_CHAIN_EMPTY); + ZEND_ASSERT(short_circuiting_type == ZEND_SHORT_CIRCUITING_CHAIN_EMPTY); ZVAL_TRUE(result); } @@ -22267,19 +22275,23 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_JMP_NULL_SPEC_VAR_ } result = EX_VAR(opline->result.var); - if (EXPECTED(opline->extended_value == ZEND_SHORT_CIRCUITING_CHAIN_EXPR)) { + uint32_t short_circuiting_type = opline->extended_value & ZEND_SHORT_CIRCUITING_CHAIN_MASK; + if (EXPECTED(short_circuiting_type == ZEND_SHORT_CIRCUITING_CHAIN_EXPR)) { ZVAL_NULL(result); - if (IS_VAR == IS_CV && UNEXPECTED(Z_TYPE_P(val) == IS_UNDEF)) { + if (IS_VAR == IS_CV + && UNEXPECTED(Z_TYPE_P(val) == IS_UNDEF) + && (opline->extended_value & ZEND_JMP_NULL_BP_VAR_IS) == 0 + ) { SAVE_OPLINE(); ZVAL_UNDEFINED_OP1(); if (UNEXPECTED(EG(exception) != NULL)) { HANDLE_EXCEPTION(); } } - } else if (opline->extended_value == ZEND_SHORT_CIRCUITING_CHAIN_ISSET) { + } else if (short_circuiting_type == ZEND_SHORT_CIRCUITING_CHAIN_ISSET) { ZVAL_FALSE(result); } else { - ZEND_ASSERT(opline->extended_value == ZEND_SHORT_CIRCUITING_CHAIN_EMPTY); + ZEND_ASSERT(short_circuiting_type == ZEND_SHORT_CIRCUITING_CHAIN_EMPTY); ZVAL_TRUE(result); } @@ -39116,19 +39128,23 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_JMP_NULL_SPEC_CV_H } result = EX_VAR(opline->result.var); - if (EXPECTED(opline->extended_value == ZEND_SHORT_CIRCUITING_CHAIN_EXPR)) { + uint32_t short_circuiting_type = opline->extended_value & ZEND_SHORT_CIRCUITING_CHAIN_MASK; + if (EXPECTED(short_circuiting_type == ZEND_SHORT_CIRCUITING_CHAIN_EXPR)) { ZVAL_NULL(result); - if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_P(val) == IS_UNDEF)) { + if (IS_CV == IS_CV + && UNEXPECTED(Z_TYPE_P(val) == IS_UNDEF) + && (opline->extended_value & ZEND_JMP_NULL_BP_VAR_IS) == 0 + ) { SAVE_OPLINE(); ZVAL_UNDEFINED_OP1(); if (UNEXPECTED(EG(exception) != NULL)) { HANDLE_EXCEPTION(); } } - } else if (opline->extended_value == ZEND_SHORT_CIRCUITING_CHAIN_ISSET) { + } else if (short_circuiting_type == ZEND_SHORT_CIRCUITING_CHAIN_ISSET) { ZVAL_FALSE(result); } else { - ZEND_ASSERT(opline->extended_value == ZEND_SHORT_CIRCUITING_CHAIN_EMPTY); + ZEND_ASSERT(short_circuiting_type == ZEND_SHORT_CIRCUITING_CHAIN_EMPTY); ZVAL_TRUE(result); }