]> granicus.if.org Git - php/commitdiff
Fix handling of assign-ops on overloaded props with ref return
authorNikita Popov <nikita.ppv@gmail.com>
Fri, 8 Jun 2018 22:29:33 +0000 (00:29 +0200)
committerNikita Popov <nikita.ppv@gmail.com>
Fri, 8 Jun 2018 22:36:46 +0000 (00:36 +0200)
Assign-ops and incdec on overloaded properties are implemented
using a read_property followed by write_property. Previously, if
__get() returned by-reference, pre-incdec and assign-op
additionally also modified the reference, while post-incdec worked
correctly.

This change synchronizes the three code-paths to not modify the
reference. The pre-incdec implementation matches the post-incdec
implementation, the assign-op implementation uses a distinct
result operand.

Zend/tests/overloaded_prop_assign_op_refs.phpt [new file with mode: 0644]
Zend/zend_execute.c

diff --git a/Zend/tests/overloaded_prop_assign_op_refs.phpt b/Zend/tests/overloaded_prop_assign_op_refs.phpt
new file mode 100644 (file)
index 0000000..3f39f4d
--- /dev/null
@@ -0,0 +1,47 @@
+--TEST--
+Handling of assign-ops and incdecs on overloaded properties using &__get()
+--FILE--
+<?php
+
+class Test {
+    protected $a = 0;
+    protected $b = 0;
+    protected $c = 0;
+
+    public function &__get($name) {
+        echo "get($name)\n";
+        return $this->$name;
+    }
+
+    public function __set($name, $value) {
+        echo "set($name, $value)\n";
+    }
+}
+
+$test = new Test;
+
+var_dump($test->a += 1);
+var_dump($test->b++);
+var_dump(++$test->c);
+
+var_dump($test);
+
+?>
+--EXPECT--
+get(a)
+set(a, 1)
+int(1)
+get(b)
+set(b, 1)
+int(0)
+get(c)
+set(c, 1)
+int(1)
+object(Test)#1 (3) {
+  ["a":protected]=>
+  int(0)
+  ["b":protected]=>
+  int(0)
+  ["c":protected]=>
+  int(0)
+}
index e863328121b5ec7af0f438a13487aac3f54fc768..9a170edd3daeb682dfcbe9bd525298a6d298aa3d 100644 (file)
@@ -1490,11 +1490,12 @@ static zend_never_inline void zend_pre_incdec_overloaded_property(zval *object,
        zval rv;
 
        if (Z_OBJ_HT_P(object)->read_property && Z_OBJ_HT_P(object)->write_property) {
-               zval *z, *zptr, obj;
+               zval *z, obj;
+               zval z_copy;
 
                ZVAL_OBJ(&obj, Z_OBJ_P(object));
                Z_ADDREF(obj);
-               zptr = z = Z_OBJ_HT(obj)->read_property(&obj, property, BP_VAR_R, cache_slot, &rv);
+               z = Z_OBJ_HT(obj)->read_property(&obj, property, BP_VAR_R, cache_slot, &rv);
                if (UNEXPECTED(EG(exception))) {
                        OBJ_RELEASE(Z_OBJ(obj));
                        if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
@@ -1512,18 +1513,23 @@ static zend_never_inline void zend_pre_incdec_overloaded_property(zval *object,
                        }
                        ZVAL_COPY_VALUE(z, value);
                }
-               ZVAL_DEREF(z);
+               if (UNEXPECTED(Z_TYPE_P(z) == IS_REFERENCE)) {
+                       ZVAL_COPY(&z_copy, Z_REFVAL_P(z));
+               } else {
+                       ZVAL_COPY(&z_copy, z);
+               }
                if (inc) {
-                       increment_function(z);
+                       increment_function(&z_copy);
                } else {
-                       decrement_function(z);
+                       decrement_function(&z_copy);
                }
                if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
-                       ZVAL_COPY(EX_VAR(opline->result.var), z);
+                       ZVAL_COPY(EX_VAR(opline->result.var), &z_copy);
                }
-               Z_OBJ_HT(obj)->write_property(&obj, property, z, cache_slot);
+               Z_OBJ_HT(obj)->write_property(&obj, property, &z_copy, cache_slot);
                OBJ_RELEASE(Z_OBJ(obj));
-               zval_ptr_dtor(zptr);
+               zval_ptr_dtor(&z_copy);
+               zval_ptr_dtor(z);
        } else {
                zend_error(E_WARNING, "Attempt to increment/decrement property of non-object");
                if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
@@ -1535,8 +1541,7 @@ static zend_never_inline void zend_pre_incdec_overloaded_property(zval *object,
 static zend_never_inline void zend_assign_op_overloaded_property(zval *object, zval *property, void **cache_slot, zval *value, binary_op_type binary_op OPLINE_DC EXECUTE_DATA_DC)
 {
        zval *z;
-       zval rv, obj;
-       zval *zptr;
+       zval rv, obj, res;
 
        ZVAL_OBJ(&obj, Z_OBJ_P(object));
        Z_ADDREF(obj);
@@ -1558,14 +1563,13 @@ static zend_never_inline void zend_assign_op_overloaded_property(zval *object, z
                        }
                        ZVAL_COPY_VALUE(z, value);
                }
-               zptr = z;
-               ZVAL_DEREF(z);
-               binary_op(z, z, value);
-               Z_OBJ_HT(obj)->write_property(&obj, property, z, cache_slot);
+               binary_op(&res, z, value);
+               Z_OBJ_HT(obj)->write_property(&obj, property, &res, cache_slot);
                if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
-                       ZVAL_COPY(EX_VAR(opline->result.var), z);
+                       ZVAL_COPY(EX_VAR(opline->result.var), &res);
                }
-               zval_ptr_dtor(zptr);
+               zval_ptr_dtor(z);
+               zval_ptr_dtor(&res);
        } else {
                zend_error(E_WARNING, "Attempt to assign property of non-object");
                if (UNEXPECTED(RETURN_VALUE_USED(opline))) {