Skip to content

Fix GH-8661: Nullsafe in coalesce triggers undefined variable warning #8690

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion Zend/Optimizer/sccp.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
9 changes: 6 additions & 3 deletions Zend/Optimizer/zend_inference.c
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
10 changes: 10 additions & 0 deletions Zend/tests/nullsafe_operator/gh8661.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--TEST--
GH-8661: Nullsafe in coalesce triggers undefined variable error
--FILE--
<?php

var_dump($a?->foo ?? null);

?>
--EXPECT--
NULL
11 changes: 7 additions & 4 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -2261,21 +2261,24 @@ 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;
zend_stack_del_top(&CG(short_circuiting_opnums));
}
}

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);
}

Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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);
}
}

Expand Down
4 changes: 4 additions & 0 deletions Zend/zend_compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
12 changes: 8 additions & 4 deletions Zend/zend_vm_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
48 changes: 32 additions & 16 deletions Zend/zend_vm_execute.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.