1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

Implement consistent behavior for foreach by value over plain object

This commit is contained in:
Dmitry Stogov
2015-01-30 09:49:35 +03:00
parent 4c5b385ff5
commit 5aa9712b0a
6 changed files with 1024 additions and 631 deletions

View File

@@ -0,0 +1,40 @@
--TEST--
Nested foreach by value over object and object modification with resize
--FILE--
<?php
$o = (object)['a'=>0, 'b'=>1, 'c'=>2, 'd'=>3, 'e'=>4, 'f'=>5, 'g'=>6, 'h'=>7];
unset($o->a, $o->b, $o->c, $o->d);
foreach ($o as $v1) {
foreach ($o as $v2) {
echo "$v1-$v2\n";
if ($v1 == 5 && $v2 == 6) {
$o->i = 8;
}
}
}
?>
--EXPECT--
4-4
4-5
4-6
4-7
5-4
5-5
5-6
5-7
5-8
6-4
6-5
6-6
6-7
6-8
7-4
7-5
7-6
7-7
7-8
8-4
8-5
8-6
8-7
8-8

View File

@@ -3398,7 +3398,7 @@ void zend_compile_foreach(zend_ast *ast) /* {{{ */
opnum_reset = get_next_op_number(CG(active_op_array));
opline = zend_emit_op(&reset_node, by_ref ? ZEND_FE_RESET_RW : ZEND_FE_RESET_R, &expr_node, NULL);
reset_node.flag = by_ref;
reset_node.flag = 1; /* generate FE_FREE */
zend_stack_push(&CG(loop_var_stack), &reset_node);
opnum_fetch = get_next_op_number(CG(active_op_array));

View File

@@ -1587,7 +1587,7 @@ static inline zend_brk_cont_element* zend_brk_cont(int nest_levels, int array_of
} else if (brk_opline->opcode == ZEND_FE_FREE) {
if (!(brk_opline->extended_value & EXT_TYPE_FREE_ON_RETURN)) {
zval *var = EX_VAR(brk_opline->op1.var);
if (Z_FE_ITER_P(var) != (uint32_t)-1) {
if (Z_TYPE_P(var) != IS_ARRAY && Z_FE_ITER_P(var) != (uint32_t)-1) {
zend_hash_iterator_del(Z_FE_ITER_P(var));
}
zval_ptr_dtor_nogc(var);

View File

@@ -62,7 +62,7 @@ static void zend_generator_cleanup_unfinished_execution(zend_generator *generato
zval_ptr_dtor_nogc(var);
} else if (brk_opline->opcode == ZEND_FE_FREE) {
zval *var = EX_VAR(brk_opline->op1.var);
if (Z_FE_ITER_P(var) != (uint32_t)-1) {
if (Z_TYPE_P(var) != IS_ARRAY && Z_FE_ITER_P(var) != (uint32_t)-1) {
zend_hash_iterator_del(Z_FE_ITER_P(var));
}
zval_ptr_dtor_nogc(var);

View File

@@ -2145,7 +2145,7 @@ ZEND_VM_HANDLER(127, ZEND_FE_FREE, TMPVAR, ANY)
SAVE_OPLINE();
var = EX_VAR(opline->op1.var);
if (Z_FE_ITER_P(var) != (uint32_t)-1) {
if (Z_TYPE_P(var) != IS_ARRAY && Z_FE_ITER_P(var) != (uint32_t)-1) {
zend_hash_iterator_del(Z_FE_ITER_P(var));
}
zval_ptr_dtor_nogc(var);
@@ -3839,7 +3839,7 @@ ZEND_VM_HANDLER(100, ZEND_GOTO, ANY, CONST)
} else if (brk_opline->opcode == ZEND_FE_FREE) {
if (!(brk_opline->extended_value & EXT_TYPE_FREE_ON_RETURN)) {
zval *var = EX_VAR(brk_opline->op1.var);
if (Z_FE_ITER_P(var) != (uint32_t)-1) {
if (Z_TYPE_P(var) != IS_ARRAY && Z_FE_ITER_P(var) != (uint32_t)-1) {
zend_hash_iterator_del(Z_FE_ITER_P(var));
}
zval_ptr_dtor_nogc(var);
@@ -4647,58 +4647,14 @@ ZEND_VM_HANDLER(77, ZEND_FE_RESET_R, CONST|TMP|VAR|CV, ANY)
{
USE_OPLINE
zend_free_op free_op1;
zval *array_ptr;
zval *array_ptr, *result;
HashTable *fe_ht;
SAVE_OPLINE();
array_ptr = GET_OP1_ZVAL_PTR_DEREF(BP_VAR_R);
if (OP1_TYPE != IS_CONST &&
Z_TYPE_P(array_ptr) == IS_OBJECT && Z_OBJCE_P(array_ptr)->get_iterator) {
zend_class_entry *ce = Z_OBJCE_P(array_ptr);
zend_object_iterator *iter = ce->get_iterator(ce, array_ptr, 0);
zend_bool is_empty;
if (UNEXPECTED(!iter) || UNEXPECTED(EG(exception))) {
FREE_OP1();
if (!EG(exception)) {
zend_throw_exception_ex(NULL, 0, "Object of type %s did not create an Iterator", ce->name->val);
}
zend_throw_exception_internal(NULL);
HANDLE_EXCEPTION();
}
iter->index = 0;
if (iter->funcs->rewind) {
iter->funcs->rewind(iter);
if (UNEXPECTED(EG(exception) != NULL)) {
OBJ_RELEASE(&iter->std);
FREE_OP1();
HANDLE_EXCEPTION();
}
}
is_empty = iter->funcs->valid(iter) != SUCCESS;
if (UNEXPECTED(EG(exception) != NULL)) {
OBJ_RELEASE(&iter->std);
FREE_OP1();
HANDLE_EXCEPTION();
}
iter->index = -1; /* will be set to 0 before using next handler */
ZVAL_OBJ(EX_VAR(opline->result.var), &iter->std);
FREE_OP1();
if (is_empty) {
ZEND_VM_JMP(OP_JMP_ADDR(opline, opline->op2));
} else {
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
} else if ((fe_ht = HASH_OF(array_ptr)) != NULL) {
zval *result = EX_VAR(opline->result.var);
if (EXPECTED(Z_TYPE_P(array_ptr) == IS_ARRAY)) {
result = EX_VAR(opline->result.var);
ZVAL_COPY_VALUE(result, array_ptr);
if (OP1_TYPE != IS_TMP_VAR && Z_OPT_REFCOUNTED_P(result)) {
Z_ADDREF_P(array_ptr);
@@ -4708,9 +4664,88 @@ ZEND_VM_HANDLER(77, ZEND_FE_RESET_R, CONST|TMP|VAR|CV, ANY)
FREE_OP1_IF_VAR();
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
} else if (OP1_TYPE != IS_CONST && EXPECTED(Z_TYPE_P(array_ptr) == IS_OBJECT)) {
if (!Z_OBJCE_P(array_ptr)->get_iterator) {
HashPosition pos = 0;
Bucket *p;
result = EX_VAR(opline->result.var);
ZVAL_COPY_VALUE(result, array_ptr);
if (OP1_TYPE != IS_TMP_VAR) {
Z_ADDREF_P(array_ptr);
}
fe_ht = Z_OBJPROP_P(array_ptr);
pos = 0;
while (1) {
if (pos >= fe_ht->nNumUsed) {
FREE_OP1_IF_VAR();
Z_FE_ITER_P(EX_VAR(opline->result.var)) = (uint32_t)-1;
ZEND_VM_JMP(OP_JMP_ADDR(opline, opline->op2));
}
p = fe_ht->arData + pos;
if ((Z_TYPE(p->val) != IS_UNDEF &&
(Z_TYPE(p->val) != IS_INDIRECT ||
Z_TYPE_P(Z_INDIRECT(p->val)) != IS_UNDEF)) &&
(!p->key ||
zend_check_property_access(Z_OBJ_P(array_ptr), p->key) == SUCCESS)) {
break;
}
pos++;
}
fe_ht->nInternalPointer = pos;
Z_FE_ITER_P(EX_VAR(opline->result.var)) = zend_hash_iterator_add(fe_ht);
FREE_OP1_IF_VAR();
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
} else {
zend_class_entry *ce = Z_OBJCE_P(array_ptr);
zend_object_iterator *iter = ce->get_iterator(ce, array_ptr, 0);
zend_bool is_empty;
if (UNEXPECTED(!iter) || UNEXPECTED(EG(exception))) {
FREE_OP1();
if (!EG(exception)) {
zend_throw_exception_ex(NULL, 0, "Object of type %s did not create an Iterator", ce->name->val);
}
zend_throw_exception_internal(NULL);
HANDLE_EXCEPTION();
}
iter->index = 0;
if (iter->funcs->rewind) {
iter->funcs->rewind(iter);
if (UNEXPECTED(EG(exception) != NULL)) {
OBJ_RELEASE(&iter->std);
FREE_OP1();
HANDLE_EXCEPTION();
}
}
is_empty = iter->funcs->valid(iter) != SUCCESS;
if (UNEXPECTED(EG(exception) != NULL)) {
OBJ_RELEASE(&iter->std);
FREE_OP1();
HANDLE_EXCEPTION();
}
iter->index = -1; /* will be set to 0 before using next handler */
ZVAL_OBJ(EX_VAR(opline->result.var), &iter->std);
Z_FE_ITER_P(EX_VAR(opline->result.var)) = (uint32_t)-1;
FREE_OP1();
if (is_empty) {
ZEND_VM_JMP(OP_JMP_ADDR(opline, opline->op2));
} else {
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
}
} else {
zend_error(E_WARNING, "Invalid argument supplied for foreach()");
ZVAL_UNDEF(EX_VAR(opline->result.var));
Z_FE_ITER_P(EX_VAR(opline->result.var)) = (uint32_t)-1;
FREE_OP1();
ZEND_VM_JMP(OP_JMP_ADDR(opline, opline->op2));
}
@@ -4722,6 +4757,8 @@ ZEND_VM_HANDLER(125, ZEND_FE_RESET_RW, CONST|TMP|VAR|CV, ANY)
zend_free_op free_op1;
zval *array_ptr, *array_ref;
HashTable *fe_ht;
HashPosition pos = 0;
Bucket *p;
SAVE_OPLINE();
@@ -4734,70 +4771,7 @@ ZEND_VM_HANDLER(125, ZEND_FE_RESET_RW, CONST|TMP|VAR|CV, ANY)
array_ref = array_ptr = GET_OP1_ZVAL_PTR(BP_VAR_R);
}
if (OP1_TYPE != IS_CONST &&
Z_TYPE_P(array_ptr) == IS_OBJECT && Z_OBJCE_P(array_ptr)->get_iterator) {
zend_class_entry *ce = Z_OBJCE_P(array_ptr);
zend_object_iterator *iter = ce->get_iterator(ce, array_ptr, 1);
zend_bool is_empty;
if (UNEXPECTED(!iter) || UNEXPECTED(EG(exception))) {
if (OP1_TYPE == IS_VAR) {
FREE_OP1_VAR_PTR();
} else {
FREE_OP1();
}
if (!EG(exception)) {
zend_throw_exception_ex(NULL, 0, "Object of type %s did not create an Iterator", ce->name->val);
}
zend_throw_exception_internal(NULL);
HANDLE_EXCEPTION();
}
iter->index = 0;
if (iter->funcs->rewind) {
iter->funcs->rewind(iter);
if (UNEXPECTED(EG(exception) != NULL)) {
OBJ_RELEASE(&iter->std);
if (OP1_TYPE == IS_VAR) {
FREE_OP1_VAR_PTR();
} else {
FREE_OP1();
}
HANDLE_EXCEPTION();
}
}
is_empty = iter->funcs->valid(iter) != SUCCESS;
if (UNEXPECTED(EG(exception) != NULL)) {
OBJ_RELEASE(&iter->std);
if (OP1_TYPE == IS_VAR) {
FREE_OP1_VAR_PTR();
} else {
FREE_OP1();
}
HANDLE_EXCEPTION();
}
iter->index = -1; /* will be set to 0 before using next handler */
ZVAL_OBJ(EX_VAR(opline->result.var), &iter->std);
Z_FE_ITER_P(EX_VAR(opline->result.var)) = (uint32_t)-1;
if (OP1_TYPE == IS_VAR) {
FREE_OP1_VAR_PTR();
} else {
FREE_OP1();
}
if (is_empty) {
ZEND_VM_JMP(OP_JMP_ADDR(opline, opline->op2));
} else {
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
} else if ((fe_ht = HASH_OF(array_ptr)) != NULL) {
HashPosition pos = 0;
Bucket *p;
if (EXPECTED(Z_TYPE_P(array_ptr) == IS_ARRAY)) {
if (OP1_TYPE == IS_VAR || OP1_TYPE == IS_CV) {
if (array_ptr == array_ref) {
ZVAL_NEW_REF(array_ref, array_ref);
@@ -4809,14 +4783,12 @@ ZEND_VM_HANDLER(125, ZEND_FE_RESET_RW, CONST|TMP|VAR|CV, ANY)
array_ptr = EX_VAR(opline->result.var);
ZVAL_COPY_VALUE(array_ptr, array_ref);
}
if (Z_TYPE_P(array_ptr) == IS_ARRAY) {
if (OP1_TYPE == IS_CONST) {
zval_copy_ctor_func(array_ptr);
} else {
SEPARATE_ARRAY(array_ptr);
}
fe_ht = Z_ARRVAL_P(array_ptr);
if (OP1_TYPE == IS_CONST) {
zval_copy_ctor_func(array_ptr);
} else {
SEPARATE_ARRAY(array_ptr);
}
fe_ht = Z_ARRVAL_P(array_ptr);
while (1) {
if (pos >= fe_ht->nNumUsed) {
FREE_OP1_VAR_PTR();
@@ -4824,12 +4796,9 @@ ZEND_VM_HANDLER(125, ZEND_FE_RESET_RW, CONST|TMP|VAR|CV, ANY)
ZEND_VM_JMP(OP_JMP_ADDR(opline, opline->op2));
}
p = fe_ht->arData + pos;
if ((Z_TYPE(p->val) != IS_UNDEF &&
(Z_TYPE(p->val) != IS_INDIRECT ||
Z_TYPE_P(Z_INDIRECT(p->val)) != IS_UNDEF)) &&
(Z_TYPE_P(array_ptr) != IS_OBJECT ||
!p->key ||
zend_check_property_access(Z_OBJ_P(array_ptr), p->key) == SUCCESS)) {
if (Z_TYPE(p->val) != IS_UNDEF &&
(Z_TYPE(p->val) != IS_INDIRECT ||
Z_TYPE_P(Z_INDIRECT(p->val)) != IS_UNDEF)) {
break;
}
pos++;
@@ -4840,6 +4809,102 @@ ZEND_VM_HANDLER(125, ZEND_FE_RESET_RW, CONST|TMP|VAR|CV, ANY)
FREE_OP1_VAR_PTR();
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
} else if (OP1_TYPE != IS_CONST && EXPECTED(Z_TYPE_P(array_ptr) == IS_OBJECT)) {
if (!Z_OBJCE_P(array_ptr)->get_iterator) {
if (OP1_TYPE == IS_VAR || OP1_TYPE == IS_CV) {
if (array_ptr == array_ref) {
ZVAL_NEW_REF(array_ref, array_ref);
array_ptr = Z_REFVAL_P(array_ref);
}
Z_ADDREF_P(array_ref);
ZVAL_COPY_VALUE(EX_VAR(opline->result.var), array_ref);
} else {
array_ptr = EX_VAR(opline->result.var);
ZVAL_COPY_VALUE(array_ptr, array_ref);
}
fe_ht = Z_OBJPROP_P(array_ptr);
while (1) {
if (pos >= fe_ht->nNumUsed) {
FREE_OP1_VAR_PTR();
Z_FE_ITER_P(EX_VAR(opline->result.var)) = (uint32_t)-1;
ZEND_VM_JMP(OP_JMP_ADDR(opline, opline->op2));
}
p = fe_ht->arData + pos;
if ((Z_TYPE(p->val) != IS_UNDEF &&
(Z_TYPE(p->val) != IS_INDIRECT ||
Z_TYPE_P(Z_INDIRECT(p->val)) != IS_UNDEF)) &&
(!p->key ||
zend_check_property_access(Z_OBJ_P(array_ptr), p->key) == SUCCESS)) {
break;
}
pos++;
}
fe_ht->nInternalPointer = pos;
Z_FE_ITER_P(EX_VAR(opline->result.var)) = zend_hash_iterator_add(fe_ht);
FREE_OP1_VAR_PTR();
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
} else {
zend_class_entry *ce = Z_OBJCE_P(array_ptr);
zend_object_iterator *iter = ce->get_iterator(ce, array_ptr, 1);
zend_bool is_empty;
if (UNEXPECTED(!iter) || UNEXPECTED(EG(exception))) {
if (OP1_TYPE == IS_VAR) {
FREE_OP1_VAR_PTR();
} else {
FREE_OP1();
}
if (!EG(exception)) {
zend_throw_exception_ex(NULL, 0, "Object of type %s did not create an Iterator", ce->name->val);
}
zend_throw_exception_internal(NULL);
HANDLE_EXCEPTION();
}
iter->index = 0;
if (iter->funcs->rewind) {
iter->funcs->rewind(iter);
if (UNEXPECTED(EG(exception) != NULL)) {
OBJ_RELEASE(&iter->std);
if (OP1_TYPE == IS_VAR) {
FREE_OP1_VAR_PTR();
} else {
FREE_OP1();
}
HANDLE_EXCEPTION();
}
}
is_empty = iter->funcs->valid(iter) != SUCCESS;
if (UNEXPECTED(EG(exception) != NULL)) {
OBJ_RELEASE(&iter->std);
if (OP1_TYPE == IS_VAR) {
FREE_OP1_VAR_PTR();
} else {
FREE_OP1();
}
HANDLE_EXCEPTION();
}
iter->index = -1; /* will be set to 0 before using next handler */
ZVAL_OBJ(EX_VAR(opline->result.var), &iter->std);
Z_FE_ITER_P(EX_VAR(opline->result.var)) = (uint32_t)-1;
if (OP1_TYPE == IS_VAR) {
FREE_OP1_VAR_PTR();
} else {
FREE_OP1();
}
if (is_empty) {
ZEND_VM_JMP(OP_JMP_ADDR(opline, opline->op2));
} else {
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
}
} else {
zend_error(E_WARNING, "Invalid argument supplied for foreach()");
ZVAL_UNDEF(EX_VAR(opline->result.var));
@@ -4906,7 +4971,7 @@ ZEND_VM_HANDLER(78, ZEND_FE_FETCH_R, VAR, ANY)
zend_object *zobj = Z_OBJ_P(array);
fe_ht = Z_OBJPROP_P(array);
pos = Z_FE_POS_P(EX_VAR(opline->op1.var));
pos = zend_hash_iterator_pos(Z_FE_ITER_P(EX_VAR(opline->op1.var)), fe_ht);
while (1) {
if (UNEXPECTED(pos >= fe_ht->nNumUsed)) {
/* reached end of iteration */
@@ -4945,7 +5010,24 @@ ZEND_VM_HANDLER(78, ZEND_FE_FETCH_R, VAR, ANY)
}
}
ZVAL_COPY(EX_VAR(opline->result.var), value);
Z_FE_POS_P(EX_VAR(opline->op1.var)) = pos + 1;
while (1) {
pos++;
if (pos >= fe_ht->nNumUsed) {
pos = INVALID_IDX;
break;
}
p = fe_ht->arData + pos;
if ((Z_TYPE(p->val) != IS_UNDEF &&
(Z_TYPE(p->val) != IS_INDIRECT ||
Z_TYPE_P(Z_INDIRECT(p->val)) != IS_UNDEF)) &&
(Z_TYPE_P(array) != IS_OBJECT ||
!p->key ||
zend_check_property_access(Z_OBJ_P(array), p->key) == SUCCESS)) {
break;
}
}
EG(ht_iterators)[Z_FE_ITER_P(EX_VAR(opline->op1.var))].pos =
fe_ht->nInternalPointer = pos;
ZEND_VM_INC_OPCODE();
ZEND_VM_NEXT_OPCODE();
} else {
@@ -5966,7 +6048,7 @@ ZEND_VM_HANDLER(149, ZEND_HANDLE_EXCEPTION, ANY, ANY)
} else if (brk_opline->opcode == ZEND_FE_FREE) {
if (!(brk_opline->extended_value & EXT_TYPE_FREE_ON_RETURN)) {
zval *var = EX_VAR(brk_opline->op1.var);
if (Z_FE_ITER_P(var) != (uint32_t)-1) {
if (Z_TYPE_P(var) != IS_ARRAY && Z_FE_ITER_P(var) != (uint32_t)-1) {
zend_hash_iterator_del(Z_FE_ITER_P(var));
}
zval_ptr_dtor_nogc(var);

File diff suppressed because it is too large Load Diff