]> granicus.if.org Git - php/commitdiff
Throw Error when referencing uninit typed prop in __sleep
authorNikita Popov <nikita.ppv@gmail.com>
Fri, 3 Jan 2020 14:35:10 +0000 (15:35 +0100)
committerNikita Popov <nikita.ppv@gmail.com>
Mon, 6 Jan 2020 17:47:27 +0000 (18:47 +0100)
Previously this generated a notice, but would likely generate an
Error when unserializing.

Now we treat it with the same distinction as direct property
accesses, i.e. referencing an unset/undefined normal property
stays a notice, while a typed property becomes an Error exception.

This fixed bug #79002.

Closes GH-5050.

NEWS
ext/standard/tests/serialize/sleep_uninitialized_typed_prop.phpt [new file with mode: 0644]
ext/standard/var.c

diff --git a/NEWS b/NEWS
index 0a4ccbd28b0e936a7aaa305c09b0918cd75e31e2..9a28926ff666f5ec26945d77e658a136fbb13b06 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -16,6 +16,8 @@ PHP                                                                        NEWS
     (Dmitry)
   . Fixed bug #79008 (General performance regression with PHP 7.4 on Windows).
     (cmb)
+  . Fixed bug #79002 (Serializing uninitialized typed properties with __sleep
+    makes unserialize throw). (Nikita)
 
 - CURL:
   . Fixed bug #79033 (Curl timeout error with specific url and post). (cmb)
diff --git a/ext/standard/tests/serialize/sleep_uninitialized_typed_prop.phpt b/ext/standard/tests/serialize/sleep_uninitialized_typed_prop.phpt
new file mode 100644 (file)
index 0000000..3d78e11
--- /dev/null
@@ -0,0 +1,60 @@
+--TEST--
+Referencing an uninitialized typed property in __sleep() should result in Error
+--FILE--
+<?php
+
+class Test {
+    public int $x;
+    protected int $y;
+    private int $z;
+
+    public function __sleep() {
+        return ['x', 'y', 'z'];
+    }
+
+    public function __set($name, $val) {
+        $this->$name = $val;
+    }
+}
+
+$t = new Test;
+try {
+    serialize($t);
+} catch (Error $e) {
+    echo $e->getMessage(), "\n";
+}
+
+$t->x = 1;
+try {
+    serialize($t);
+} catch (Error $e) {
+    echo $e->getMessage(), "\n";
+}
+
+$t->y = 2;
+try {
+    serialize($t);
+} catch (Error $e) {
+    echo $e->getMessage(), "\n";
+}
+
+$t->z = 3;
+try {
+    var_dump(unserialize(serialize($t)));
+} catch (Error $e) {
+    echo $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+Typed property Test::$x must not be accessed before initialization (in __sleep)
+Typed property Test::$y must not be accessed before initialization (in __sleep)
+Typed property Test::$z must not be accessed before initialization (in __sleep)
+object(Test)#3 (3) {
+  ["x"]=>
+  int(1)
+  ["y":protected]=>
+  int(2)
+  ["z":"Test":private]=>
+  int(3)
+}
index b611b121c9e1c7fe8f3e7bd871b6281ee1b0831a..c816dd05f9e25cadcd16c633baf3e9a0493857d7 100644 (file)
@@ -772,7 +772,7 @@ static int php_var_serialize_call_magic_serialize(zval *retval, zval *obj) /* {{
 /* }}} */
 
 static int php_var_serialize_try_add_sleep_prop(
-               HashTable *ht, HashTable *props, zend_string *name, zend_string *error_name) /* {{{ */
+               HashTable *ht, HashTable *props, zend_string *name, zend_string *error_name, zval *struc) /* {{{ */
 {
        zval *val = zend_hash_find(props, name);
        if (val == NULL) {
@@ -782,6 +782,12 @@ static int php_var_serialize_try_add_sleep_prop(
        if (Z_TYPE_P(val) == IS_INDIRECT) {
                val = Z_INDIRECT_P(val);
                if (Z_TYPE_P(val) == IS_UNDEF) {
+                       zend_property_info *info = zend_get_typed_property_info_for_slot(Z_OBJ_P(struc), val);
+                       if (info) {
+                               zend_throw_error(NULL,
+                                       "Typed property %s::$%s must not be accessed before initialization (in __sleep)",
+                                       ZSTR_VAL(Z_OBJCE_P(struc)->name), ZSTR_VAL(error_name));
+                       }
                        return FAILURE;
                }
        }
@@ -797,14 +803,17 @@ static int php_var_serialize_try_add_sleep_prop(
 }
 /* }}} */
 
-static void php_var_serialize_get_sleep_props(
+static int php_var_serialize_get_sleep_props(
                HashTable *ht, zval *struc, HashTable *sleep_retval) /* {{{ */
 {
        zend_class_entry *ce = Z_OBJCE_P(struc);
        HashTable *props = zend_get_properties_for(struc, ZEND_PROP_PURPOSE_SERIALIZE);
        zval *name_val;
+       int retval = SUCCESS;
 
        zend_hash_init(ht, zend_hash_num_elements(sleep_retval), NULL, ZVAL_PTR_DTOR, 0);
+       /* TODO: Rewrite this by fetching the property info instead of trying out different
+        * name manglings? */
        ZEND_HASH_FOREACH_VAL(sleep_retval, name_val) {
                zend_string *name, *tmp_name, *priv_name, *prot_name;
 
@@ -815,36 +824,56 @@ static void php_var_serialize_get_sleep_props(
                }
 
                name = zval_get_tmp_string(name_val, &tmp_name);
-               if (php_var_serialize_try_add_sleep_prop(ht, props, name, name) == SUCCESS) {
+               if (php_var_serialize_try_add_sleep_prop(ht, props, name, name, struc) == SUCCESS) {
                        zend_tmp_string_release(tmp_name);
                        continue;
                }
 
+               if (EG(exception)) {
+                       zend_tmp_string_release(tmp_name);
+                       retval = FAILURE;
+                       break;
+               }
+
                priv_name = zend_mangle_property_name(
                        ZSTR_VAL(ce->name), ZSTR_LEN(ce->name),
                        ZSTR_VAL(name), ZSTR_LEN(name), ce->type & ZEND_INTERNAL_CLASS);
-               if (php_var_serialize_try_add_sleep_prop(ht, props, priv_name, name) == SUCCESS) {
+               if (php_var_serialize_try_add_sleep_prop(ht, props, priv_name, name, struc) == SUCCESS) {
                        zend_tmp_string_release(tmp_name);
                        zend_string_release(priv_name);
                        continue;
                }
                zend_string_release(priv_name);
 
+               if (EG(exception)) {
+                       zend_tmp_string_release(tmp_name);
+                       retval = FAILURE;
+                       break;
+               }
+
                prot_name = zend_mangle_property_name(
                        "*", 1, ZSTR_VAL(name), ZSTR_LEN(name), ce->type & ZEND_INTERNAL_CLASS);
-               if (php_var_serialize_try_add_sleep_prop(ht, props, prot_name, name) == SUCCESS) {
+               if (php_var_serialize_try_add_sleep_prop(ht, props, prot_name, name, struc) == SUCCESS) {
                        zend_tmp_string_release(tmp_name);
                        zend_string_release(prot_name);
                        continue;
                }
                zend_string_release(prot_name);
 
+               if (EG(exception)) {
+                       zend_tmp_string_release(tmp_name);
+                       retval = FAILURE;
+                       break;
+               }
+
                php_error_docref(NULL, E_NOTICE,
                        "\"%s\" returned as member variable from __sleep() but does not exist", ZSTR_VAL(name));
                zend_hash_add(ht, name, &EG(uninitialized_zval));
                zend_tmp_string_release(tmp_name);
        } ZEND_HASH_FOREACH_END();
+
        zend_release_properties(props);
+       return retval;
 }
 /* }}} */
 
@@ -900,10 +929,11 @@ static void php_var_serialize_nested_data(smart_str *buf, zval *struc, HashTable
 static void php_var_serialize_class(smart_str *buf, zval *struc, zval *retval_ptr, php_serialize_data_t var_hash) /* {{{ */
 {
        HashTable props;
-       php_var_serialize_get_sleep_props(&props, struc, HASH_OF(retval_ptr));
-       php_var_serialize_class_name(buf, struc);
-       php_var_serialize_nested_data(
-               buf, struc, &props, zend_hash_num_elements(&props), /* incomplete_class */ 0, var_hash);
+       if (php_var_serialize_get_sleep_props(&props, struc, HASH_OF(retval_ptr)) == SUCCESS) {
+               php_var_serialize_class_name(buf, struc);
+               php_var_serialize_nested_data(
+                       buf, struc, &props, zend_hash_num_elements(&props), /* incomplete_class */ 0, var_hash);
+       }
        zend_hash_destroy(&props);
 }
 /* }}} */