]> granicus.if.org Git - php/commitdiff
Support list($a, $b) = $a
authorNikita Popov <nikic@php.net>
Tue, 24 Feb 2015 17:57:14 +0000 (18:57 +0100)
committerNikita Popov <nikic@php.net>
Tue, 24 Feb 2015 18:08:23 +0000 (19:08 +0100)
By compiling the RHS $a as a non-CV fetch.

This worked as expected in PHP 5, but broke in PHP 7 due to the
different assign order. However the new implementation is more
general, in that it allows $a on the LHS in any place, not just
the first element.

Zend/tests/list_self_assign.phpt [new file with mode: 0644]
Zend/zend_compile.c

diff --git a/Zend/tests/list_self_assign.phpt b/Zend/tests/list_self_assign.phpt
new file mode 100644 (file)
index 0000000..4640912
--- /dev/null
@@ -0,0 +1,56 @@
+--TEST--
+Test variable occuring on both LHS and RHS of list()
+--FILE--
+<?php
+
+$a = [1, 2, 3];
+list($a, $b, $c) = $a;
+var_dump($a, $b, $c);
+
+$b = [1, 2, 3];
+list($a, $b, $c) = $b;
+var_dump($a, $b, $c);
+
+$c = [1, 2, 3];
+list($a, $b, $c) = $c;
+var_dump($a, $b, $c);
+
+$a = [[1, 2], 3];
+list(list($a, $b), $c) = $a;
+var_dump($a, $b, $c);
+
+$b = [[1, 2], 3];
+list(list($a, $b), $c) = $b;
+var_dump($a, $b, $c);
+
+$b = [1, [2, 3]];
+list($a, list($b, $c)) = $b;
+var_dump($a, $b, $c);
+
+$c = [1, [2, 3]];
+list($a, list($b, $c)) = $c;
+var_dump($a, $b, $c);
+
+?>
+--EXPECT--
+int(1)
+int(2)
+int(3)
+int(1)
+int(2)
+int(3)
+int(1)
+int(2)
+int(3)
+int(1)
+int(2)
+int(3)
+int(1)
+int(2)
+int(3)
+int(1)
+int(2)
+int(3)
+int(1)
+int(2)
+int(3)
index 9db4f284afa766543e163e79ac0b6402d4b2779f..e7177c7cd056ffba1fc10f9693366b0785c76251 100644 (file)
@@ -2316,6 +2316,50 @@ zend_bool zend_is_assign_to_self(zend_ast *var_ast, zend_ast *expr_ast) /* {{{ *
 }
 /* }}} */
 
+/* Detects if list($a, $b, $c) contains variable with given name */
+zend_bool zend_list_has_assign_to(zend_ast *list_ast, zend_string *name) /* {{{ */
+{
+       zend_ast_list *list = zend_ast_get_list(list_ast);
+       uint32_t i;
+       for (i = 0; i < list->children; i++) {
+               zend_ast *var_ast = list->child[i];
+               if (!var_ast) {
+                       continue;
+               }
+
+               /* Recursively check nested list()s */
+               if (var_ast->kind == ZEND_AST_LIST && zend_list_has_assign_to(var_ast, name)) {
+                       return 1;
+               }
+
+               if (var_ast->kind == ZEND_AST_VAR && var_ast->child[0]->kind == ZEND_AST_ZVAL) {
+                       zend_string *var_name = zval_get_string(zend_ast_get_zval(var_ast->child[0]));
+                       zend_bool result = zend_string_equals(var_name, name);
+                       zend_string_release(var_name);
+                       if (result) {
+                               return 1;
+                       }
+               }
+       }
+
+       return 0;
+}
+/* }}} */
+
+/* Detects patterns like list($a, $b, $c) = $a */
+zend_bool zend_list_has_assign_to_self(zend_ast *list_ast, zend_ast *expr_ast) /* {{{ */
+{
+       /* Only check simple variables on the RHS, as only CVs cause issues with this. */
+       if (expr_ast->kind == ZEND_AST_VAR && expr_ast->child[0]->kind == ZEND_AST_ZVAL) {
+               zend_string *name = zval_get_string(zend_ast_get_zval(expr_ast->child[0]));
+               zend_bool result = zend_list_has_assign_to(list_ast, name);
+               zend_string_release(name);
+               return result;
+       }
+       return 0;
+}
+/* }}} */
+
 void zend_compile_assign(znode *result, zend_ast *ast) /* {{{ */
 {
        zend_ast *var_ast = ast->child[0];
@@ -2365,7 +2409,13 @@ void zend_compile_assign(znode *result, zend_ast *ast) /* {{{ */
                        zend_emit_op_data(&expr_node);
                        return;
                case ZEND_AST_LIST:
-                       zend_compile_expr(&expr_node, expr_ast);
+                       if (zend_list_has_assign_to_self(var_ast, expr_ast)) {
+                               /* list($a, $b) = $a should evaluate the right $a first */
+                               zend_compile_simple_var_no_cv(&expr_node, expr_ast, BP_VAR_R, 0);
+                       } else {
+                               zend_compile_expr(&expr_node, expr_ast);
+                       }
+
                        zend_compile_list_assign(result, var_ast, &expr_node);
                        return;
                EMPTY_SWITCH_DEFAULT_CASE();