Skip to content
7 changes: 7 additions & 0 deletions Zend/Optimizer/block_pass.c
Original file line number Diff line number Diff line change
Expand Up @@ -911,6 +911,9 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array
src->opcode != ZEND_COPY_TMP &&
src->opcode != ZEND_ADD_ARRAY_ELEMENT &&
src->opcode != ZEND_ADD_ARRAY_UNPACK &&
/* ARRAY_DUP is ok. If it is used within a QM_ASSIGN, it
* has no ARRAY_SET_PLACEHOLDER calls. */
src->opcode != ZEND_ARRAY_SET_PLACEHOLDER &&
(src->opcode != ZEND_DECLARE_LAMBDA_FUNCTION ||
src == opline -1)) {
src->result.var = opline->result.var;
Expand Down Expand Up @@ -1474,6 +1477,7 @@ static void zend_t_usage(zend_cfg *cfg, zend_op_array *op_array, zend_bitset use
switch (opline->opcode) {
case ZEND_ADD_ARRAY_ELEMENT:
case ZEND_ADD_ARRAY_UNPACK:
case ZEND_ARRAY_SET_PLACEHOLDER:
case ZEND_ROPE_ADD:
/* these opcodes use the result as argument */
if (!zend_bitset_in(defined_here, var_num)) {
Expand Down Expand Up @@ -1568,6 +1572,8 @@ static void zend_t_usage(zend_cfg *cfg, zend_op_array *op_array, zend_bitset use
break;
case ZEND_ADD_ARRAY_ELEMENT:
case ZEND_ADD_ARRAY_UNPACK:
/* FIXME: Can we eliminate these? There's no reason for them to be added if unused. */
case ZEND_ARRAY_SET_PLACEHOLDER:
case ZEND_ROPE_ADD:
zend_bitset_incl(usage, VAR_NUM(opline->result.var));
break;
Expand All @@ -1576,6 +1582,7 @@ static void zend_t_usage(zend_cfg *cfg, zend_op_array *op_array, zend_bitset use
switch (opline->opcode) {
case ZEND_ADD_ARRAY_ELEMENT:
case ZEND_ADD_ARRAY_UNPACK:
case ZEND_ARRAY_SET_PLACEHOLDER:
case ZEND_ROPE_ADD:
break;
default:
Expand Down
2 changes: 2 additions & 0 deletions Zend/Optimizer/dce.c
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ static inline bool may_have_side_effects(
case ZEND_ROPE_INIT:
case ZEND_ROPE_ADD:
case ZEND_INIT_ARRAY:
case ZEND_ARRAY_DUP:
case ZEND_ARRAY_SET_PLACEHOLDER:
case ZEND_SPACESHIP:
case ZEND_STRLEN:
case ZEND_COUNT:
Expand Down
5 changes: 5 additions & 0 deletions Zend/Optimizer/dfa_pass.c
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,11 @@ static bool opline_supports_assign_contraction(
&& (opline->op2_type != IS_CV || opline->op2.var != cv_var);
}

if (opline->opcode == ZEND_ARRAY_DUP) {
/* ARRAY_DUP initializes the result array before reading key/value. */
return opline->op2_type != IS_CV || opline->op2.var != cv_var;
}

if (opline->opcode == ZEND_CAST
&& (opline->extended_value == IS_ARRAY || opline->extended_value == IS_OBJECT)) {
/* CAST to array/object may initialize the result to an empty array/object before
Expand Down
17 changes: 17 additions & 0 deletions Zend/Optimizer/escape_analysis.c
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ static bool is_allocation_def(zend_op_array *op_array, zend_ssa *ssa, int def, i
if (ssa_op->result_def == var) {
switch (opline->opcode) {
case ZEND_INIT_ARRAY:
case ZEND_ARRAY_DUP:
return 1;
case ZEND_NEW: {
/* objects with destructors should escape */
Expand Down Expand Up @@ -227,6 +228,8 @@ static bool is_local_def(zend_op_array *op_array, zend_ssa *ssa, int def, int va
switch (opline->opcode) {
case ZEND_INIT_ARRAY:
case ZEND_ADD_ARRAY_ELEMENT:
case ZEND_ARRAY_DUP:
case ZEND_ARRAY_SET_PLACEHOLDER:
case ZEND_QM_ASSIGN:
case ZEND_ASSIGN:
return 1;
Expand Down Expand Up @@ -318,6 +321,13 @@ static bool is_escape_use(zend_op_array *op_array, zend_ssa *ssa, int use, int v
}
/* reference dependencies processed separately */
break;
case ZEND_ARRAY_DUP:
case ZEND_ARRAY_SET_PLACEHOLDER:
if (OP2_INFO() & MAY_BE_OBJECT) {
/* object aliasing */
return 1;
}
break;
case ZEND_OP_DATA:
if ((opline-1)->opcode != ZEND_ASSIGN_DIM
&& (opline-1)->opcode != ZEND_ASSIGN_OBJ) {
Expand Down Expand Up @@ -369,6 +379,8 @@ static bool is_escape_use(zend_op_array *op_array, zend_ssa *ssa, int use, int v
case ZEND_QM_ASSIGN:
case ZEND_INIT_ARRAY:
case ZEND_ADD_ARRAY_ELEMENT:
case ZEND_ARRAY_DUP:
case ZEND_ARRAY_SET_PLACEHOLDER:
break;
default:
return 1;
Expand Down Expand Up @@ -492,6 +504,11 @@ zend_result zend_ssa_escape_analysis(const zend_script *script, zend_op_array *o
op->op1_use == i &&
op->result_def >= 0) {
enclosing_root = ees[op->result_def];
} else if ((opline->opcode == ZEND_ARRAY_DUP
|| opline->opcode == ZEND_ARRAY_SET_PLACEHOLDER)
&& op->op2_use == i
&& op->result_def >= 0) {
enclosing_root = ees[op->result_def];
} else {
continue;
}
Expand Down
38 changes: 37 additions & 1 deletion Zend/Optimizer/sccp.c
Original file line number Diff line number Diff line change
Expand Up @@ -1182,6 +1182,40 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o
}
return;
}
case ZEND_ARRAY_DUP:
case ZEND_ARRAY_SET_PLACEHOLDER: {
zval *result;
if (opline->opcode == ZEND_ARRAY_DUP) {
result = op1;
} else {
result = &ctx->values[ssa_op->result_use];
SKIP_IF_TOP(result);
}

SKIP_IF_TOP(op2);

/* FIXME: Avoid dup-ing the array in every step. */
zend_array *array = zend_array_dup(Z_ARRVAL_P(result));
zval *element = (zval*)((uintptr_t)array->arData + opline->extended_value);

zval tmp;
ZVAL_ARR(&tmp, array);

if (IS_BOT(op2)) {
/* Remove unknown index and mark array as partial. */
MAKE_BOT(element);
MAKE_PARTIAL_ARRAY(&tmp);
} else {
ZVAL_COPY(element, op2);
if (IS_PARTIAL_ARRAY(result) || IS_PARTIAL_ARRAY(op2)) {
MAKE_PARTIAL_ARRAY(&tmp);
}
}

SET_RESULT(result, &tmp);
zval_ptr_dtor_nogc(&tmp);
return;
}
case ZEND_ADD_ARRAY_UNPACK: {
zval *result = &ctx->values[ssa_op->result_use];
if (IS_BOT(result) || IS_BOT(op1)) {
Expand Down Expand Up @@ -2196,7 +2230,9 @@ static int try_remove_definition(sccp_ctx *ctx, int var_num, zend_ssa_var *var,
&& opline->opcode != ZEND_ROPE_ADD
&& opline->opcode != ZEND_INIT_ARRAY
&& opline->opcode != ZEND_ADD_ARRAY_ELEMENT
&& opline->opcode != ZEND_ADD_ARRAY_UNPACK) {
&& opline->opcode != ZEND_ADD_ARRAY_UNPACK
&& opline->opcode != ZEND_ARRAY_DUP
&& opline->opcode != ZEND_ARRAY_SET_PLACEHOLDER) {
/* Replace with QM_ASSIGN */
uint8_t old_type = opline->result_type;
uint32_t old_var = opline->result.var;
Expand Down
12 changes: 12 additions & 0 deletions Zend/Optimizer/zend_dfg.c
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,18 @@ static zend_always_inline void _zend_dfg_add_use_def_op(const zend_op_array *op_
goto add_op1_def;
}
break;
/* FIXME: Verify whether this is correct. */
case ZEND_ARRAY_SET_PLACEHOLDER:
var_num = EX_VAR_TO_NUM(opline->result.var);
if (!zend_bitset_in(def, var_num)) {
zend_bitset_incl(use, var_num);
}
ZEND_FALLTHROUGH;
case ZEND_ARRAY_DUP:
if ((build_flags & ZEND_SSA_RC_INFERENCE) && opline->op2_type == IS_CV) {
zend_bitset_incl(def, EX_VAR_TO_NUM(opline->op2.var));
}
break;
case ZEND_YIELD:
if (opline->op1_type == IS_CV
&& ((op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE)
Expand Down
34 changes: 32 additions & 2 deletions Zend/Optimizer/zend_inference.c
Original file line number Diff line number Diff line change
Expand Up @@ -2105,7 +2105,9 @@ ZEND_API uint32_t ZEND_FASTCALL zend_array_type_info(const zval *zv)
} else if (HT_IS_PACKED(ht)) {
tmp |= MAY_BE_ARRAY_PACKED;
ZEND_HASH_PACKED_FOREACH_VAL(ht, val) {
tmp |= 1 << (Z_TYPE_P(val) + MAY_BE_ARRAY_SHIFT);
if (Z_TYPE_P(val) != _IS_ERROR) {
tmp |= 1 << (Z_TYPE_P(val) + MAY_BE_ARRAY_SHIFT);
}
} ZEND_HASH_FOREACH_END();
} else {
ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(ht, str, val) {
Expand All @@ -2114,7 +2116,9 @@ ZEND_API uint32_t ZEND_FASTCALL zend_array_type_info(const zval *zv)
} else {
tmp |= MAY_BE_ARRAY_NUMERIC_HASH;
}
tmp |= 1 << (Z_TYPE_P(val) + MAY_BE_ARRAY_SHIFT);
if (Z_TYPE_P(val) != _IS_ERROR) {
tmp |= 1 << (Z_TYPE_P(val) + MAY_BE_ARRAY_SHIFT);
}
} ZEND_HASH_FOREACH_END();
}
return tmp;
Expand Down Expand Up @@ -3443,6 +3447,28 @@ static zend_always_inline zend_result _zend_update_type_info(
UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
}
break;
case ZEND_ARRAY_DUP:
case ZEND_ARRAY_SET_PLACEHOLDER: {
if (ssa_op->op2_def) {
tmp = t2;
tmp &= ~MAY_BE_RC1;
tmp |= MAY_BE_RCN;
UPDATE_SSA_TYPE(tmp, ssa_op->op2_def);
}
if (opline->opcode == ZEND_ARRAY_DUP) {
ZEND_ASSERT(opline->op1_type == IS_CONST);
zval *template = CRT_CONSTANT_EX(op_array, opline, opline->op1);
tmp = zend_array_type_info(template);
tmp &= ~MAY_BE_RCN;
tmp |= MAY_BE_RC1;
} else {
tmp = RES_USE_INFO();
}
uint32_t dim_type = ((tmp & MAY_BE_ARRAY_KEY_LONG) ? MAY_BE_LONG : 0) | ((tmp & MAY_BE_ARRAY_KEY_STRING) ? MAY_BE_STRING : 0);
tmp |= assign_dim_array_result_type(tmp, dim_type, t2, IS_CONST);
UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
break;
}
case ZEND_ADD_ARRAY_UNPACK:
tmp = ssa_var_info[ssa_op->result_use].type;
ZEND_ASSERT(tmp & MAY_BE_ARRAY);
Expand Down Expand Up @@ -3662,6 +3688,8 @@ static zend_always_inline zend_result _zend_update_type_info(
case ZEND_YIELD:
case ZEND_INIT_ARRAY:
case ZEND_ADD_ARRAY_ELEMENT:
case ZEND_ARRAY_DUP:
case ZEND_ARRAY_SET_PLACEHOLDER:
case ZEND_RETURN_BY_REF:
case ZEND_VERIFY_RETURN_TYPE:
case ZEND_MAKE_REF:
Expand Down Expand Up @@ -5024,6 +5052,8 @@ ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op
case ZEND_COPY_TMP:
case ZEND_JMP_NULL:
case ZEND_JMP_FRAMELESS:
case ZEND_ARRAY_DUP:
case ZEND_ARRAY_SET_PLACEHOLDER:
return 0;
case ZEND_IS_IDENTICAL:
case ZEND_IS_NOT_IDENTICAL:
Expand Down
11 changes: 11 additions & 0 deletions Zend/Optimizer/zend_ssa.c
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,17 @@ static zend_always_inline int _zend_ssa_rename_op(const zend_op_array *op_array,
goto add_op1_def;
}
break;
case ZEND_ARRAY_SET_PLACEHOLDER:
ssa_ops[k].result_use = var[EX_VAR_TO_NUM(opline->result.var)];
ZEND_FALLTHROUGH;
case ZEND_ARRAY_DUP:
if ((build_flags & ZEND_SSA_RC_INFERENCE) && opline->op2_type == IS_CV) {
ssa_ops[k].op2_def = ssa_vars_count;
var[EX_VAR_TO_NUM(opline->op2.var)] = ssa_vars_count;
ssa_vars_count++;
//NEW_SSA_VAR(opline->op2.var)
}
break;
case ZEND_YIELD:
if (opline->op1_type == IS_CV
&& ((op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE)
Expand Down
2 changes: 2 additions & 0 deletions Zend/tests/array_unpack/unpack_invalid_type_compile_time.phpt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
--TEST--
Unpacking non-array/Traversable detected at compile-time
--XFAIL--
Still need to check why this behavior changes
--FILE--
<?php

Expand Down
Loading
Loading