Skip to content

Add support for non-scalar foreach keys #278

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

Closed
wants to merge 3 commits into from
Closed
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
52 changes: 52 additions & 0 deletions Zend/tests/generators/generator_with_nonscalar_keys.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
--TEST--
Generators can return non-scalar keys
--FILE--
<?php

function gen() {
yield [1, 2, 3] => [4, 5, 6];
yield (object) ['a' => 'b'] => (object) ['b' => 'a'];
yield 3.14 => 2.73;
yield false => true;
yield true => false;
yield null => null;
}

foreach (gen() as $k => $v) {
var_dump($k, $v);
}

?>
--EXPECT--
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
}
array(3) {
[0]=>
int(4)
[1]=>
int(5)
[2]=>
int(6)
}
object(stdClass)#3 (1) {
["a"]=>
string(1) "b"
}
object(stdClass)#4 (1) {
["b"]=>
string(1) "a"
}
float(3.14)
float(2.73)
bool(false)
bool(true)
bool(true)
bool(false)
NULL
NULL
34 changes: 34 additions & 0 deletions Zend/zend_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -1502,6 +1502,40 @@ ZEND_API int add_get_index_stringl(zval *arg, ulong index, const char *str, uint
}
/* }}} */

ZEND_API int array_set_zval_key(HashTable *ht, zval *key, zval *value) /* {{{ */
{
int result;

switch (Z_TYPE_P(key)) {
case IS_STRING:
result = zend_symtable_update(ht, Z_STRVAL_P(key), Z_STRLEN_P(key) + 1, &value, sizeof(zval *), NULL);
break;
case IS_NULL:
result = zend_symtable_update(ht, "", 1, &value, sizeof(zval *), NULL);
break;
case IS_RESOURCE:
zend_error(E_STRICT, "Resource ID#%ld used as offset, casting to integer (%ld)", Z_LVAL_P(key), Z_LVAL_P(key));
/* break missing intentionally */
case IS_BOOL:
case IS_LONG:
result = zend_hash_index_update(ht, Z_LVAL_P(key), &value, sizeof(zval *), NULL);
break;
case IS_DOUBLE:
result = zend_hash_index_update(ht, zend_dval_to_lval(Z_LVAL_P(key)), &value, sizeof(zval *), NULL);
break;
default:
zend_error(E_WARNING, "Illegal offset type");
result = FAILURE;
}

if (result == SUCCESS) {
Z_ADDREF_P(value);
}

return result;
}
/* }}} */

ZEND_API int add_property_long_ex(zval *arg, const char *key, uint key_len, long n TSRMLS_DC) /* {{{ */
{
zval *tmp;
Expand Down
2 changes: 2 additions & 0 deletions Zend/zend_API.h
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,8 @@ ZEND_API int add_get_index_double(zval *arg, ulong idx, double d, void **dest);
ZEND_API int add_get_index_string(zval *arg, ulong idx, const char *str, void **dest, int duplicate);
ZEND_API int add_get_index_stringl(zval *arg, ulong idx, const char *str, uint length, void **dest, int duplicate);

ZEND_API int array_set_zval_key(HashTable *ht, zval *key, zval *value);

ZEND_API int add_property_long_ex(zval *arg, const char *key, uint key_len, long l TSRMLS_DC);
ZEND_API int add_property_null_ex(zval *arg, const char *key, uint key_len TSRMLS_DC);
ZEND_API int add_property_bool_ex(zval *arg, const char *key, uint key_len, int b TSRMLS_DC);
Expand Down
2 changes: 1 addition & 1 deletion Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -6360,7 +6360,7 @@ void zend_do_foreach_cont(znode *foreach_token, const znode *open_brackets_token
znode key_node;

opline = &CG(active_op_array)->opcodes[as_token->u.op.opline_num+1];
opline->result_type = IS_TMP_VAR;
opline->result_type = IS_VAR;
opline->result.opline_num = get_temporary_variable(CG(active_op_array));
GET_NODE(&key_node, opline->result);

Expand Down
27 changes: 8 additions & 19 deletions Zend/zend_generators.c
Original file line number Diff line number Diff line change
Expand Up @@ -755,31 +755,20 @@ static void zend_generator_iterator_get_data(zend_object_iterator *iterator, zva
}
/* }}} */

static int zend_generator_iterator_get_key(zend_object_iterator *iterator, char **str_key, uint *str_key_len, ulong *int_key TSRMLS_DC) /* {{{ */
static zval *zend_generator_iterator_get_key(zend_object_iterator *iterator TSRMLS_DC) /* {{{ */
{
zend_generator *generator = (zend_generator *) iterator->data;

zend_generator_ensure_initialized(generator TSRMLS_CC);

if (!generator->key) {
return HASH_KEY_NON_EXISTANT;
}

if (Z_TYPE_P(generator->key) == IS_LONG) {
*int_key = Z_LVAL_P(generator->key);
return HASH_KEY_IS_LONG;
}

if (Z_TYPE_P(generator->key) == IS_STRING) {
*str_key = estrndup(Z_STRVAL_P(generator->key), Z_STRLEN_P(generator->key));
*str_key_len = Z_STRLEN_P(generator->key) + 1;
return HASH_KEY_IS_STRING;
if (generator->key) {
Z_ADDREF_P(generator->key);
return generator->key;
} else {
zval *key;
MAKE_STD_ZVAL(key);
return key;
}

/* Waiting for Etienne's patch to allow arbitrary zval keys. Until then
* error out on non-int and non-string keys. */
zend_error_noreturn(E_ERROR, "Currently only int and string keys can be yielded");
return HASH_KEY_NON_EXISTANT; /* Nerver reached */
}
/* }}} */

Expand Down
21 changes: 21 additions & 0 deletions Zend/zend_hash.c
Original file line number Diff line number Diff line change
Expand Up @@ -1171,6 +1171,27 @@ ZEND_API int zend_hash_get_current_key_ex(const HashTable *ht, char **str_index,
return HASH_KEY_NON_EXISTANT;
}

ZEND_API zval *zend_hash_get_current_key_zval_ex(const HashTable *ht, HashPosition *pos) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

currently, zend_hash.c didn't depend on zval struct, will it better move the function to zend_API ?

Bucket *p;
zval *key;

IS_CONSISTENT(ht);

p = pos ? (*pos) : ht->pInternalPointer;

ALLOC_INIT_ZVAL(key);
if (!p) {
Z_TYPE_P(key) = IS_NULL;
} else if (p->nKeyLength) {
Z_TYPE_P(key) = IS_STRING;
Z_STRVAL_P(key) = estrndup(p->arKey, p->nKeyLength - 1);
Z_STRLEN_P(key) = p->nKeyLength - 1;
} else {
Z_TYPE_P(key) = IS_LONG;
Z_LVAL_P(key) = p->h;
}
return key;
}

ZEND_API int zend_hash_get_current_key_type_ex(HashTable *ht, HashPosition *pos)
{
Expand Down
3 changes: 3 additions & 0 deletions Zend/zend_hash.h
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ ZEND_API ulong zend_hash_next_free_element(const HashTable *ht);
ZEND_API int zend_hash_move_forward_ex(HashTable *ht, HashPosition *pos);
ZEND_API int zend_hash_move_backwards_ex(HashTable *ht, HashPosition *pos);
ZEND_API int zend_hash_get_current_key_ex(const HashTable *ht, char **str_index, uint *str_length, ulong *num_index, zend_bool duplicate, HashPosition *pos);
ZEND_API struct _zval_struct *zend_hash_get_current_key_zval_ex(const HashTable *ht, HashPosition *pos);
ZEND_API int zend_hash_get_current_key_type_ex(HashTable *ht, HashPosition *pos);
ZEND_API int zend_hash_get_current_data_ex(HashTable *ht, void **pData, HashPosition *pos);
ZEND_API void zend_hash_internal_pointer_reset_ex(HashTable *ht, HashPosition *pos);
Expand All @@ -199,6 +200,8 @@ ZEND_API int zend_hash_set_pointer(HashTable *ht, const HashPointer *ptr);
zend_hash_move_backwards_ex(ht, NULL)
#define zend_hash_get_current_key(ht, str_index, num_index, duplicate) \
zend_hash_get_current_key_ex(ht, str_index, NULL, num_index, duplicate, NULL)
#define zend_hash_get_current_key_zval(ht) \
zend_hash_get_current_key_zval_ex(ht, NULL)
#define zend_hash_get_current_key_type(ht) \
zend_hash_get_current_key_type_ex(ht, NULL)
#define zend_hash_get_current_data(ht, pData) \
Expand Down
40 changes: 6 additions & 34 deletions Zend/zend_interfaces.c
Original file line number Diff line number Diff line change
Expand Up @@ -195,50 +195,22 @@ static int zend_user_it_get_current_key_default(zend_object_iterator *_iter, cha
/* }}} */

/* {{{ zend_user_it_get_current_key */
ZEND_API int zend_user_it_get_current_key(zend_object_iterator *_iter, char **str_key, uint *str_key_len, ulong *int_key TSRMLS_DC)
ZEND_API zval *zend_user_it_get_current_key(zend_object_iterator *_iter TSRMLS_DC)
{
zend_user_iterator *iter = (zend_user_iterator*)_iter;
zval *object = (zval*)iter->it.data;
zval *retval;

zend_call_method_with_0_params(&object, iter->ce, &iter->ce->iterator_funcs.zf_key, "key", &retval);

if (!retval) {
*int_key = 0;
if (!EG(exception))
{
zend_error(E_WARNING, "Nothing returned from %s::key()", iter->ce->name);
}
return HASH_KEY_IS_LONG;
if (!retval && !EG(exception)) {
zend_error(E_WARNING, "Nothing returned from %s::key()", iter->ce->name);
MAKE_STD_ZVAL(retval);
ZVAL_LONG(retval, 0);
}
switch (Z_TYPE_P(retval)) {
default:
zend_error(E_WARNING, "Illegal type returned from %s::key()", iter->ce->name);
case IS_NULL:
*int_key = 0;
zval_ptr_dtor(&retval);
return HASH_KEY_IS_LONG;

case IS_STRING:
*str_key = estrndup(Z_STRVAL_P(retval), Z_STRLEN_P(retval));
*str_key_len = Z_STRLEN_P(retval)+1;
zval_ptr_dtor(&retval);
return HASH_KEY_IS_STRING;

case IS_DOUBLE:
*int_key = (long)Z_DVAL_P(retval);
zval_ptr_dtor(&retval);
return HASH_KEY_IS_LONG;

case IS_RESOURCE:
case IS_BOOL:
case IS_LONG:
*int_key = (long)Z_LVAL_P(retval);
zval_ptr_dtor(&retval);
return HASH_KEY_IS_LONG;
}
return retval;
}
/* }}} */

/* {{{ zend_user_it_move_forward */
ZEND_API void zend_user_it_move_forward(zend_object_iterator *_iter TSRMLS_DC)
Expand Down
2 changes: 1 addition & 1 deletion Zend/zend_interfaces.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ ZEND_API zval* zend_call_method(zval **object_pp, zend_class_entry *obj_ce, zend

ZEND_API void zend_user_it_rewind(zend_object_iterator *_iter TSRMLS_DC);
ZEND_API int zend_user_it_valid(zend_object_iterator *_iter TSRMLS_DC);
ZEND_API int zend_user_it_get_current_key(zend_object_iterator *_iter, char **str_key, uint *str_key_len, ulong *int_key TSRMLS_DC);
ZEND_API zval *zend_user_it_get_current_key(zend_object_iterator *_iter TSRMLS_DC);
ZEND_API void zend_user_it_get_current_data(zend_object_iterator *_iter, zval ***data TSRMLS_DC);
ZEND_API void zend_user_it_move_forward(zend_object_iterator *_iter TSRMLS_DC);
ZEND_API void zend_user_it_invalidate_current(zend_object_iterator *_iter TSRMLS_DC);
Expand Down
4 changes: 2 additions & 2 deletions Zend/zend_iterators.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ typedef struct _zend_object_iterator_funcs {
/* fetch the item data for the current element */
void (*get_current_data)(zend_object_iterator *iter, zval ***data TSRMLS_DC);

/* fetch the key for the current element (return HASH_KEY_IS_STRING or HASH_KEY_IS_LONG) (optional, may be NULL) */
int (*get_current_key)(zend_object_iterator *iter, char **str_key, uint *str_key_len, ulong *int_key TSRMLS_DC);
/* fetch the key for the current element */
zval *(*get_current_key)(zend_object_iterator *iter TSRMLS_DC);

/* step forwards to next element */
void (*move_forward)(zend_object_iterator *iter TSRMLS_DC);
Expand Down
66 changes: 34 additions & 32 deletions Zend/zend_vm_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -4249,13 +4249,10 @@ ZEND_VM_HANDLER(78, ZEND_FE_FETCH, VAR, ANY)
zend_free_op free_op1;
zval *array = EX_T(opline->op1.var).fe.ptr;
zval **value;
char *str_key;
uint str_key_len;
ulong int_key;
HashTable *fe_ht;
zend_object_iterator *iter = NULL;
int key_type = 0;
zend_bool use_key = (zend_bool)(opline->extended_value & ZEND_FE_FETCH_WITH_KEY);
zval *key = NULL;

SAVE_OPLINE();

Expand All @@ -4266,8 +4263,11 @@ ZEND_VM_HANDLER(78, ZEND_FE_FETCH, VAR, ANY)
ZEND_VM_JMP(EX(op_array)->opcodes+opline->op2.opline_num);

case ZEND_ITER_PLAIN_OBJECT: {
const char *class_name, *prop_name;
zend_object *zobj = zend_objects_get_address(array TSRMLS_CC);
int key_type;
char *str_key;
zend_uint str_key_len;
zend_ulong int_key;

fe_ht = Z_OBJPROP_P(array);
zend_hash_set_pointer(fe_ht, &EX_T(opline->op1.var).fe.fe_pos);
Expand All @@ -4281,13 +4281,30 @@ ZEND_VM_HANDLER(78, ZEND_FE_FETCH, VAR, ANY)
zend_hash_move_forward(fe_ht);
} while (key_type == HASH_KEY_NON_EXISTANT ||
(key_type != HASH_KEY_IS_LONG &&
zend_check_property_access(zobj, str_key, str_key_len-1 TSRMLS_CC) != SUCCESS));
zend_hash_get_pointer(fe_ht, &EX_T(opline->op1.var).fe.fe_pos);
if (use_key && key_type != HASH_KEY_IS_LONG) {
zend_unmangle_property_name_ex(str_key, str_key_len-1, &class_name, &prop_name, &str_key_len);
str_key = estrndup(prop_name, str_key_len);
str_key_len++;
zend_check_property_access(zobj, str_key, str_key_len - 1 TSRMLS_CC) != SUCCESS));

if (use_key) {
const char *class_name, *prop_name;
int prop_name_len;

ALLOC_INIT_ZVAL(key);
switch (key_type) {
case HASH_KEY_IS_STRING:
zend_unmangle_property_name_ex(
str_key, str_key_len - 1, &class_name, &prop_name, &prop_name_len
);
ZVAL_STRINGL(key, prop_name, prop_name_len, 1);
break;
case HASH_KEY_IS_LONG:
ZVAL_LONG(key, int_key);
break;
case HASH_KEY_NON_EXISTANT:
ZVAL_NULL(key);
break;
}
}

zend_hash_get_pointer(fe_ht, &EX_T(opline->op1.var).fe.fe_pos);
break;
}

Expand All @@ -4299,7 +4316,7 @@ ZEND_VM_HANDLER(78, ZEND_FE_FETCH, VAR, ANY)
ZEND_VM_JMP(EX(op_array)->opcodes+opline->op2.opline_num);
}
if (use_key) {
key_type = zend_hash_get_current_key_ex(fe_ht, &str_key, &str_key_len, &int_key, 1, NULL);
key = zend_hash_get_current_key_zval(fe_ht);
}
zend_hash_move_forward(fe_ht);
zend_hash_get_pointer(fe_ht, &EX_T(opline->op1.var).fe.fe_pos);
Expand Down Expand Up @@ -4336,14 +4353,14 @@ ZEND_VM_HANDLER(78, ZEND_FE_FETCH, VAR, ANY)
}
if (use_key) {
if (iter->funcs->get_current_key) {
key_type = iter->funcs->get_current_key(iter, &str_key, &str_key_len, &int_key TSRMLS_CC);
key = iter->funcs->get_current_key(iter TSRMLS_CC);
if (UNEXPECTED(EG(exception) != NULL)) {
zval_ptr_dtor(&array);
HANDLE_EXCEPTION();
}
} else {
key_type = HASH_KEY_IS_LONG;
int_key = iter->index;
MAKE_STD_ZVAL(key);
ZVAL_LONG(key, iter->index);
}
}
break;
Expand All @@ -4360,23 +4377,8 @@ ZEND_VM_HANDLER(78, ZEND_FE_FETCH, VAR, ANY)
}

if (use_key) {
zval *key = &EX_T((opline+1)->result.var).tmp_var;

switch (key_type) {
case HASH_KEY_IS_STRING:
Z_STRVAL_P(key) = (char*)str_key;
Z_STRLEN_P(key) = str_key_len-1;
Z_TYPE_P(key) = IS_STRING;
break;
case HASH_KEY_IS_LONG:
Z_LVAL_P(key) = int_key;
Z_TYPE_P(key) = IS_LONG;
break;
default:
case HASH_KEY_NON_EXISTANT:
ZVAL_NULL(key);
break;
}
/*PZVAL_LOCK(key);*/
AI_SET_PTR(&EX_T((opline+1)->result.var), key);
}

CHECK_EXCEPTION();
Expand Down
Loading