From cede13ccfe0c486591fa84764271ac1b8cb90d0b Mon Sep 17 00:00:00 2001 From: Andrea Faulds Date: Sun, 10 Jan 2016 20:46:44 +0000 Subject: [PATCH 01/13] list() with keys (no foreach or tests) --- Zend/zend_compile.c | 33 +++++++++++++++--- Zend/zend_language_parser.y | 6 +++- Zend/zend_vm_def.h | 23 ++++++++++--- Zend/zend_vm_execute.h | 69 +++++++++++++++++++++++++++++-------- 4 files changed, 105 insertions(+), 26 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 9b79202014dff..de17463058f7e 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -2713,19 +2713,42 @@ static void zend_compile_list_assign(znode *result, zend_ast *ast, znode *expr_n { zend_ast_list *list = zend_ast_get_list(ast); uint32_t i; + zend_ulong next_index = 0; zend_bool has_elems = 0; for (i = 0; i < list->children; ++i) { - zend_ast *var_ast = list->child[i]; + zend_ast *pair_ast = list->child[i]; + zend_ast *var_ast; + zend_ast *key_ast; znode fetch_result, dim_node; - if (var_ast == NULL) { + if (pair_ast == NULL) { + next_index++; continue; } - has_elems = 1; - dim_node.op_type = IS_CONST; - ZVAL_LONG(&dim_node.u.constant, i); + var_ast = pair_ast->child[0]; + key_ast = pair_ast->child[1]; + + if (key_ast) { + zend_compile_expr(&dim_node, key_ast); + zend_handle_numeric_op(&dim_node); + + if (Z_TYPE(dim_node.u.constant) != IS_LONG && Z_TYPE(dim_node.u.constant) != IS_STRING) { + zend_error_noreturn(E_COMPILE_ERROR, "Key must be an integer or string literal"); + } + + if (Z_TYPE(dim_node.u.constant) == IS_LONG) { + next_index = Z_LVAL(dim_node.u.constant); + } + } else { + dim_node.op_type = IS_CONST; + ZVAL_LONG(&dim_node.u.constant, next_index); + + next_index++; + } + + has_elems = 1; if (expr_node->op_type == IS_CONST) { Z_TRY_ADDREF(expr_node->u.constant); diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 754c50215dac5..2f64ae55f24f1 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -1177,8 +1177,12 @@ assignment_list: ; assignment_list_element: - variable { $$ = $1; } + variable { $$ = zend_ast_create(ZEND_AST_ARRAY_ELEM, $1, NULL); } + | scalar T_DOUBLE_ARROW variable + { $$ = zend_ast_create(ZEND_AST_ARRAY_ELEM, $3, $1); } | T_LIST '(' assignment_list ')' { $$ = $3; } + | scalar T_DOUBLE_ARROW T_LIST '(' assignment_list ')' + { $$ = zend_ast_create(ZEND_AST_ARRAY_ELEM, $5, $1); } | /* empty */ { $$ = NULL; } ; diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index af9daa89b1d19..98e477e8c4464 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -2073,13 +2073,26 @@ ZEND_VM_HANDLER(98, ZEND_FETCH_LIST, CONST|TMPVAR|CV, CONST) ZEND_VM_C_LABEL(try_fetch_list): if (EXPECTED(Z_TYPE_P(container) == IS_ARRAY)) { - zval *value = zend_hash_index_find(Z_ARRVAL_P(container), Z_LVAL_P(EX_CONSTANT(opline->op2))); + zval *value; + + if (Z_TYPE_P(EX_CONSTANT(opline->op2)) == IS_LONG) { + value = zend_hash_index_find(Z_ARRVAL_P(container), Z_LVAL_P(EX_CONSTANT(opline->op2))); - if (UNEXPECTED(value == NULL)) { - zend_error(E_NOTICE,"Undefined offset: " ZEND_ULONG_FMT, Z_LVAL_P(EX_CONSTANT(opline->op2))); - ZVAL_NULL(EX_VAR(opline->result.var)); + if (UNEXPECTED(value == NULL)) { + zend_error(E_NOTICE,"Undefined offset: " ZEND_ULONG_FMT, Z_LVAL_P(EX_CONSTANT(opline->op2))); + ZVAL_NULL(EX_VAR(opline->result.var)); + } else { + ZVAL_COPY(EX_VAR(opline->result.var), value); + } } else { - ZVAL_COPY(EX_VAR(opline->result.var), value); + value = zend_hash_find(Z_ARRVAL_P(container), Z_STR_P(EX_CONSTANT(opline->op2))); + + if (UNEXPECTED(value == NULL)) { + zend_error(E_NOTICE, "Undefined index: %s", Z_STRVAL_P(EX_CONSTANT(opline->op2))); + ZVAL_NULL(EX_VAR(opline->result.var)); + } else { + ZVAL_COPY(EX_VAR(opline->result.var), value); + } } } else if (OP1_TYPE != IS_CONST && UNEXPECTED(Z_TYPE_P(container) == IS_OBJECT) && diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index a4edfd0f98e89..9a0bf762fc3bc 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -5753,13 +5753,26 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_LIST_SPEC_CONST_CONST_HA try_fetch_list: if (EXPECTED(Z_TYPE_P(container) == IS_ARRAY)) { - zval *value = zend_hash_index_find(Z_ARRVAL_P(container), Z_LVAL_P(EX_CONSTANT(opline->op2))); + zval *value; - if (UNEXPECTED(value == NULL)) { - zend_error(E_NOTICE,"Undefined offset: " ZEND_ULONG_FMT, Z_LVAL_P(EX_CONSTANT(opline->op2))); - ZVAL_NULL(EX_VAR(opline->result.var)); + if (Z_TYPE_P(EX_CONSTANT(opline->op2)) == IS_LONG) { + value = zend_hash_index_find(Z_ARRVAL_P(container), Z_LVAL_P(EX_CONSTANT(opline->op2))); + + if (UNEXPECTED(value == NULL)) { + zend_error(E_NOTICE,"Undefined offset: " ZEND_ULONG_FMT, Z_LVAL_P(EX_CONSTANT(opline->op2))); + ZVAL_NULL(EX_VAR(opline->result.var)); + } else { + ZVAL_COPY(EX_VAR(opline->result.var), value); + } } else { - ZVAL_COPY(EX_VAR(opline->result.var), value); + value = zend_hash_find(Z_ARRVAL_P(container), Z_STR_P(EX_CONSTANT(opline->op2))); + + if (UNEXPECTED(value == NULL)) { + zend_error(E_NOTICE, "Undefined index: %s", Z_STRVAL_P(EX_CONSTANT(opline->op2))); + ZVAL_NULL(EX_VAR(opline->result.var)); + } else { + ZVAL_COPY(EX_VAR(opline->result.var), value); + } } } else if (IS_CONST != IS_CONST && UNEXPECTED(Z_TYPE_P(container) == IS_OBJECT) && @@ -37866,13 +37879,26 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_LIST_SPEC_CV_CONST_HANDL try_fetch_list: if (EXPECTED(Z_TYPE_P(container) == IS_ARRAY)) { - zval *value = zend_hash_index_find(Z_ARRVAL_P(container), Z_LVAL_P(EX_CONSTANT(opline->op2))); + zval *value; - if (UNEXPECTED(value == NULL)) { - zend_error(E_NOTICE,"Undefined offset: " ZEND_ULONG_FMT, Z_LVAL_P(EX_CONSTANT(opline->op2))); - ZVAL_NULL(EX_VAR(opline->result.var)); + if (Z_TYPE_P(EX_CONSTANT(opline->op2)) == IS_LONG) { + value = zend_hash_index_find(Z_ARRVAL_P(container), Z_LVAL_P(EX_CONSTANT(opline->op2))); + + if (UNEXPECTED(value == NULL)) { + zend_error(E_NOTICE,"Undefined offset: " ZEND_ULONG_FMT, Z_LVAL_P(EX_CONSTANT(opline->op2))); + ZVAL_NULL(EX_VAR(opline->result.var)); + } else { + ZVAL_COPY(EX_VAR(opline->result.var), value); + } } else { - ZVAL_COPY(EX_VAR(opline->result.var), value); + value = zend_hash_find(Z_ARRVAL_P(container), Z_STR_P(EX_CONSTANT(opline->op2))); + + if (UNEXPECTED(value == NULL)) { + zend_error(E_NOTICE, "Undefined index: %s", Z_STRVAL_P(EX_CONSTANT(opline->op2))); + ZVAL_NULL(EX_VAR(opline->result.var)); + } else { + ZVAL_COPY(EX_VAR(opline->result.var), value); + } } } else if (IS_CV != IS_CONST && UNEXPECTED(Z_TYPE_P(container) == IS_OBJECT) && @@ -51017,13 +51043,26 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_LIST_SPEC_TMPVAR_CONST_H try_fetch_list: if (EXPECTED(Z_TYPE_P(container) == IS_ARRAY)) { - zval *value = zend_hash_index_find(Z_ARRVAL_P(container), Z_LVAL_P(EX_CONSTANT(opline->op2))); + zval *value; - if (UNEXPECTED(value == NULL)) { - zend_error(E_NOTICE,"Undefined offset: " ZEND_ULONG_FMT, Z_LVAL_P(EX_CONSTANT(opline->op2))); - ZVAL_NULL(EX_VAR(opline->result.var)); + if (Z_TYPE_P(EX_CONSTANT(opline->op2)) == IS_LONG) { + value = zend_hash_index_find(Z_ARRVAL_P(container), Z_LVAL_P(EX_CONSTANT(opline->op2))); + + if (UNEXPECTED(value == NULL)) { + zend_error(E_NOTICE,"Undefined offset: " ZEND_ULONG_FMT, Z_LVAL_P(EX_CONSTANT(opline->op2))); + ZVAL_NULL(EX_VAR(opline->result.var)); + } else { + ZVAL_COPY(EX_VAR(opline->result.var), value); + } } else { - ZVAL_COPY(EX_VAR(opline->result.var), value); + value = zend_hash_find(Z_ARRVAL_P(container), Z_STR_P(EX_CONSTANT(opline->op2))); + + if (UNEXPECTED(value == NULL)) { + zend_error(E_NOTICE, "Undefined index: %s", Z_STRVAL_P(EX_CONSTANT(opline->op2))); + ZVAL_NULL(EX_VAR(opline->result.var)); + } else { + ZVAL_COPY(EX_VAR(opline->result.var), value); + } } } else if ((IS_TMP_VAR|IS_VAR) != IS_CONST && UNEXPECTED(Z_TYPE_P(container) == IS_OBJECT) && From e572d2d0ada6a64b36a2c6f5e8cb57439f51b55e Mon Sep 17 00:00:00 2001 From: Andrea Faulds Date: Sat, 16 Jan 2016 21:10:33 +0000 Subject: [PATCH 02/13] Disallow mixing keyed and unkeyed list() elements --- Zend/zend_compile.c | 71 ++++++++++++++++++++++--------------- Zend/zend_language_parser.y | 37 ++++++++++++++----- 2 files changed, 71 insertions(+), 37 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index de17463058f7e..6da2900b31df3 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -2709,46 +2709,53 @@ void zend_compile_static_prop(znode *result, zend_ast *ast, uint32_t type, int d } /* }}} */ -static void zend_compile_list_assign(znode *result, zend_ast *ast, znode *expr_node) /* {{{ */ +static void zend_compile_unkeyed_list_assign(zend_ast_list *list, znode *expr_node) /* {{{ */ { - zend_ast_list *list = zend_ast_get_list(ast); uint32_t i; - zend_ulong next_index = 0; zend_bool has_elems = 0; for (i = 0; i < list->children; ++i) { - zend_ast *pair_ast = list->child[i]; - zend_ast *var_ast; - zend_ast *key_ast; + zend_ast *var_ast = list->child[i]; znode fetch_result, dim_node; - if (pair_ast == NULL) { - next_index++; + if (var_ast == NULL) { continue; } + has_elems = 1; - var_ast = pair_ast->child[0]; - key_ast = pair_ast->child[1]; + dim_node.op_type = IS_CONST; + ZVAL_LONG(&dim_node.u.constant, i); - if (key_ast) { - zend_compile_expr(&dim_node, key_ast); - zend_handle_numeric_op(&dim_node); + if (expr_node->op_type == IS_CONST) { + Z_TRY_ADDREF(expr_node->u.constant); + } - if (Z_TYPE(dim_node.u.constant) != IS_LONG && Z_TYPE(dim_node.u.constant) != IS_STRING) { - zend_error_noreturn(E_COMPILE_ERROR, "Key must be an integer or string literal"); - } - - if (Z_TYPE(dim_node.u.constant) == IS_LONG) { - next_index = Z_LVAL(dim_node.u.constant); - } - } else { - dim_node.op_type = IS_CONST; - ZVAL_LONG(&dim_node.u.constant, next_index); + zend_emit_op(&fetch_result, ZEND_FETCH_LIST, expr_node, &dim_node); + zend_emit_assign_znode(var_ast, &fetch_result); + } - next_index++; - } + if (!has_elems) { + zend_error_noreturn(E_COMPILE_ERROR, "Cannot use empty list"); + } +} +/* }}} */ - has_elems = 1; +static void zend_compile_keyed_list_assign(zend_ast_list *list, znode *expr_node) /* {{{ */ +{ + uint32_t i; + + for (i = 0; i < list->children; ++i) { + zend_ast *pair_ast = list->child[i]; + zend_ast *var_ast = pair_ast->child[0]; + zend_ast *key_ast = pair_ast->child[1]; + znode fetch_result, dim_node; + + zend_compile_expr(&dim_node, key_ast); + zend_handle_numeric_op(&dim_node); + + if (Z_TYPE(dim_node.u.constant) != IS_LONG && Z_TYPE(dim_node.u.constant) != IS_STRING) { + zend_error_noreturn(E_COMPILE_ERROR, "Key must be an integer or string literal"); + } if (expr_node->op_type == IS_CONST) { Z_TRY_ADDREF(expr_node->u.constant); @@ -2757,9 +2764,17 @@ static void zend_compile_list_assign(znode *result, zend_ast *ast, znode *expr_n zend_emit_op(&fetch_result, ZEND_FETCH_LIST, expr_node, &dim_node); zend_emit_assign_znode(var_ast, &fetch_result); } +} +/* }}} */ - if (!has_elems) { - zend_error_noreturn(E_COMPILE_ERROR, "Cannot use empty list"); +static void zend_compile_list_assign(znode *result, zend_ast *ast, znode *expr_node) /* {{{ */ +{ + zend_ast_list *list = zend_ast_get_list(ast); + + if (list->children > 0 && list->child[0] != NULL && list->child[0]->kind == ZEND_AST_ARRAY_ELEM) { + zend_compile_keyed_list_assign(list, expr_node); + } else { + zend_compile_unkeyed_list_assign(list, expr_node); } *result = *expr_node; diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 2f64ae55f24f1..e1def1fe1c7f5 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -241,7 +241,8 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type exit_expr scalar backticks_expr lexical_var function_call member_name property_name %type variable_class_name dereferencable_scalar constant dereferencable %type callable_expr callable_variable static_member new_variable -%type assignment_list_element array_pair encaps_var encaps_var_offset isset_variables +%type unkeyed_assignment_list_element keyed_assignment_list_element array_pair +%type encaps_var encaps_var_offset isset_variables %type top_statement_list use_declarations const_list inner_statement_list if_stmt %type alt_if_stmt for_exprs switch_case_list global_var_list static_var_list %type echo_expr_list unset_variables catch_list parameter_list class_statement_list @@ -250,7 +251,8 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type class_const_list class_const_decl name_list trait_adaptations method_body non_empty_for_exprs %type ctor_arguments alt_if_stmt_without_else trait_adaptation_list lexical_vars %type lexical_var_list encaps_list array_pair_list non_empty_array_pair_list -%type assignment_list isset_variable type return_type +%type assignment_list unkeyed_assignment_list keyed_assignment_list +%type isset_variable type return_type %type identifier %type returns_ref function is_reference is_variadic variable_modifiers @@ -1170,20 +1172,37 @@ property_name: ; assignment_list: - assignment_list ',' assignment_list_element + unkeyed_assignment_list + { $$ = $1; } + | keyed_assignment_list + { $$ = $1; } +; + +unkeyed_assignment_list: + unkeyed_assignment_list ',' unkeyed_assignment_list_element { $$ = zend_ast_list_add($1, $3); } - | assignment_list_element + | unkeyed_assignment_list_element { $$ = zend_ast_create_list(1, ZEND_AST_LIST, $1); } ; -assignment_list_element: - variable { $$ = zend_ast_create(ZEND_AST_ARRAY_ELEM, $1, NULL); } - | scalar T_DOUBLE_ARROW variable - { $$ = zend_ast_create(ZEND_AST_ARRAY_ELEM, $3, $1); } +unkeyed_assignment_list_element: + variable { $$ = $1; } | T_LIST '(' assignment_list ')' { $$ = $3; } + | /* empty */ { $$ = NULL; } +; + +keyed_assignment_list: + keyed_assignment_list ',' keyed_assignment_list_element + { $$ = zend_ast_list_add($1, $3); } + | keyed_assignment_list_element + { $$ = zend_ast_create_list(1, ZEND_AST_LIST, $1); } +; + +keyed_assignment_list_element: + scalar T_DOUBLE_ARROW variable + { $$ = zend_ast_create(ZEND_AST_ARRAY_ELEM, $3, $1); } | scalar T_DOUBLE_ARROW T_LIST '(' assignment_list ')' { $$ = zend_ast_create(ZEND_AST_ARRAY_ELEM, $5, $1); } - | /* empty */ { $$ = NULL; } ; From 0085884a6176c3a981b53131fbb4fa0c44db2670 Mon Sep 17 00:00:00 2001 From: Andrea Faulds Date: Sat, 16 Jan 2016 22:24:23 +0000 Subject: [PATCH 03/13] Handle non-integer/string opcodes --- Zend/zend_compile.c | 4 - Zend/zend_vm_def.h | 51 ++++- Zend/zend_vm_execute.h | 427 ++++++++++++++++++++++++++++++++++++++--- Zend/zend_vm_opcodes.c | 2 +- 4 files changed, 441 insertions(+), 43 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 6da2900b31df3..0461e643913e1 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -2753,10 +2753,6 @@ static void zend_compile_keyed_list_assign(zend_ast_list *list, znode *expr_node zend_compile_expr(&dim_node, key_ast); zend_handle_numeric_op(&dim_node); - if (Z_TYPE(dim_node.u.constant) != IS_LONG && Z_TYPE(dim_node.u.constant) != IS_STRING) { - zend_error_noreturn(E_COMPILE_ERROR, "Key must be an integer or string literal"); - } - if (expr_node->op_type == IS_CONST) { Z_TRY_ADDREF(expr_node->u.constant); } diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 98e477e8c4464..f38f37a6dc85a 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -2062,11 +2062,13 @@ ZEND_VM_HANDLER(97, ZEND_FETCH_OBJ_UNSET, VAR|UNUSED|THIS|CV, CONST|TMPVAR|CV) ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } -ZEND_VM_HANDLER(98, ZEND_FETCH_LIST, CONST|TMPVAR|CV, CONST) +ZEND_VM_HANDLER(98, ZEND_FETCH_LIST, CONST|TMPVAR|CV, CONST|TMPVAR) { USE_OPLINE zend_free_op free_op1; + zend_free_op free_op2; zval *container; + zval *offset = GET_OP2_ZVAL_PTR(BP_VAR_R); SAVE_OPLINE(); container = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R); @@ -2074,31 +2076,61 @@ ZEND_VM_HANDLER(98, ZEND_FETCH_LIST, CONST|TMPVAR|CV, CONST) ZEND_VM_C_LABEL(try_fetch_list): if (EXPECTED(Z_TYPE_P(container) == IS_ARRAY)) { zval *value; - - if (Z_TYPE_P(EX_CONSTANT(opline->op2)) == IS_LONG) { - value = zend_hash_index_find(Z_ARRVAL_P(container), Z_LVAL_P(EX_CONSTANT(opline->op2))); + zend_string *str; + zend_ulong hval; + +ZEND_VM_C_LABEL(assign_again_list): + if (EXPECTED(Z_TYPE_P(offset) == IS_LONG)) { + hval = Z_LVAL_P(offset); +ZEND_VM_C_LABEL(num_index_list): + value = zend_hash_index_find(Z_ARRVAL_P(container), hval); if (UNEXPECTED(value == NULL)) { - zend_error(E_NOTICE,"Undefined offset: " ZEND_ULONG_FMT, Z_LVAL_P(EX_CONSTANT(opline->op2))); + zend_error(E_NOTICE, "Undefined offset: " ZEND_ULONG_FMT, hval); ZVAL_NULL(EX_VAR(opline->result.var)); } else { ZVAL_COPY(EX_VAR(opline->result.var), value); } - } else { - value = zend_hash_find(Z_ARRVAL_P(container), Z_STR_P(EX_CONSTANT(opline->op2))); + } else if (EXPECTED(Z_TYPE_P(offset) == IS_STRING)) { + str = Z_STR_P(offset); +ZEND_VM_C_LABEL(str_index_list): + value = zend_hash_find(Z_ARRVAL_P(container), str); if (UNEXPECTED(value == NULL)) { - zend_error(E_NOTICE, "Undefined index: %s", Z_STRVAL_P(EX_CONSTANT(opline->op2))); + zend_error(E_NOTICE, "Undefined index: %s", ZSTR_VAL(str)); ZVAL_NULL(EX_VAR(opline->result.var)); } else { ZVAL_COPY(EX_VAR(opline->result.var), value); } + if (UNEXPECTED(str != Z_STR_P(offset))) { + zend_string_release(str); + } + } else if (EXPECTED(Z_TYPE_P(offset) == IS_REFERENCE)) { + offset = Z_REFVAL_P(offset); + ZEND_VM_C_GOTO(assign_again_list); + } else if (Z_TYPE_P(offset) == IS_NULL) { + str = ZSTR_EMPTY_ALLOC(); + ZEND_VM_C_GOTO(str_index_list); + } else if (Z_TYPE_P(offset) == IS_DOUBLE) { + hval = zend_dval_to_lval(Z_DVAL_P(offset)); + ZEND_VM_C_GOTO(num_index_list); + } else if (Z_TYPE_P(offset) == IS_FALSE) { + hval = 0; + ZEND_VM_C_GOTO(num_index_list); + } else if (Z_TYPE_P(offset) == IS_TRUE) { + hval = 1; + ZEND_VM_C_GOTO(num_index_list); + } else if (Z_TYPE_P(offset) == IS_RESOURCE) { + zend_error(E_NOTICE, "Resource ID#%pd used as offset, casting to integer (%pd)", Z_RES_HANDLE_P(offset), Z_RES_HANDLE_P(offset)); + hval = Z_RES_HANDLE_P(offset); + } else { + zend_error(E_WARNING, "Illegal offset type"); } } else if (OP1_TYPE != IS_CONST && UNEXPECTED(Z_TYPE_P(container) == IS_OBJECT) && EXPECTED(Z_OBJ_HT_P(container)->read_dimension)) { zval *result = EX_VAR(opline->result.var); - zval *retval = Z_OBJ_HT_P(container)->read_dimension(container, EX_CONSTANT(opline->op2), BP_VAR_R, result); + zval *retval = Z_OBJ_HT_P(container)->read_dimension(container, offset, BP_VAR_R, result); if (retval) { if (result != retval) { @@ -2116,6 +2148,7 @@ ZEND_VM_C_LABEL(try_fetch_list): } ZVAL_NULL(EX_VAR(opline->result.var)); } + FREE_OP2(); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 9a0bf762fc3bc..bb39dd90b2205 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -5746,7 +5746,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_LIST_SPEC_CONST_CONST_HA { USE_OPLINE + zval *container; + zval *offset = EX_CONSTANT(opline->op2); SAVE_OPLINE(); container = EX_CONSTANT(opline->op1); @@ -5754,31 +5756,61 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_LIST_SPEC_CONST_CONST_HA try_fetch_list: if (EXPECTED(Z_TYPE_P(container) == IS_ARRAY)) { zval *value; + zend_string *str; + zend_ulong hval; - if (Z_TYPE_P(EX_CONSTANT(opline->op2)) == IS_LONG) { - value = zend_hash_index_find(Z_ARRVAL_P(container), Z_LVAL_P(EX_CONSTANT(opline->op2))); +assign_again_list: + if (EXPECTED(Z_TYPE_P(offset) == IS_LONG)) { + hval = Z_LVAL_P(offset); +num_index_list: + value = zend_hash_index_find(Z_ARRVAL_P(container), hval); if (UNEXPECTED(value == NULL)) { - zend_error(E_NOTICE,"Undefined offset: " ZEND_ULONG_FMT, Z_LVAL_P(EX_CONSTANT(opline->op2))); + zend_error(E_NOTICE, "Undefined offset: " ZEND_ULONG_FMT, hval); ZVAL_NULL(EX_VAR(opline->result.var)); } else { ZVAL_COPY(EX_VAR(opline->result.var), value); } - } else { - value = zend_hash_find(Z_ARRVAL_P(container), Z_STR_P(EX_CONSTANT(opline->op2))); + } else if (EXPECTED(Z_TYPE_P(offset) == IS_STRING)) { + str = Z_STR_P(offset); +str_index_list: + value = zend_hash_find(Z_ARRVAL_P(container), str); if (UNEXPECTED(value == NULL)) { - zend_error(E_NOTICE, "Undefined index: %s", Z_STRVAL_P(EX_CONSTANT(opline->op2))); + zend_error(E_NOTICE, "Undefined index: %s", ZSTR_VAL(str)); ZVAL_NULL(EX_VAR(opline->result.var)); } else { ZVAL_COPY(EX_VAR(opline->result.var), value); } + if (UNEXPECTED(str != Z_STR_P(offset))) { + zend_string_release(str); + } + } else if (EXPECTED(Z_TYPE_P(offset) == IS_REFERENCE)) { + offset = Z_REFVAL_P(offset); + goto assign_again_list; + } else if (Z_TYPE_P(offset) == IS_NULL) { + str = ZSTR_EMPTY_ALLOC(); + goto str_index_list; + } else if (Z_TYPE_P(offset) == IS_DOUBLE) { + hval = zend_dval_to_lval(Z_DVAL_P(offset)); + goto num_index_list; + } else if (Z_TYPE_P(offset) == IS_FALSE) { + hval = 0; + goto num_index_list; + } else if (Z_TYPE_P(offset) == IS_TRUE) { + hval = 1; + goto num_index_list; + } else if (Z_TYPE_P(offset) == IS_RESOURCE) { + zend_error(E_NOTICE, "Resource ID#%pd used as offset, casting to integer (%pd)", Z_RES_HANDLE_P(offset), Z_RES_HANDLE_P(offset)); + hval = Z_RES_HANDLE_P(offset); + } else { + zend_error(E_WARNING, "Illegal offset type"); } } else if (IS_CONST != IS_CONST && UNEXPECTED(Z_TYPE_P(container) == IS_OBJECT) && EXPECTED(Z_OBJ_HT_P(container)->read_dimension)) { zval *result = EX_VAR(opline->result.var); - zval *retval = Z_OBJ_HT_P(container)->read_dimension(container, EX_CONSTANT(opline->op2), BP_VAR_R, result); + zval *retval = Z_OBJ_HT_P(container)->read_dimension(container, offset, BP_VAR_R, result); if (retval) { if (result != retval) { @@ -5796,6 +5828,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_LIST_SPEC_CONST_CONST_HA } ZVAL_NULL(EX_VAR(opline->result.var)); } + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } @@ -11323,6 +11356,96 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_FUNC_ARG_SPEC_CONST_ } } +static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_LIST_SPEC_CONST_TMPVAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + + zend_free_op free_op2; + zval *container; + zval *offset = _get_zval_ptr_var(opline->op2.var, execute_data, &free_op2); + + SAVE_OPLINE(); + container = EX_CONSTANT(opline->op1); + +try_fetch_list: + if (EXPECTED(Z_TYPE_P(container) == IS_ARRAY)) { + zval *value; + zend_string *str; + zend_ulong hval; + +assign_again_list: + if (EXPECTED(Z_TYPE_P(offset) == IS_LONG)) { + hval = Z_LVAL_P(offset); +num_index_list: + value = zend_hash_index_find(Z_ARRVAL_P(container), hval); + + if (UNEXPECTED(value == NULL)) { + zend_error(E_NOTICE, "Undefined offset: " ZEND_ULONG_FMT, hval); + ZVAL_NULL(EX_VAR(opline->result.var)); + } else { + ZVAL_COPY(EX_VAR(opline->result.var), value); + } + } else if (EXPECTED(Z_TYPE_P(offset) == IS_STRING)) { + str = Z_STR_P(offset); +str_index_list: + value = zend_hash_find(Z_ARRVAL_P(container), str); + + if (UNEXPECTED(value == NULL)) { + zend_error(E_NOTICE, "Undefined index: %s", ZSTR_VAL(str)); + ZVAL_NULL(EX_VAR(opline->result.var)); + } else { + ZVAL_COPY(EX_VAR(opline->result.var), value); + } + if (UNEXPECTED(str != Z_STR_P(offset))) { + zend_string_release(str); + } + } else if (EXPECTED(Z_TYPE_P(offset) == IS_REFERENCE)) { + offset = Z_REFVAL_P(offset); + goto assign_again_list; + } else if (Z_TYPE_P(offset) == IS_NULL) { + str = ZSTR_EMPTY_ALLOC(); + goto str_index_list; + } else if (Z_TYPE_P(offset) == IS_DOUBLE) { + hval = zend_dval_to_lval(Z_DVAL_P(offset)); + goto num_index_list; + } else if (Z_TYPE_P(offset) == IS_FALSE) { + hval = 0; + goto num_index_list; + } else if (Z_TYPE_P(offset) == IS_TRUE) { + hval = 1; + goto num_index_list; + } else if (Z_TYPE_P(offset) == IS_RESOURCE) { + zend_error(E_NOTICE, "Resource ID#%pd used as offset, casting to integer (%pd)", Z_RES_HANDLE_P(offset), Z_RES_HANDLE_P(offset)); + hval = Z_RES_HANDLE_P(offset); + } else { + zend_error(E_WARNING, "Illegal offset type"); + } + } else if (IS_CONST != IS_CONST && + UNEXPECTED(Z_TYPE_P(container) == IS_OBJECT) && + EXPECTED(Z_OBJ_HT_P(container)->read_dimension)) { + zval *result = EX_VAR(opline->result.var); + zval *retval = Z_OBJ_HT_P(container)->read_dimension(container, offset, BP_VAR_R, result); + + if (retval) { + if (result != retval) { + ZVAL_COPY(result, retval); + } + } else { + ZVAL_NULL(result); + } + } else if ((IS_CONST & (IS_VAR|IS_CV)) && Z_TYPE_P(container) == IS_REFERENCE) { + container = Z_REFVAL_P(container); + goto try_fetch_list; + } else { + if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_P(container) == IS_UNDEF)) { + GET_OP1_UNDEF_CV(container, BP_VAR_R); + } + ZVAL_NULL(EX_VAR(opline->result.var)); + } + zval_ptr_dtor_nogc(free_op2); + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); +} + static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FAST_CONCAT_SPEC_CONST_TMPVAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -37872,7 +37995,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_LIST_SPEC_CV_CONST_HANDL { USE_OPLINE + zval *container; + zval *offset = EX_CONSTANT(opline->op2); SAVE_OPLINE(); container = _get_zval_ptr_cv_undef(execute_data, opline->op1.var); @@ -37880,31 +38005,61 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_LIST_SPEC_CV_CONST_HANDL try_fetch_list: if (EXPECTED(Z_TYPE_P(container) == IS_ARRAY)) { zval *value; + zend_string *str; + zend_ulong hval; - if (Z_TYPE_P(EX_CONSTANT(opline->op2)) == IS_LONG) { - value = zend_hash_index_find(Z_ARRVAL_P(container), Z_LVAL_P(EX_CONSTANT(opline->op2))); +assign_again_list: + if (EXPECTED(Z_TYPE_P(offset) == IS_LONG)) { + hval = Z_LVAL_P(offset); +num_index_list: + value = zend_hash_index_find(Z_ARRVAL_P(container), hval); if (UNEXPECTED(value == NULL)) { - zend_error(E_NOTICE,"Undefined offset: " ZEND_ULONG_FMT, Z_LVAL_P(EX_CONSTANT(opline->op2))); + zend_error(E_NOTICE, "Undefined offset: " ZEND_ULONG_FMT, hval); ZVAL_NULL(EX_VAR(opline->result.var)); } else { ZVAL_COPY(EX_VAR(opline->result.var), value); } - } else { - value = zend_hash_find(Z_ARRVAL_P(container), Z_STR_P(EX_CONSTANT(opline->op2))); + } else if (EXPECTED(Z_TYPE_P(offset) == IS_STRING)) { + str = Z_STR_P(offset); +str_index_list: + value = zend_hash_find(Z_ARRVAL_P(container), str); if (UNEXPECTED(value == NULL)) { - zend_error(E_NOTICE, "Undefined index: %s", Z_STRVAL_P(EX_CONSTANT(opline->op2))); + zend_error(E_NOTICE, "Undefined index: %s", ZSTR_VAL(str)); ZVAL_NULL(EX_VAR(opline->result.var)); } else { ZVAL_COPY(EX_VAR(opline->result.var), value); } + if (UNEXPECTED(str != Z_STR_P(offset))) { + zend_string_release(str); + } + } else if (EXPECTED(Z_TYPE_P(offset) == IS_REFERENCE)) { + offset = Z_REFVAL_P(offset); + goto assign_again_list; + } else if (Z_TYPE_P(offset) == IS_NULL) { + str = ZSTR_EMPTY_ALLOC(); + goto str_index_list; + } else if (Z_TYPE_P(offset) == IS_DOUBLE) { + hval = zend_dval_to_lval(Z_DVAL_P(offset)); + goto num_index_list; + } else if (Z_TYPE_P(offset) == IS_FALSE) { + hval = 0; + goto num_index_list; + } else if (Z_TYPE_P(offset) == IS_TRUE) { + hval = 1; + goto num_index_list; + } else if (Z_TYPE_P(offset) == IS_RESOURCE) { + zend_error(E_NOTICE, "Resource ID#%pd used as offset, casting to integer (%pd)", Z_RES_HANDLE_P(offset), Z_RES_HANDLE_P(offset)); + hval = Z_RES_HANDLE_P(offset); + } else { + zend_error(E_WARNING, "Illegal offset type"); } } else if (IS_CV != IS_CONST && UNEXPECTED(Z_TYPE_P(container) == IS_OBJECT) && EXPECTED(Z_OBJ_HT_P(container)->read_dimension)) { zval *result = EX_VAR(opline->result.var); - zval *retval = Z_OBJ_HT_P(container)->read_dimension(container, EX_CONSTANT(opline->op2), BP_VAR_R, result); + zval *retval = Z_OBJ_HT_P(container)->read_dimension(container, offset, BP_VAR_R, result); if (retval) { if (result != retval) { @@ -37922,6 +38077,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_LIST_SPEC_CV_CONST_HANDL } ZVAL_NULL(EX_VAR(opline->result.var)); } + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } @@ -47819,6 +47975,96 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_UNSET_SPEC_CV_TMPVAR ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } +static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_LIST_SPEC_CV_TMPVAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + + zend_free_op free_op2; + zval *container; + zval *offset = _get_zval_ptr_var(opline->op2.var, execute_data, &free_op2); + + SAVE_OPLINE(); + container = _get_zval_ptr_cv_undef(execute_data, opline->op1.var); + +try_fetch_list: + if (EXPECTED(Z_TYPE_P(container) == IS_ARRAY)) { + zval *value; + zend_string *str; + zend_ulong hval; + +assign_again_list: + if (EXPECTED(Z_TYPE_P(offset) == IS_LONG)) { + hval = Z_LVAL_P(offset); +num_index_list: + value = zend_hash_index_find(Z_ARRVAL_P(container), hval); + + if (UNEXPECTED(value == NULL)) { + zend_error(E_NOTICE, "Undefined offset: " ZEND_ULONG_FMT, hval); + ZVAL_NULL(EX_VAR(opline->result.var)); + } else { + ZVAL_COPY(EX_VAR(opline->result.var), value); + } + } else if (EXPECTED(Z_TYPE_P(offset) == IS_STRING)) { + str = Z_STR_P(offset); +str_index_list: + value = zend_hash_find(Z_ARRVAL_P(container), str); + + if (UNEXPECTED(value == NULL)) { + zend_error(E_NOTICE, "Undefined index: %s", ZSTR_VAL(str)); + ZVAL_NULL(EX_VAR(opline->result.var)); + } else { + ZVAL_COPY(EX_VAR(opline->result.var), value); + } + if (UNEXPECTED(str != Z_STR_P(offset))) { + zend_string_release(str); + } + } else if (EXPECTED(Z_TYPE_P(offset) == IS_REFERENCE)) { + offset = Z_REFVAL_P(offset); + goto assign_again_list; + } else if (Z_TYPE_P(offset) == IS_NULL) { + str = ZSTR_EMPTY_ALLOC(); + goto str_index_list; + } else if (Z_TYPE_P(offset) == IS_DOUBLE) { + hval = zend_dval_to_lval(Z_DVAL_P(offset)); + goto num_index_list; + } else if (Z_TYPE_P(offset) == IS_FALSE) { + hval = 0; + goto num_index_list; + } else if (Z_TYPE_P(offset) == IS_TRUE) { + hval = 1; + goto num_index_list; + } else if (Z_TYPE_P(offset) == IS_RESOURCE) { + zend_error(E_NOTICE, "Resource ID#%pd used as offset, casting to integer (%pd)", Z_RES_HANDLE_P(offset), Z_RES_HANDLE_P(offset)); + hval = Z_RES_HANDLE_P(offset); + } else { + zend_error(E_WARNING, "Illegal offset type"); + } + } else if (IS_CV != IS_CONST && + UNEXPECTED(Z_TYPE_P(container) == IS_OBJECT) && + EXPECTED(Z_OBJ_HT_P(container)->read_dimension)) { + zval *result = EX_VAR(opline->result.var); + zval *retval = Z_OBJ_HT_P(container)->read_dimension(container, offset, BP_VAR_R, result); + + if (retval) { + if (result != retval) { + ZVAL_COPY(result, retval); + } + } else { + ZVAL_NULL(result); + } + } else if ((IS_CV & (IS_VAR|IS_CV)) && Z_TYPE_P(container) == IS_REFERENCE) { + container = Z_REFVAL_P(container); + goto try_fetch_list; + } else { + if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_P(container) == IS_UNDEF)) { + GET_OP1_UNDEF_CV(container, BP_VAR_R); + } + ZVAL_NULL(EX_VAR(opline->result.var)); + } + zval_ptr_dtor_nogc(free_op2); + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); +} + static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_DATA_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -50936,7 +51182,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_DIM_R_SPEC_TMPVAR_CONST_ { USE_OPLINE zend_free_op free_op1; + zval *container; + zval *offset = EX_CONSTANT(opline->op2); SAVE_OPLINE(); container = _get_zval_ptr_var(opline->op1.var, execute_data, &free_op1); @@ -51044,31 +51292,61 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_LIST_SPEC_TMPVAR_CONST_H try_fetch_list: if (EXPECTED(Z_TYPE_P(container) == IS_ARRAY)) { zval *value; + zend_string *str; + zend_ulong hval; - if (Z_TYPE_P(EX_CONSTANT(opline->op2)) == IS_LONG) { - value = zend_hash_index_find(Z_ARRVAL_P(container), Z_LVAL_P(EX_CONSTANT(opline->op2))); +assign_again_list: + if (EXPECTED(Z_TYPE_P(offset) == IS_LONG)) { + hval = Z_LVAL_P(offset); +num_index_list: + value = zend_hash_index_find(Z_ARRVAL_P(container), hval); if (UNEXPECTED(value == NULL)) { - zend_error(E_NOTICE,"Undefined offset: " ZEND_ULONG_FMT, Z_LVAL_P(EX_CONSTANT(opline->op2))); + zend_error(E_NOTICE, "Undefined offset: " ZEND_ULONG_FMT, hval); ZVAL_NULL(EX_VAR(opline->result.var)); } else { ZVAL_COPY(EX_VAR(opline->result.var), value); } - } else { - value = zend_hash_find(Z_ARRVAL_P(container), Z_STR_P(EX_CONSTANT(opline->op2))); + } else if (EXPECTED(Z_TYPE_P(offset) == IS_STRING)) { + str = Z_STR_P(offset); +str_index_list: + value = zend_hash_find(Z_ARRVAL_P(container), str); if (UNEXPECTED(value == NULL)) { - zend_error(E_NOTICE, "Undefined index: %s", Z_STRVAL_P(EX_CONSTANT(opline->op2))); + zend_error(E_NOTICE, "Undefined index: %s", ZSTR_VAL(str)); ZVAL_NULL(EX_VAR(opline->result.var)); } else { ZVAL_COPY(EX_VAR(opline->result.var), value); } + if (UNEXPECTED(str != Z_STR_P(offset))) { + zend_string_release(str); + } + } else if (EXPECTED(Z_TYPE_P(offset) == IS_REFERENCE)) { + offset = Z_REFVAL_P(offset); + goto assign_again_list; + } else if (Z_TYPE_P(offset) == IS_NULL) { + str = ZSTR_EMPTY_ALLOC(); + goto str_index_list; + } else if (Z_TYPE_P(offset) == IS_DOUBLE) { + hval = zend_dval_to_lval(Z_DVAL_P(offset)); + goto num_index_list; + } else if (Z_TYPE_P(offset) == IS_FALSE) { + hval = 0; + goto num_index_list; + } else if (Z_TYPE_P(offset) == IS_TRUE) { + hval = 1; + goto num_index_list; + } else if (Z_TYPE_P(offset) == IS_RESOURCE) { + zend_error(E_NOTICE, "Resource ID#%pd used as offset, casting to integer (%pd)", Z_RES_HANDLE_P(offset), Z_RES_HANDLE_P(offset)); + hval = Z_RES_HANDLE_P(offset); + } else { + zend_error(E_WARNING, "Illegal offset type"); } } else if ((IS_TMP_VAR|IS_VAR) != IS_CONST && UNEXPECTED(Z_TYPE_P(container) == IS_OBJECT) && EXPECTED(Z_OBJ_HT_P(container)->read_dimension)) { zval *result = EX_VAR(opline->result.var); - zval *retval = Z_OBJ_HT_P(container)->read_dimension(container, EX_CONSTANT(opline->op2), BP_VAR_R, result); + zval *retval = Z_OBJ_HT_P(container)->read_dimension(container, offset, BP_VAR_R, result); if (retval) { if (result != retval) { @@ -51086,6 +51364,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_LIST_SPEC_TMPVAR_CONST_H } ZVAL_NULL(EX_VAR(opline->result.var)); } + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } @@ -54483,6 +54762,96 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_TMPVAR_TMPVA ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } +static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_LIST_SPEC_TMPVAR_TMPVAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + zend_free_op free_op1; + zend_free_op free_op2; + zval *container; + zval *offset = _get_zval_ptr_var(opline->op2.var, execute_data, &free_op2); + + SAVE_OPLINE(); + container = _get_zval_ptr_var(opline->op1.var, execute_data, &free_op1); + +try_fetch_list: + if (EXPECTED(Z_TYPE_P(container) == IS_ARRAY)) { + zval *value; + zend_string *str; + zend_ulong hval; + +assign_again_list: + if (EXPECTED(Z_TYPE_P(offset) == IS_LONG)) { + hval = Z_LVAL_P(offset); +num_index_list: + value = zend_hash_index_find(Z_ARRVAL_P(container), hval); + + if (UNEXPECTED(value == NULL)) { + zend_error(E_NOTICE, "Undefined offset: " ZEND_ULONG_FMT, hval); + ZVAL_NULL(EX_VAR(opline->result.var)); + } else { + ZVAL_COPY(EX_VAR(opline->result.var), value); + } + } else if (EXPECTED(Z_TYPE_P(offset) == IS_STRING)) { + str = Z_STR_P(offset); +str_index_list: + value = zend_hash_find(Z_ARRVAL_P(container), str); + + if (UNEXPECTED(value == NULL)) { + zend_error(E_NOTICE, "Undefined index: %s", ZSTR_VAL(str)); + ZVAL_NULL(EX_VAR(opline->result.var)); + } else { + ZVAL_COPY(EX_VAR(opline->result.var), value); + } + if (UNEXPECTED(str != Z_STR_P(offset))) { + zend_string_release(str); + } + } else if (EXPECTED(Z_TYPE_P(offset) == IS_REFERENCE)) { + offset = Z_REFVAL_P(offset); + goto assign_again_list; + } else if (Z_TYPE_P(offset) == IS_NULL) { + str = ZSTR_EMPTY_ALLOC(); + goto str_index_list; + } else if (Z_TYPE_P(offset) == IS_DOUBLE) { + hval = zend_dval_to_lval(Z_DVAL_P(offset)); + goto num_index_list; + } else if (Z_TYPE_P(offset) == IS_FALSE) { + hval = 0; + goto num_index_list; + } else if (Z_TYPE_P(offset) == IS_TRUE) { + hval = 1; + goto num_index_list; + } else if (Z_TYPE_P(offset) == IS_RESOURCE) { + zend_error(E_NOTICE, "Resource ID#%pd used as offset, casting to integer (%pd)", Z_RES_HANDLE_P(offset), Z_RES_HANDLE_P(offset)); + hval = Z_RES_HANDLE_P(offset); + } else { + zend_error(E_WARNING, "Illegal offset type"); + } + } else if ((IS_TMP_VAR|IS_VAR) != IS_CONST && + UNEXPECTED(Z_TYPE_P(container) == IS_OBJECT) && + EXPECTED(Z_OBJ_HT_P(container)->read_dimension)) { + zval *result = EX_VAR(opline->result.var); + zval *retval = Z_OBJ_HT_P(container)->read_dimension(container, offset, BP_VAR_R, result); + + if (retval) { + if (result != retval) { + ZVAL_COPY(result, retval); + } + } else { + ZVAL_NULL(result); + } + } else if (((IS_TMP_VAR|IS_VAR) & (IS_VAR|IS_CV)) && Z_TYPE_P(container) == IS_REFERENCE) { + container = Z_REFVAL_P(container); + goto try_fetch_list; + } else { + if ((IS_TMP_VAR|IS_VAR) == IS_CV && UNEXPECTED(Z_TYPE_P(container) == IS_UNDEF)) { + GET_OP1_UNDEF_CV(container, BP_VAR_R); + } + ZVAL_NULL(EX_VAR(opline->result.var)); + } + zval_ptr_dtor_nogc(free_op2); + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); +} + static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FAST_CONCAT_SPEC_TMPVAR_TMPVAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -57534,18 +57903,18 @@ void zend_init_opcodes_handlers(void) ZEND_NULL_HANDLER, ZEND_FETCH_OBJ_UNSET_SPEC_CV_CV_HANDLER, ZEND_FETCH_LIST_SPEC_CONST_CONST_HANDLER, - ZEND_NULL_HANDLER, - ZEND_NULL_HANDLER, + ZEND_FETCH_LIST_SPEC_CONST_TMPVAR_HANDLER, + ZEND_FETCH_LIST_SPEC_CONST_TMPVAR_HANDLER, ZEND_NULL_HANDLER, ZEND_NULL_HANDLER, ZEND_FETCH_LIST_SPEC_TMPVAR_CONST_HANDLER, - ZEND_NULL_HANDLER, - ZEND_NULL_HANDLER, + ZEND_FETCH_LIST_SPEC_TMPVAR_TMPVAR_HANDLER, + ZEND_FETCH_LIST_SPEC_TMPVAR_TMPVAR_HANDLER, ZEND_NULL_HANDLER, ZEND_NULL_HANDLER, ZEND_FETCH_LIST_SPEC_TMPVAR_CONST_HANDLER, - ZEND_NULL_HANDLER, - ZEND_NULL_HANDLER, + ZEND_FETCH_LIST_SPEC_TMPVAR_TMPVAR_HANDLER, + ZEND_FETCH_LIST_SPEC_TMPVAR_TMPVAR_HANDLER, ZEND_NULL_HANDLER, ZEND_NULL_HANDLER, ZEND_NULL_HANDLER, @@ -57554,8 +57923,8 @@ void zend_init_opcodes_handlers(void) ZEND_NULL_HANDLER, ZEND_NULL_HANDLER, ZEND_FETCH_LIST_SPEC_CV_CONST_HANDLER, - ZEND_NULL_HANDLER, - ZEND_NULL_HANDLER, + ZEND_FETCH_LIST_SPEC_CV_TMPVAR_HANDLER, + ZEND_FETCH_LIST_SPEC_CV_TMPVAR_HANDLER, ZEND_NULL_HANDLER, ZEND_NULL_HANDLER, ZEND_NULL_HANDLER, diff --git a/Zend/zend_vm_opcodes.c b/Zend/zend_vm_opcodes.c index 37ba8d411ba34..ac5f0d6a32eee 100644 --- a/Zend/zend_vm_opcodes.c +++ b/Zend/zend_vm_opcodes.c @@ -307,7 +307,7 @@ static uint32_t zend_vm_opcodes_flags[184] = { 0x00010107, 0x00000701, 0x00000751, - 0x00000307, + 0x00000707, 0x06000301, 0x00000000, 0x00000000, From f4c8b2cb30fc074b15b5f7aabef5444382403b5d Mon Sep 17 00:00:00 2001 From: Andrea Faulds Date: Sat, 16 Jan 2016 23:47:11 +0000 Subject: [PATCH 04/13] Add tests --- Zend/tests/foreach_list_keyed.phpt | 36 ++++++++++ Zend/tests/list_keyed.phpt | 71 +++++++++++++++++++ Zend/tests/list_keyed_ArrayAccess.phpt | 16 +++++ Zend/tests/list_keyed_constants.phpt | 25 +++++++ Zend/tests/list_keyed_conversions.phpt | 24 +++++++ Zend/tests/list_keyed_undefined.phpt | 22 ++++++ Zend/tests/list_mixed_keyed_unkeyed.phpt | 16 +++++ .../list_mixed_nested_keyed_unkeyed.phpt | 34 +++++++++ 8 files changed, 244 insertions(+) create mode 100644 Zend/tests/foreach_list_keyed.phpt create mode 100644 Zend/tests/list_keyed.phpt create mode 100644 Zend/tests/list_keyed_ArrayAccess.phpt create mode 100644 Zend/tests/list_keyed_constants.phpt create mode 100644 Zend/tests/list_keyed_conversions.phpt create mode 100644 Zend/tests/list_keyed_undefined.phpt create mode 100644 Zend/tests/list_mixed_keyed_unkeyed.phpt create mode 100644 Zend/tests/list_mixed_nested_keyed_unkeyed.phpt diff --git a/Zend/tests/foreach_list_keyed.phpt b/Zend/tests/foreach_list_keyed.phpt new file mode 100644 index 0000000000000..f5fab4e342e75 --- /dev/null +++ b/Zend/tests/foreach_list_keyed.phpt @@ -0,0 +1,36 @@ +--TEST-- +foreach with list syntax, keyed +--FILE-- + 1, "y" => 2], + ["x" => 2, "y" => 1] +]; + +foreach ($points as list("x" => $x, "y" => $y)) { + var_dump($x, $y); +} + +echo PHP_EOL; + +$invertedPoints = [ + "x" => [1, 2], + "y" => [2, 1] +]; + +foreach ($invertedPoints as list(0 => $row1, 1 => $row2)) { + var_dump($row1, $row2); +} + +?> +--EXPECT-- +int(1) +int(2) +int(2) +int(1) + +int(1) +int(2) +int(2) +int(1) diff --git a/Zend/tests/list_keyed.phpt b/Zend/tests/list_keyed.phpt new file mode 100644 index 0000000000000..b549ed9bf5018 --- /dev/null +++ b/Zend/tests/list_keyed.phpt @@ -0,0 +1,71 @@ +--TEST-- +list() with keys +--FILE-- + "bad", + "happy" => "sad", +]; + +list("good" => $good_antonym, "happy" => $happy_antonym) = $antonyms; +var_dump($good_antonym, $happy_antonym); + +echo PHP_EOL; + +$powersOfTwo = [ + 1 => 2, + 2 => 4, + 3 => 8 +]; + +list(1 => $two_1, 2 => $two_2, 3 => $two_3) = $powersOfTwo; +var_dump($two_1, $two_2, $two_3); + +echo PHP_EOL; + +$contrivedMixedKeyTypesExample = [ + 7 => "the best PHP version", + "elePHPant" => "the cutest mascot" +]; + +list(7 => $seven, "elePHPant" => $elePHPant) = $contrivedMixedKeyTypesExample; +var_dump($seven, $elePHPant); + +echo PHP_EOL; + +$allTogetherNow = [ + "antonyms" => $antonyms, + "powersOfTwo" => $powersOfTwo, + "contrivedMixedKeyTypesExample" => $contrivedMixedKeyTypesExample +]; + +list( + "antonyms" => list("good" => $good_antonym, "happy" => $happy_antonym), + "powersOfTwo" => list(1 => $two_1, 2 => $two_2, 3 => $two_3), + "contrivedMixedKeyTypesExample" => list(7 => $seven, "elePHPant" => $elePHPant) +) = $allTogetherNow; + +var_dump($good_antonym, $happy_antonym); +var_dump($two_1, $two_2, $two_3); +var_dump($seven, $elePHPant); + +?> +--EXPECT-- +string(3) "bad" +string(3) "sad" + +int(2) +int(4) +int(8) + +string(20) "the best PHP version" +string(17) "the cutest mascot" + +string(3) "bad" +string(3) "sad" +int(2) +int(4) +int(8) +string(20) "the best PHP version" +string(17) "the cutest mascot" diff --git a/Zend/tests/list_keyed_ArrayAccess.phpt b/Zend/tests/list_keyed_ArrayAccess.phpt new file mode 100644 index 0000000000000..eeeb64dd584ed --- /dev/null +++ b/Zend/tests/list_keyed_ArrayAccess.phpt @@ -0,0 +1,16 @@ +--TEST-- +list() with keys and ArrayAccess +--FILE-- + $good, "happy" => $happy) = $antonymObject; +var_dump($good, $happy); + +?> +--EXPECT-- +string(3) "bad" +string(3) "sad" diff --git a/Zend/tests/list_keyed_constants.phpt b/Zend/tests/list_keyed_constants.phpt new file mode 100644 index 0000000000000..f05659b3557ac --- /dev/null +++ b/Zend/tests/list_keyed_constants.phpt @@ -0,0 +1,25 @@ +--TEST-- +list() with constant keys +--FILE-- + "one", + 2 => "two" +]; + +const COMPILE_TIME_RESOLVABLE = 1; + +define('PROBABLY_NOT_COMPILE_TIME_RESOLVABLE', (int)file_get_contents("data:text/plain,2")); + +list( + COMPILE_TIME_RESOLVABLE => $one, + PROBABLY_NOT_COMPILE_TIME_RESOLVABLE => $two +) = $arr; + +var_dump($one, $two); + +?> +--EXPECTF-- +string(3) "one" +string(3) "two" diff --git a/Zend/tests/list_keyed_conversions.phpt b/Zend/tests/list_keyed_conversions.phpt new file mode 100644 index 0000000000000..e0cfa694d7ef8 --- /dev/null +++ b/Zend/tests/list_keyed_conversions.phpt @@ -0,0 +1,24 @@ +--TEST-- +list() with non-integer-or-string keys +--FILE-- + 0, + 1 => 1, + "" => "" +]; + +list(NULL => $NULL, 1.5 => $float, FALSE => $FALSE, TRUE => $TRUE) = $results; +var_dump($NULL, $float, $FALSE, $TRUE); + +list(STDIN => $resource) = []; + +?> +--EXPECTF-- +string(0) "" +int(1) +int(0) +int(1) + +Notice: Resource ID#%d used as offset, casting to integer (%d) in %s on line %d diff --git a/Zend/tests/list_keyed_undefined.phpt b/Zend/tests/list_keyed_undefined.phpt new file mode 100644 index 0000000000000..a18e3b4d20650 --- /dev/null +++ b/Zend/tests/list_keyed_undefined.phpt @@ -0,0 +1,22 @@ +--TEST-- +list() with undefined keys +--FILE-- + "the best PHP version", + "elePHPant" => "the cutest mascot" +]; + +list(5 => $five, "duke" => $duke) = $contrivedMixedKeyTypesExample; + +var_dump($five, $duke); + +?> +--EXPECTF-- + +Notice: Undefined offset: 5 in %s on line %d + +Notice: Undefined index: duke in %s on line %d +NULL +NULL diff --git a/Zend/tests/list_mixed_keyed_unkeyed.phpt b/Zend/tests/list_mixed_keyed_unkeyed.phpt new file mode 100644 index 0000000000000..5562479fc387d --- /dev/null +++ b/Zend/tests/list_mixed_keyed_unkeyed.phpt @@ -0,0 +1,16 @@ +--TEST-- +list() with both keyed and unkeyed elements +--FILE-- + 1, + "foo" => "bar" +]; + +list($zero, 1 => $one, "foo" => $foo) = $contrivedKeyedAndUnkeyedArrayExample; + +?> +--EXPECTF-- +Parse error: syntax error, unexpected %s in %s on line %d diff --git a/Zend/tests/list_mixed_nested_keyed_unkeyed.phpt b/Zend/tests/list_mixed_nested_keyed_unkeyed.phpt new file mode 100644 index 0000000000000..3087775b76ab2 --- /dev/null +++ b/Zend/tests/list_mixed_nested_keyed_unkeyed.phpt @@ -0,0 +1,34 @@ +--TEST-- +list() with nested unkeyed and keyed list() +--FILE-- + 1, "y" => 2], + ["x" => 2, "y" => 1] +]; + +list(list("x" => $x1, "y" => $y1), list("x" => $x2, "y" => $y2)) = $points; +var_dump($x1, $y1, $x2, $y2); + +echo PHP_EOL; + +$invertedPoints = [ + "x" => [1, 2], + "y" => [2, 1] +]; + +list("x" => list($x1, $x2), "y" => list($y1, $y2)) = $invertedPoints; +var_dump($x1, $y1, $x2, $y2); + +?> +--EXPECT-- +int(1) +int(2) +int(2) +int(1) + +int(1) +int(2) +int(2) +int(1) From 14bfe93ddc34d1175bccb42a158be8842c472a9c Mon Sep 17 00:00:00 2001 From: Andrea Faulds Date: Sun, 17 Jan 2016 01:09:36 +0000 Subject: [PATCH 05/13] Allow trailing comma --- Zend/tests/list_keyed_trailing_comma.phpt | 38 +++++++++++++++++++++++ Zend/zend_language_parser.y | 2 +- 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/list_keyed_trailing_comma.phpt diff --git a/Zend/tests/list_keyed_trailing_comma.phpt b/Zend/tests/list_keyed_trailing_comma.phpt new file mode 100644 index 0000000000000..e0af0aed21192 --- /dev/null +++ b/Zend/tests/list_keyed_trailing_comma.phpt @@ -0,0 +1,38 @@ +--TEST-- +list() with keys and a trailing comma +--FILE-- + "bad", + "happy" => "sad", +]; + +list( + "good" => $good, + "happy" => $happy +) = $antonyms; + +var_dump($good, $happy); + +echo PHP_EOL; + +$antonyms = [ + "good" => "bad", + "happy" => "sad", +]; + +list( + "good" => $good, + "happy" => $happy, +) = $antonyms; + +var_dump($good, $happy); + +?> +--EXPECT-- +string(3) "bad" +string(3) "sad" + +string(3) "bad" +string(3) "sad" diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index e1def1fe1c7f5..8487b7da97490 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -1174,7 +1174,7 @@ property_name: assignment_list: unkeyed_assignment_list { $$ = $1; } - | keyed_assignment_list + | keyed_assignment_list possible_comma { $$ = $1; } ; From bab758119aec63289a2c5bef6a5f90a7bc6441a2 Mon Sep 17 00:00:00 2001 From: Andrea Faulds Date: Sun, 17 Jan 2016 01:20:26 +0000 Subject: [PATCH 06/13] Handle numeric strings --- Zend/tests/list_keyed_constants.phpt | 2 +- Zend/tests/list_keyed_conversions.phpt | 8 +++++++ Zend/zend_vm_def.h | 5 +++++ Zend/zend_vm_execute.h | 30 ++++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 1 deletion(-) diff --git a/Zend/tests/list_keyed_constants.phpt b/Zend/tests/list_keyed_constants.phpt index f05659b3557ac..6c8ca82209a93 100644 --- a/Zend/tests/list_keyed_constants.phpt +++ b/Zend/tests/list_keyed_constants.phpt @@ -10,7 +10,7 @@ $arr = [ const COMPILE_TIME_RESOLVABLE = 1; -define('PROBABLY_NOT_COMPILE_TIME_RESOLVABLE', (int)file_get_contents("data:text/plain,2")); +define('PROBABLY_NOT_COMPILE_TIME_RESOLVABLE', file_get_contents("data:text/plain,2")); list( COMPILE_TIME_RESOLVABLE => $one, diff --git a/Zend/tests/list_keyed_conversions.phpt b/Zend/tests/list_keyed_conversions.phpt index e0cfa694d7ef8..e2cf91a27d2de 100644 --- a/Zend/tests/list_keyed_conversions.phpt +++ b/Zend/tests/list_keyed_conversions.phpt @@ -12,6 +12,11 @@ $results = [ list(NULL => $NULL, 1.5 => $float, FALSE => $FALSE, TRUE => $TRUE) = $results; var_dump($NULL, $float, $FALSE, $TRUE); +echo PHP_EOL; + +list("0" => $zeroString, "1" => $oneString) = $results; +var_dump($zeroString, $oneString); + list(STDIN => $resource) = []; ?> @@ -21,4 +26,7 @@ int(1) int(0) int(1) +int(0) +int(1) + Notice: Resource ID#%d used as offset, casting to integer (%d) in %s on line %d diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index f38f37a6dc85a..0c02f132caf86 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -2093,6 +2093,11 @@ ZEND_VM_C_LABEL(num_index_list): } } else if (EXPECTED(Z_TYPE_P(offset) == IS_STRING)) { str = Z_STR_P(offset); + + if (ZEND_HANDLE_NUMERIC(str, hval)) { + ZEND_VM_C_GOTO(num_index_list); + } + ZEND_VM_C_LABEL(str_index_list): value = zend_hash_find(Z_ARRVAL_P(container), str); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index bb39dd90b2205..df3eb0efc9071 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -5773,6 +5773,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_LIST_SPEC_CONST_CONST_HA } } else if (EXPECTED(Z_TYPE_P(offset) == IS_STRING)) { str = Z_STR_P(offset); + + if (ZEND_HANDLE_NUMERIC(str, hval)) { + goto num_index_list; + } + str_index_list: value = zend_hash_find(Z_ARRVAL_P(container), str); @@ -11387,6 +11392,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_LIST_SPEC_CONST_TMPVAR_H } } else if (EXPECTED(Z_TYPE_P(offset) == IS_STRING)) { str = Z_STR_P(offset); + + if (ZEND_HANDLE_NUMERIC(str, hval)) { + goto num_index_list; + } + str_index_list: value = zend_hash_find(Z_ARRVAL_P(container), str); @@ -38022,6 +38032,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_LIST_SPEC_CV_CONST_HANDL } } else if (EXPECTED(Z_TYPE_P(offset) == IS_STRING)) { str = Z_STR_P(offset); + + if (ZEND_HANDLE_NUMERIC(str, hval)) { + goto num_index_list; + } + str_index_list: value = zend_hash_find(Z_ARRVAL_P(container), str); @@ -48006,6 +48021,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_LIST_SPEC_CV_TMPVAR_HAND } } else if (EXPECTED(Z_TYPE_P(offset) == IS_STRING)) { str = Z_STR_P(offset); + + if (ZEND_HANDLE_NUMERIC(str, hval)) { + goto num_index_list; + } + str_index_list: value = zend_hash_find(Z_ARRVAL_P(container), str); @@ -51309,6 +51329,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_LIST_SPEC_TMPVAR_CONST_H } } else if (EXPECTED(Z_TYPE_P(offset) == IS_STRING)) { str = Z_STR_P(offset); + + if (ZEND_HANDLE_NUMERIC(str, hval)) { + goto num_index_list; + } + str_index_list: value = zend_hash_find(Z_ARRVAL_P(container), str); @@ -54793,6 +54818,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_LIST_SPEC_TMPVAR_TMPVAR_ } } else if (EXPECTED(Z_TYPE_P(offset) == IS_STRING)) { str = Z_STR_P(offset); + + if (ZEND_HANDLE_NUMERIC(str, hval)) { + goto num_index_list; + } + str_index_list: value = zend_hash_find(Z_ARRVAL_P(container), str); From 3f622077c32fcd82fcf27a41bd0f22e2552ec4c5 Mon Sep 17 00:00:00 2001 From: Andrea Faulds Date: Tue, 19 Jan 2016 17:45:10 +0000 Subject: [PATCH 07/13] Remove compile-time HANDLE_NUMERIC (see bug #63217) --- Zend/tests/list_keyed_ArrayAccess.phpt | 24 ++++++++++++++++++++++++ Zend/zend_compile.c | 1 - 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/Zend/tests/list_keyed_ArrayAccess.phpt b/Zend/tests/list_keyed_ArrayAccess.phpt index eeeb64dd584ed..175b61660c29f 100644 --- a/Zend/tests/list_keyed_ArrayAccess.phpt +++ b/Zend/tests/list_keyed_ArrayAccess.phpt @@ -10,7 +10,31 @@ $antonymObject["happy"] = "sad"; list("good" => $good, "happy" => $happy) = $antonymObject; var_dump($good, $happy); +echo PHP_EOL; + +class IndexPrinter implements ArrayAccess +{ + public function offsetGet($offset) { + echo "GET "; + var_dump($offset); + } + public function offsetSet($offset, $value) { + } + public function offsetExists($offset) { + } + public function offsetUnset($offset) { + } +} + +$op = new IndexPrinter; +list(123 => $x) = $op; +// PHP shouldn't convert this to an integer offset, because it's ArrayAccess +list("123" => $x) = $op; + ?> --EXPECT-- string(3) "bad" string(3) "sad" + +GET int(123) +GET string(3) "123" diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 0461e643913e1..0b871ed52a25a 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -2751,7 +2751,6 @@ static void zend_compile_keyed_list_assign(zend_ast_list *list, znode *expr_node znode fetch_result, dim_node; zend_compile_expr(&dim_node, key_ast); - zend_handle_numeric_op(&dim_node); if (expr_node->op_type == IS_CONST) { Z_TRY_ADDREF(expr_node->u.constant); From 589756cbcccbb4702c90b5aa9c091af446058ca1 Mon Sep 17 00:00:00 2001 From: Andrea Faulds Date: Tue, 19 Jan 2016 17:29:34 +0000 Subject: [PATCH 08/13] Allow arbitrary expressions for key --- Zend/tests/list_keyed_ArrayAccess.phpt | 14 + ...ants.phpt => list_keyed_non_literals.phpt} | 11 +- Zend/zend_language_parser.y | 4 +- Zend/zend_vm_def.h | 2 +- Zend/zend_vm_execute.h | 293 +++++++++++++++++- 5 files changed, 314 insertions(+), 10 deletions(-) rename Zend/tests/{list_keyed_constants.phpt => list_keyed_non_literals.phpt} (57%) diff --git a/Zend/tests/list_keyed_ArrayAccess.phpt b/Zend/tests/list_keyed_ArrayAccess.phpt index 175b61660c29f..1bb20130365d0 100644 --- a/Zend/tests/list_keyed_ArrayAccess.phpt +++ b/Zend/tests/list_keyed_ArrayAccess.phpt @@ -12,6 +12,17 @@ var_dump($good, $happy); echo PHP_EOL; +$stdClassCollection = new SplObjectStorage; +$foo = new StdClass; +$stdClassCollection[$foo] = "foo"; +$bar = new StdClass; +$stdClassCollection[$bar] = "bar"; + +list($foo => $fooStr, $bar => $barStr) = $stdClassCollection; +var_dump($fooStr, $barStr); + +echo PHP_EOL; + class IndexPrinter implements ArrayAccess { public function offsetGet($offset) { @@ -36,5 +47,8 @@ list("123" => $x) = $op; string(3) "bad" string(3) "sad" +string(3) "foo" +string(3) "bar" + GET int(123) GET string(3) "123" diff --git a/Zend/tests/list_keyed_constants.phpt b/Zend/tests/list_keyed_non_literals.phpt similarity index 57% rename from Zend/tests/list_keyed_constants.phpt rename to Zend/tests/list_keyed_non_literals.phpt index 6c8ca82209a93..80f22eda22594 100644 --- a/Zend/tests/list_keyed_constants.phpt +++ b/Zend/tests/list_keyed_non_literals.phpt @@ -5,21 +5,26 @@ list() with constant keys $arr = [ 1 => "one", - 2 => "two" + 2 => "two", + 3 => "three" ]; const COMPILE_TIME_RESOLVABLE = 1; define('PROBABLY_NOT_COMPILE_TIME_RESOLVABLE', file_get_contents("data:text/plain,2")); +$probablyNotCompileTimeResolvable3 = cos(0) * 3; + list( COMPILE_TIME_RESOLVABLE => $one, - PROBABLY_NOT_COMPILE_TIME_RESOLVABLE => $two + PROBABLY_NOT_COMPILE_TIME_RESOLVABLE => $two, + $probablyNotCompileTimeResolvable3 => $three ) = $arr; -var_dump($one, $two); +var_dump($one, $two, $three); ?> --EXPECTF-- string(3) "one" string(3) "two" +string(5) "three" diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 8487b7da97490..0ec991e9086fa 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -1199,9 +1199,9 @@ keyed_assignment_list: ; keyed_assignment_list_element: - scalar T_DOUBLE_ARROW variable + expr T_DOUBLE_ARROW variable { $$ = zend_ast_create(ZEND_AST_ARRAY_ELEM, $3, $1); } - | scalar T_DOUBLE_ARROW T_LIST '(' assignment_list ')' + | expr T_DOUBLE_ARROW T_LIST '(' assignment_list ')' { $$ = zend_ast_create(ZEND_AST_ARRAY_ELEM, $5, $1); } ; diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 0c02f132caf86..e2ee420f89e5d 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -2062,7 +2062,7 @@ ZEND_VM_HANDLER(97, ZEND_FETCH_OBJ_UNSET, VAR|UNUSED|THIS|CV, CONST|TMPVAR|CV) ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } -ZEND_VM_HANDLER(98, ZEND_FETCH_LIST, CONST|TMPVAR|CV, CONST|TMPVAR) +ZEND_VM_HANDLER(98, ZEND_FETCH_LIST, CONST|TMPVAR|CV, CONST|TMPVAR|CV) { USE_OPLINE zend_free_op free_op1; diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index df3eb0efc9071..057e6944abec2 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -9550,6 +9550,101 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_FUNC_ARG_SPEC_CONST_ } } +static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_LIST_SPEC_CONST_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + + + zval *container; + zval *offset = _get_zval_ptr_cv_BP_VAR_R(execute_data, opline->op2.var); + + SAVE_OPLINE(); + container = EX_CONSTANT(opline->op1); + +try_fetch_list: + if (EXPECTED(Z_TYPE_P(container) == IS_ARRAY)) { + zval *value; + zend_string *str; + zend_ulong hval; + +assign_again_list: + if (EXPECTED(Z_TYPE_P(offset) == IS_LONG)) { + hval = Z_LVAL_P(offset); +num_index_list: + value = zend_hash_index_find(Z_ARRVAL_P(container), hval); + + if (UNEXPECTED(value == NULL)) { + zend_error(E_NOTICE, "Undefined offset: " ZEND_ULONG_FMT, hval); + ZVAL_NULL(EX_VAR(opline->result.var)); + } else { + ZVAL_COPY(EX_VAR(opline->result.var), value); + } + } else if (EXPECTED(Z_TYPE_P(offset) == IS_STRING)) { + str = Z_STR_P(offset); + + if (ZEND_HANDLE_NUMERIC(str, hval)) { + goto num_index_list; + } + +str_index_list: + value = zend_hash_find(Z_ARRVAL_P(container), str); + + if (UNEXPECTED(value == NULL)) { + zend_error(E_NOTICE, "Undefined index: %s", ZSTR_VAL(str)); + ZVAL_NULL(EX_VAR(opline->result.var)); + } else { + ZVAL_COPY(EX_VAR(opline->result.var), value); + } + if (UNEXPECTED(str != Z_STR_P(offset))) { + zend_string_release(str); + } + } else if (EXPECTED(Z_TYPE_P(offset) == IS_REFERENCE)) { + offset = Z_REFVAL_P(offset); + goto assign_again_list; + } else if (Z_TYPE_P(offset) == IS_NULL) { + str = ZSTR_EMPTY_ALLOC(); + goto str_index_list; + } else if (Z_TYPE_P(offset) == IS_DOUBLE) { + hval = zend_dval_to_lval(Z_DVAL_P(offset)); + goto num_index_list; + } else if (Z_TYPE_P(offset) == IS_FALSE) { + hval = 0; + goto num_index_list; + } else if (Z_TYPE_P(offset) == IS_TRUE) { + hval = 1; + goto num_index_list; + } else if (Z_TYPE_P(offset) == IS_RESOURCE) { + zend_error(E_NOTICE, "Resource ID#%pd used as offset, casting to integer (%pd)", Z_RES_HANDLE_P(offset), Z_RES_HANDLE_P(offset)); + hval = Z_RES_HANDLE_P(offset); + } else { + zend_error(E_WARNING, "Illegal offset type"); + } + } else if (IS_CONST != IS_CONST && + UNEXPECTED(Z_TYPE_P(container) == IS_OBJECT) && + EXPECTED(Z_OBJ_HT_P(container)->read_dimension)) { + zval *result = EX_VAR(opline->result.var); + zval *retval = Z_OBJ_HT_P(container)->read_dimension(container, offset, BP_VAR_R, result); + + if (retval) { + if (result != retval) { + ZVAL_COPY(result, retval); + } + } else { + ZVAL_NULL(result); + } + } else if ((IS_CONST & (IS_VAR|IS_CV)) && Z_TYPE_P(container) == IS_REFERENCE) { + container = Z_REFVAL_P(container); + goto try_fetch_list; + } else { + if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_P(container) == IS_UNDEF)) { + GET_OP1_UNDEF_CV(container, BP_VAR_R); + } + ZVAL_NULL(EX_VAR(opline->result.var)); + } + + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); +} + static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FAST_CONCAT_SPEC_CONST_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -44435,6 +44530,101 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_UNSET_SPEC_CV_CV_HAN ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } +static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_LIST_SPEC_CV_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + + + zval *container; + zval *offset = _get_zval_ptr_cv_BP_VAR_R(execute_data, opline->op2.var); + + SAVE_OPLINE(); + container = _get_zval_ptr_cv_undef(execute_data, opline->op1.var); + +try_fetch_list: + if (EXPECTED(Z_TYPE_P(container) == IS_ARRAY)) { + zval *value; + zend_string *str; + zend_ulong hval; + +assign_again_list: + if (EXPECTED(Z_TYPE_P(offset) == IS_LONG)) { + hval = Z_LVAL_P(offset); +num_index_list: + value = zend_hash_index_find(Z_ARRVAL_P(container), hval); + + if (UNEXPECTED(value == NULL)) { + zend_error(E_NOTICE, "Undefined offset: " ZEND_ULONG_FMT, hval); + ZVAL_NULL(EX_VAR(opline->result.var)); + } else { + ZVAL_COPY(EX_VAR(opline->result.var), value); + } + } else if (EXPECTED(Z_TYPE_P(offset) == IS_STRING)) { + str = Z_STR_P(offset); + + if (ZEND_HANDLE_NUMERIC(str, hval)) { + goto num_index_list; + } + +str_index_list: + value = zend_hash_find(Z_ARRVAL_P(container), str); + + if (UNEXPECTED(value == NULL)) { + zend_error(E_NOTICE, "Undefined index: %s", ZSTR_VAL(str)); + ZVAL_NULL(EX_VAR(opline->result.var)); + } else { + ZVAL_COPY(EX_VAR(opline->result.var), value); + } + if (UNEXPECTED(str != Z_STR_P(offset))) { + zend_string_release(str); + } + } else if (EXPECTED(Z_TYPE_P(offset) == IS_REFERENCE)) { + offset = Z_REFVAL_P(offset); + goto assign_again_list; + } else if (Z_TYPE_P(offset) == IS_NULL) { + str = ZSTR_EMPTY_ALLOC(); + goto str_index_list; + } else if (Z_TYPE_P(offset) == IS_DOUBLE) { + hval = zend_dval_to_lval(Z_DVAL_P(offset)); + goto num_index_list; + } else if (Z_TYPE_P(offset) == IS_FALSE) { + hval = 0; + goto num_index_list; + } else if (Z_TYPE_P(offset) == IS_TRUE) { + hval = 1; + goto num_index_list; + } else if (Z_TYPE_P(offset) == IS_RESOURCE) { + zend_error(E_NOTICE, "Resource ID#%pd used as offset, casting to integer (%pd)", Z_RES_HANDLE_P(offset), Z_RES_HANDLE_P(offset)); + hval = Z_RES_HANDLE_P(offset); + } else { + zend_error(E_WARNING, "Illegal offset type"); + } + } else if (IS_CV != IS_CONST && + UNEXPECTED(Z_TYPE_P(container) == IS_OBJECT) && + EXPECTED(Z_OBJ_HT_P(container)->read_dimension)) { + zval *result = EX_VAR(opline->result.var); + zval *retval = Z_OBJ_HT_P(container)->read_dimension(container, offset, BP_VAR_R, result); + + if (retval) { + if (result != retval) { + ZVAL_COPY(result, retval); + } + } else { + ZVAL_NULL(result); + } + } else if ((IS_CV & (IS_VAR|IS_CV)) && Z_TYPE_P(container) == IS_REFERENCE) { + container = Z_REFVAL_P(container); + goto try_fetch_list; + } else { + if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_P(container) == IS_UNDEF)) { + GET_OP1_UNDEF_CV(container, BP_VAR_R); + } + ZVAL_NULL(EX_VAR(opline->result.var)); + } + + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); +} + static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -53638,6 +53828,101 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_IS_SPEC_TMPVAR_CV_HA ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } +static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_LIST_SPEC_TMPVAR_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + zend_free_op free_op1; + + zval *container; + zval *offset = _get_zval_ptr_cv_BP_VAR_R(execute_data, opline->op2.var); + + SAVE_OPLINE(); + container = _get_zval_ptr_var(opline->op1.var, execute_data, &free_op1); + +try_fetch_list: + if (EXPECTED(Z_TYPE_P(container) == IS_ARRAY)) { + zval *value; + zend_string *str; + zend_ulong hval; + +assign_again_list: + if (EXPECTED(Z_TYPE_P(offset) == IS_LONG)) { + hval = Z_LVAL_P(offset); +num_index_list: + value = zend_hash_index_find(Z_ARRVAL_P(container), hval); + + if (UNEXPECTED(value == NULL)) { + zend_error(E_NOTICE, "Undefined offset: " ZEND_ULONG_FMT, hval); + ZVAL_NULL(EX_VAR(opline->result.var)); + } else { + ZVAL_COPY(EX_VAR(opline->result.var), value); + } + } else if (EXPECTED(Z_TYPE_P(offset) == IS_STRING)) { + str = Z_STR_P(offset); + + if (ZEND_HANDLE_NUMERIC(str, hval)) { + goto num_index_list; + } + +str_index_list: + value = zend_hash_find(Z_ARRVAL_P(container), str); + + if (UNEXPECTED(value == NULL)) { + zend_error(E_NOTICE, "Undefined index: %s", ZSTR_VAL(str)); + ZVAL_NULL(EX_VAR(opline->result.var)); + } else { + ZVAL_COPY(EX_VAR(opline->result.var), value); + } + if (UNEXPECTED(str != Z_STR_P(offset))) { + zend_string_release(str); + } + } else if (EXPECTED(Z_TYPE_P(offset) == IS_REFERENCE)) { + offset = Z_REFVAL_P(offset); + goto assign_again_list; + } else if (Z_TYPE_P(offset) == IS_NULL) { + str = ZSTR_EMPTY_ALLOC(); + goto str_index_list; + } else if (Z_TYPE_P(offset) == IS_DOUBLE) { + hval = zend_dval_to_lval(Z_DVAL_P(offset)); + goto num_index_list; + } else if (Z_TYPE_P(offset) == IS_FALSE) { + hval = 0; + goto num_index_list; + } else if (Z_TYPE_P(offset) == IS_TRUE) { + hval = 1; + goto num_index_list; + } else if (Z_TYPE_P(offset) == IS_RESOURCE) { + zend_error(E_NOTICE, "Resource ID#%pd used as offset, casting to integer (%pd)", Z_RES_HANDLE_P(offset), Z_RES_HANDLE_P(offset)); + hval = Z_RES_HANDLE_P(offset); + } else { + zend_error(E_WARNING, "Illegal offset type"); + } + } else if ((IS_TMP_VAR|IS_VAR) != IS_CONST && + UNEXPECTED(Z_TYPE_P(container) == IS_OBJECT) && + EXPECTED(Z_OBJ_HT_P(container)->read_dimension)) { + zval *result = EX_VAR(opline->result.var); + zval *retval = Z_OBJ_HT_P(container)->read_dimension(container, offset, BP_VAR_R, result); + + if (retval) { + if (result != retval) { + ZVAL_COPY(result, retval); + } + } else { + ZVAL_NULL(result); + } + } else if (((IS_TMP_VAR|IS_VAR) & (IS_VAR|IS_CV)) && Z_TYPE_P(container) == IS_REFERENCE) { + container = Z_REFVAL_P(container); + goto try_fetch_list; + } else { + if ((IS_TMP_VAR|IS_VAR) == IS_CV && UNEXPECTED(Z_TYPE_P(container) == IS_UNDEF)) { + GET_OP1_UNDEF_CV(container, BP_VAR_R); + } + ZVAL_NULL(EX_VAR(opline->result.var)); + } + + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); +} + static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FAST_CONCAT_SPEC_TMPVAR_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -57936,17 +58221,17 @@ void zend_init_opcodes_handlers(void) ZEND_FETCH_LIST_SPEC_CONST_TMPVAR_HANDLER, ZEND_FETCH_LIST_SPEC_CONST_TMPVAR_HANDLER, ZEND_NULL_HANDLER, - ZEND_NULL_HANDLER, + ZEND_FETCH_LIST_SPEC_CONST_CV_HANDLER, ZEND_FETCH_LIST_SPEC_TMPVAR_CONST_HANDLER, ZEND_FETCH_LIST_SPEC_TMPVAR_TMPVAR_HANDLER, ZEND_FETCH_LIST_SPEC_TMPVAR_TMPVAR_HANDLER, ZEND_NULL_HANDLER, - ZEND_NULL_HANDLER, + ZEND_FETCH_LIST_SPEC_TMPVAR_CV_HANDLER, ZEND_FETCH_LIST_SPEC_TMPVAR_CONST_HANDLER, ZEND_FETCH_LIST_SPEC_TMPVAR_TMPVAR_HANDLER, ZEND_FETCH_LIST_SPEC_TMPVAR_TMPVAR_HANDLER, ZEND_NULL_HANDLER, - ZEND_NULL_HANDLER, + ZEND_FETCH_LIST_SPEC_TMPVAR_CV_HANDLER, ZEND_NULL_HANDLER, ZEND_NULL_HANDLER, ZEND_NULL_HANDLER, @@ -57956,7 +58241,7 @@ void zend_init_opcodes_handlers(void) ZEND_FETCH_LIST_SPEC_CV_TMPVAR_HANDLER, ZEND_FETCH_LIST_SPEC_CV_TMPVAR_HANDLER, ZEND_NULL_HANDLER, - ZEND_NULL_HANDLER, + ZEND_FETCH_LIST_SPEC_CV_CV_HANDLER, ZEND_NULL_HANDLER, ZEND_NULL_HANDLER, ZEND_NULL_HANDLER, From ed3592e80c5231d9e9a95558aa768a42b75bdebc Mon Sep 17 00:00:00 2001 From: Andrea Faulds Date: Wed, 24 Feb 2016 12:42:04 +0000 Subject: [PATCH 09/13] Add test for evaluation order --- Zend/tests/list_keyed_evaluation_order.phpt | 85 +++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 Zend/tests/list_keyed_evaluation_order.phpt diff --git a/Zend/tests/list_keyed_evaluation_order.phpt b/Zend/tests/list_keyed_evaluation_order.phpt new file mode 100644 index 0000000000000..f81f7e36e797f --- /dev/null +++ b/Zend/tests/list_keyed_evaluation_order.phpt @@ -0,0 +1,85 @@ +--TEST-- +list() with keys, evaluation order +--FILE-- + $b, $c => $d) = $e; +// Should be evaluated in the order: +// 1. Evaluate $e +// 2. Evaluate $a +// 3. Evaluate $e[$a] +// 4. Assign $b from $e[$a] +// 5. Evaluate $c +// 6. Evaluate $e[$c] +// 7. Assign $c from $e[$a] + +// In order to observe this evaluation order, let's use some observer objects! + +class Stringable +{ + private $name; + public function __construct(string $name) { + $this->name = $name; + } + + public function __toString(): string { + echo "$this->name evaluated.", PHP_EOL; + return $this->name; + } +} + +class Indexable implements ArrayAccess +{ + public function offsetExists($offset): bool { + echo "Existence of offset $offset checked for.", PHP_EOL; + return true; + } + + public function offsetUnset($offset): void { + echo "Offset $offset removed.", PHP_EOL; + } + + public function offsetGet($offset) { + echo "Offset $offset retrieved.", PHP_EOL; + return "value for offset $offset"; + } + + public function offsetSet($offset, $value): void { + echo "Offset $offset set to $value.", PHP_EOL; + } +} + +class IndexableRetrievable +{ + private $label; + private $indexable; + + public function __construct(string $label, Indexable $indexable) { + $this->label = $label; + $this->indexable = $indexable; + } + + public function getIndexable(): Indexable { + echo "Indexable $this->label retrieved.", PHP_EOL; + return $this->indexable; + } +} + +$a = new Stringable("A"); +$c = new Stringable("C"); + +$e = new IndexableRetrievable("E", new Indexable(["A" => "value for A", "C" => "value for C"])); + +$store = new Indexable; + +list((string)$a => $store["B"], (string)$c => $store["D"]) = $e->getIndexable(); + +?> +--EXPECT-- +Indexable E retrieved. +A evaluated. +Offset A retrieved. +Offset B set to value for offset A. +C evaluated. +Offset C retrieved. +Offset D set to value for offset C. From 70942e4c3cbb6b4fe6305b27e1e1b2bed78e76df Mon Sep 17 00:00:00 2001 From: Andrea Faulds Date: Wed, 24 Feb 2016 13:12:26 +0000 Subject: [PATCH 10/13] Add test for evaluation order of nested list() keys --- Zend/tests/list_keyed_evaluation_order.inc | 60 +++++++++++++++ Zend/tests/list_keyed_evaluation_order.phpt | 68 +++------------- .../list_keyed_evaluation_order_nested.phpt | 77 +++++++++++++++++++ 3 files changed, 146 insertions(+), 59 deletions(-) create mode 100644 Zend/tests/list_keyed_evaluation_order.inc create mode 100644 Zend/tests/list_keyed_evaluation_order_nested.phpt diff --git a/Zend/tests/list_keyed_evaluation_order.inc b/Zend/tests/list_keyed_evaluation_order.inc new file mode 100644 index 0000000000000..490a6d84fe97b --- /dev/null +++ b/Zend/tests/list_keyed_evaluation_order.inc @@ -0,0 +1,60 @@ +name = $name; + } + + public function __toString(): string { + echo "$this->name evaluated.", PHP_EOL; + return $this->name; + } +} + +class Indexable implements ArrayAccess +{ + private $array; + public function __construct(array $array) { + $this->array = $array; + } + + public function offsetExists($offset): bool { + echo "Existence of offset $offset checked for.", PHP_EOL; + return isset($this->array[$offset]); + } + + public function offsetUnset($offset): void { + unset($this->array[$offset]); + echo "Offset $offset removed.", PHP_EOL; + } + + public function offsetGet($offset) { + echo "Offset $offset retrieved.", PHP_EOL; + return $this->array[$offset]; + } + + public function offsetSet($offset, $value): void { + $this->array[$offset] = $value; + echo "Offset $offset set to $value.", PHP_EOL; + } +} + +class IndexableRetrievable +{ + private $label; + private $indexable; + + public function __construct(string $label, Indexable $indexable) { + $this->label = $label; + $this->indexable = $indexable; + } + + public function getIndexable(): Indexable { + echo "Indexable $this->label retrieved.", PHP_EOL; + return $this->indexable; + } +} diff --git a/Zend/tests/list_keyed_evaluation_order.phpt b/Zend/tests/list_keyed_evaluation_order.phpt index f81f7e36e797f..0f0652b6a9b43 100644 --- a/Zend/tests/list_keyed_evaluation_order.phpt +++ b/Zend/tests/list_keyed_evaluation_order.phpt @@ -3,6 +3,15 @@ list() with keys, evaluation order --FILE-- "value for offset A", "C" => "value for offset C"])); + +$store = new Indexable([]); + // list($a => $b, $c => $d) = $e; // Should be evaluated in the order: // 1. Evaluate $e @@ -13,65 +22,6 @@ list() with keys, evaluation order // 6. Evaluate $e[$c] // 7. Assign $c from $e[$a] -// In order to observe this evaluation order, let's use some observer objects! - -class Stringable -{ - private $name; - public function __construct(string $name) { - $this->name = $name; - } - - public function __toString(): string { - echo "$this->name evaluated.", PHP_EOL; - return $this->name; - } -} - -class Indexable implements ArrayAccess -{ - public function offsetExists($offset): bool { - echo "Existence of offset $offset checked for.", PHP_EOL; - return true; - } - - public function offsetUnset($offset): void { - echo "Offset $offset removed.", PHP_EOL; - } - - public function offsetGet($offset) { - echo "Offset $offset retrieved.", PHP_EOL; - return "value for offset $offset"; - } - - public function offsetSet($offset, $value): void { - echo "Offset $offset set to $value.", PHP_EOL; - } -} - -class IndexableRetrievable -{ - private $label; - private $indexable; - - public function __construct(string $label, Indexable $indexable) { - $this->label = $label; - $this->indexable = $indexable; - } - - public function getIndexable(): Indexable { - echo "Indexable $this->label retrieved.", PHP_EOL; - return $this->indexable; - } -} - -$a = new Stringable("A"); -$c = new Stringable("C"); - -$e = new IndexableRetrievable("E", new Indexable(["A" => "value for A", "C" => "value for C"])); - -$store = new Indexable; - list((string)$a => $store["B"], (string)$c => $store["D"]) = $e->getIndexable(); ?> diff --git a/Zend/tests/list_keyed_evaluation_order_nested.phpt b/Zend/tests/list_keyed_evaluation_order_nested.phpt new file mode 100644 index 0000000000000..8a7725d4eaaae --- /dev/null +++ b/Zend/tests/list_keyed_evaluation_order_nested.phpt @@ -0,0 +1,77 @@ +--TEST-- +list() with keys, evaluation order: nested +--FILE-- + "offset value for A", + "C" => new Indexable([ + 0 => "offset value for 0", + 1 => "offset value for 1" + ]), + "F" => new Indexable([ + "G" => "offset value for G", + "I" => "offset value for I" + ]) +])); + +$store = new Indexable([]); + +// list($a => $b, $c => list($d, $e), $f => list($g => $h, $i => $j)) = $k; +// Should be evaluated in the order: +// 1. Evaluate $k +// 2. Evaluate $a +// 3. Evaluate $k[$a] +// 4. Assign $b from $k[$a] +// 5. Evaluate $c +// 6. Evaluate $k[$c] +// 7. Evaluate $k[$c][0] +// 8. Assign $d from $k[$c][0] +// 9. Evaluate $k[$c][1] +// 10. Assign $e from $k[$c][1] +// 11. Evaluate $f +// 12. Evaluate $k[$f] +// 13. Evaluate $g +// 14. Evaluate $k[$f][$g] +// 15. Assign $h from $k[$f][$g] +// 16. Evaluate $i +// 17. Evaluate $k[$f][$i] +// 18. Assign $j from $k[$f][$i] + +list( + (string)$a => $store["B"], + (string)$c => list($store["D"], $store["E"]), + (string)$f => list( + (string)$g => $store["H"], + (string)$i => $store["J"] + ) +) = $k->getIndexable(); + +?> +--EXPECT-- +Indexable K retrieved. +A evaluated. +Offset A retrieved. +Offset B set to offset value for A. +C evaluated. +Offset C retrieved. +Offset 0 retrieved. +Offset D set to offset value for 0. +Offset 1 retrieved. +Offset E set to offset value for 1. +F evaluated. +Offset F retrieved. +G evaluated. +Offset G retrieved. +Offset H set to offset value for G. +I evaluated. +Offset I retrieved. +Offset J set to offset value for I. From e557f77eab692ed8bb18dbdff48777d80b6f6cbd Mon Sep 17 00:00:00 2001 From: Andrea Faulds Date: Fri, 25 Mar 2016 16:44:51 +0000 Subject: [PATCH 11/13] Rebuild VM --- Zend/zend_vm_execute.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 057e6944abec2..612feb9e9c8f9 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -51392,9 +51392,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_DIM_R_SPEC_TMPVAR_CONST_ { USE_OPLINE zend_free_op free_op1; - zval *container; - zval *offset = EX_CONSTANT(opline->op2); SAVE_OPLINE(); container = _get_zval_ptr_var(opline->op1.var, execute_data, &free_op1); @@ -51494,7 +51492,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_LIST_SPEC_TMPVAR_CONST_H { USE_OPLINE zend_free_op free_op1; + zval *container; + zval *offset = EX_CONSTANT(opline->op2); SAVE_OPLINE(); container = _get_zval_ptr_var(opline->op1.var, execute_data, &free_op1); From dca9d4a36c845bfe4fbcb9db18e184469110ea5a Mon Sep 17 00:00:00 2001 From: Andrea Faulds Date: Fri, 25 Mar 2016 16:45:18 +0000 Subject: [PATCH 12/13] Add tests contributed by @jesseschalken --- Zend/tests/list_keyed_evaluation_order_2.phpt | 77 +++++++++++++++++++ Zend/tests/list_keyed_evaluation_order_3.phpt | 24 ++++++ 2 files changed, 101 insertions(+) create mode 100644 Zend/tests/list_keyed_evaluation_order_2.phpt create mode 100644 Zend/tests/list_keyed_evaluation_order_3.phpt diff --git a/Zend/tests/list_keyed_evaluation_order_2.phpt b/Zend/tests/list_keyed_evaluation_order_2.phpt new file mode 100644 index 0000000000000..ddfba68c464de --- /dev/null +++ b/Zend/tests/list_keyed_evaluation_order_2.phpt @@ -0,0 +1,77 @@ +--TEST-- +list() with keys, evaluation order #2 +--FILE-- + $a, 1 => $b) = ['a', 'b']; +var_dump($a); +var_dump($b); + +list(1 => $b, 0 => $a) = ['a', 'b']; +var_dump($a); +var_dump($b); + +$arr = []; +list($arr[], $arr[]) = ['a', 'b']; +var_dump($arr[0]); +var_dump($arr[1]); + +$arr = []; +list(0 => $arr[], 1 => $arr[]) = ['a', 'b']; +var_dump($arr[0]); +var_dump($arr[1]); + +$arr = []; +list(1 => $arr[], 0 => $arr[]) = ['b', 'a']; +var_dump($arr[0]); +var_dump($arr[1]); + +$arr = []; +list(list(1 => $arr[], 0 => $arr[])) = [['b', 'a']]; +var_dump($arr[0]); +var_dump($arr[1]); + +$arr = []; +list('key1' => $arr[], 'key2' => $arr[]) = ['key2' => 'b', 'key1' => 'a']; +var_dump($arr[0]); +var_dump($arr[1]); + +// This should print 'foo' +$a = 0; +list($a => $a) = ['foo', 'bar']; +var_dump($a); + +// This should print 'bar' then 'foo' +$a = 0; +$b = 1; +list($b => $a, $a => $c) = ['bar' => 'foo', 1 => 'bar']; +var_dump($a); +var_dump($c); + +?> +--EXPECT-- +string(1) "a" +string(1) "b" +string(1) "a" +string(1) "b" +string(1) "a" +string(1) "b" +string(1) "a" +string(1) "b" +string(1) "a" +string(1) "b" +string(1) "a" +string(1) "b" +string(1) "a" +string(1) "b" +string(1) "a" +string(1) "b" +string(3) "foo" +string(3) "bar" +string(3) "foo" diff --git a/Zend/tests/list_keyed_evaluation_order_3.phpt b/Zend/tests/list_keyed_evaluation_order_3.phpt new file mode 100644 index 0000000000000..7850834c3b2b0 --- /dev/null +++ b/Zend/tests/list_keyed_evaluation_order_3.phpt @@ -0,0 +1,24 @@ +--TEST-- +list() with keys, evaluation order #3 +--FILE-- + [ + 'b' => 'bar', + 'a' => 'foo', + ], + 1 => 'a', + 3 => 'b', +]; +list($a[$i++] => $a[$i++], $a[$i++] => $a[$i++]) = $a[$i++]; +var_dump($i); // should be 5 +var_dump($a[2]); // should be 'foo' +var_dump($a[4]); // should be 'bar' + +?> +--EXPECT-- +int(5) +string(3) "foo" +string(3) "bar" From 0361dbe35616722fbe51b446ab7b43a9ca01f455 Mon Sep 17 00:00:00 2001 From: Andrea Faulds Date: Fri, 25 Mar 2016 16:59:20 +0000 Subject: [PATCH 13/13] UPGRADING and NEWS --- NEWS | 1 + UPGRADING | 2 ++ 2 files changed, 3 insertions(+) diff --git a/NEWS b/NEWS index fe4640391560e..604cf485dd7a3 100644 --- a/NEWS +++ b/NEWS @@ -12,6 +12,7 @@ PHP NEWS . Added void return type. (Andrea) . Added support for negative string offsets in string offset syntax and various string functions. (Francois) + . Added a form of the list() construct where keys can be specified. (Andrea) - FTP: . Implemented FR #55651 (Option to ignore the returned FTP PASV address). diff --git a/UPGRADING b/UPGRADING index fc23e3676f10a..a938978d7bf7f 100644 --- a/UPGRADING +++ b/UPGRADING @@ -35,6 +35,8 @@ PHP 7.1 UPGRADE NOTES . String offset access now supports negative references, which will be counted from the end of the string. (RFC: https://wiki.php.net/rfc/negative-string-offsets) + . Added a form of the list() construct where keys can be specified. + (RFC: https://wiki.php.net/rfc/list_keys) ======================================== 3. Changes in SAPI modules