From 04f2f4587a7c2d46e5403e3b91edc2cbb08d4bee Mon Sep 17 00:00:00 2001 From: Tyson Andre Date: Thu, 19 Dec 2019 18:55:28 -0500 Subject: [PATCH] Change __sleep serialization for uninitialized typed properties Proposed fix for https://bugs.php.net/79002 **This will change the behavior of php 7.4 applications that use __sleep with typed properties.** I think this is an improvement, because if `serialize()` returned a string, I'd expect unserialize() to return equivalent data unless wakeup/unserialize/__unserialize threw or misbehaved. Previously, a TypeError would be thrown on unserialize. PHP 7.0 is able to handle leading 0s in the number of properties of an object. I haven't tested earlier versions. --- ...efined_declared_many_typed_properties.phpt | 117 ++++++++++++++++++ ...p_undefined_declared_typed_properties.phpt | 38 ++++++ ext/standard/var.c | 47 ++++++- 3 files changed, 196 insertions(+), 6 deletions(-) create mode 100644 ext/standard/tests/serialize/sleep_undefined_declared_many_typed_properties.phpt create mode 100644 ext/standard/tests/serialize/sleep_undefined_declared_typed_properties.phpt diff --git a/ext/standard/tests/serialize/sleep_undefined_declared_many_typed_properties.phpt b/ext/standard/tests/serialize/sleep_undefined_declared_many_typed_properties.phpt new file mode 100644 index 0000000000000..734899721a22e --- /dev/null +++ b/ext/standard/tests/serialize/sleep_undefined_declared_many_typed_properties.phpt @@ -0,0 +1,117 @@ +--TEST-- +__sleep() returning undefined declared typed properties +--FILE-- +a,$this->b,$this->c,$this->d,$this->e,$this->f,$this->g,$this->h,$this->i,$this->j); + } + + public function __sleep() { + return ['a','b','c','d','e','f','g','h','i','j']; + } +} + +var_dump($s = serialize(new Test)); +var_dump(unserialize($s)); +$t = new Test(); +$t->a = 1234; +$t->f = null; +var_dump($s = serialize($t)); +var_dump(unserialize($s)); + +?> +--EXPECTF-- +Notice: serialize(): "a" returned as member variable from __sleep() but does not exist in %s on line %d + +Notice: serialize(): "b" returned as member variable from __sleep() but does not exist in %s on line %d + +Notice: serialize(): "c" returned as member variable from __sleep() but does not exist in %s on line %d + +Notice: serialize(): "d" returned as member variable from __sleep() but does not exist in %s on line %d + +Notice: serialize(): "e" returned as member variable from __sleep() but does not exist in %s on line %d + +Notice: serialize(): "f" returned as member variable from __sleep() but does not exist in %s on line %d + +Notice: serialize(): "g" returned as member variable from __sleep() but does not exist in %s on line %d + +Notice: serialize(): "h" returned as member variable from __sleep() but does not exist in %s on line %d + +Notice: serialize(): "i" returned as member variable from __sleep() but does not exist in %s on line %d + +Notice: serialize(): "j" returned as member variable from __sleep() but does not exist in %s on line %d +string(16) "O:4:"Test":00:{}" +object(Test)#1 (0) { + ["a"]=> + uninitialized(int) + ["b"]=> + uninitialized(int) + ["c"]=> + uninitialized(int) + ["d"]=> + uninitialized(int) + ["e"]=> + uninitialized(int) + ["f"]=> + uninitialized(?int) + ["g"]=> + uninitialized(int) + ["h"]=> + uninitialized(iterable) + ["i"]=> + uninitialized(?ArrayObject) + ["j"]=> + uninitialized(stdClass) +} + +Notice: serialize(): "b" returned as member variable from __sleep() but does not exist in %s on line %d + +Notice: serialize(): "c" returned as member variable from __sleep() but does not exist in %s on line %d + +Notice: serialize(): "d" returned as member variable from __sleep() but does not exist in %s on line %d + +Notice: serialize(): "e" returned as member variable from __sleep() but does not exist in %s on line %d + +Notice: serialize(): "g" returned as member variable from __sleep() but does not exist in %s on line %d + +Notice: serialize(): "h" returned as member variable from __sleep() but does not exist in %s on line %d + +Notice: serialize(): "i" returned as member variable from __sleep() but does not exist in %s on line %d + +Notice: serialize(): "j" returned as member variable from __sleep() but does not exist in %s on line %d +string(41) "O:4:"Test":02:{s:1:"a";i:1234;s:1:"f";N;}" +object(Test)#2 (2) { + ["a"]=> + int(1234) + ["b"]=> + uninitialized(int) + ["c"]=> + uninitialized(int) + ["d"]=> + uninitialized(int) + ["e"]=> + uninitialized(int) + ["f"]=> + NULL + ["g"]=> + uninitialized(int) + ["h"]=> + uninitialized(iterable) + ["i"]=> + uninitialized(?ArrayObject) + ["j"]=> + uninitialized(stdClass) +} diff --git a/ext/standard/tests/serialize/sleep_undefined_declared_typed_properties.phpt b/ext/standard/tests/serialize/sleep_undefined_declared_typed_properties.phpt new file mode 100644 index 0000000000000..99116989868da --- /dev/null +++ b/ext/standard/tests/serialize/sleep_undefined_declared_typed_properties.phpt @@ -0,0 +1,38 @@ +--TEST-- +__sleep() returning undefined declared typed properties +--FILE-- +pub, $this->prot, $this->priv); + } + + public function __sleep() { + return ['pub', 'prot', 'priv']; + } +} + +var_dump($s = serialize(new Test)); +var_dump(unserialize($s)); + +?> +--EXPECTF-- +Notice: serialize(): "pub" returned as member variable from __sleep() but does not exist in %s on line %d + +Notice: serialize(): "prot" returned as member variable from __sleep() but does not exist in %s on line %d + +Notice: serialize(): "priv" returned as member variable from __sleep() but does not exist in %s on line %d +string(15) "O:4:"Test":0:{}" +object(Test)#1 (0) { + ["pub"]=> + uninitialized(int) + ["prot":protected]=> + uninitialized(?int) + ["priv":"Test":private]=> + uninitialized(stdClass) +} diff --git a/ext/standard/var.c b/ext/standard/var.c index c6e9bde9f2cb4..0d21cadb66e5d 100644 --- a/ext/standard/var.c +++ b/ext/standard/var.c @@ -796,20 +796,37 @@ static void php_var_serialize_collect_names(HashTable *ht, HashTable *src) /* {{ } /* }}} */ +static void rewrite_number(zend_string *s, size_t smart_str_offset, uint32_t original_num, uint32_t new_num) { /* }}} */ + /* Replace a positive integer such as "100" with a smaller non-negative integer representation such as "009" in-place, with leading zeroes. */ + ZEND_ASSERT(new_num < original_num); + char* data = &ZSTR_VAL(s)[smart_str_offset]; + while (original_num > 0) { + *(data++) = '0'; + original_num /= 10; + } + while (new_num > 0) { + *(--data) = '0' + (new_num % 10); + new_num /= 10; + } +} +/* }}} */ + static void php_var_serialize_class(smart_str *buf, zval *struc, zval *retval_ptr, php_serialize_data_t var_hash) /* {{{ */ { zend_class_entry *ce = Z_OBJCE_P(struc); HashTable names, *propers; - zval nval; zend_string *name; + uint32_t num_elements; + size_t smart_str_offset; php_var_serialize_class_name(buf, struc); php_var_serialize_collect_names(&names, HASH_OF(retval_ptr)); - smart_str_append_unsigned(buf, zend_hash_num_elements(&names)); + smart_str_offset = ZSTR_LEN(buf->s); /* buf->s is definitely initialized to contain "O:" */ + num_elements = zend_hash_num_elements(&names); + smart_str_append_unsigned(buf, num_elements); smart_str_appendl(buf, ":{", 2); - ZVAL_NULL(&nval); propers = zend_get_properties_for(struc, ZEND_PROP_PURPOSE_SERIALIZE); ZEND_HASH_FOREACH_STR_KEY(&names, name) { @@ -818,8 +835,10 @@ static void php_var_serialize_class(smart_str *buf, zval *struc, zval *retval_pt zval *val = zend_hash_find_ex(propers, name, 1); if (val != NULL) { if (Z_TYPE_P(val) == IS_INDIRECT) { + zval *old_val = val; val = Z_INDIRECT_P(val); if (Z_TYPE_P(val) == IS_UNDEF) { + val = old_val; goto undef_prop; } } @@ -834,8 +853,10 @@ static void php_var_serialize_class(smart_str *buf, zval *struc, zval *retval_pt val = zend_hash_find(propers, priv_name); if (val != NULL) { if (Z_TYPE_P(val) == IS_INDIRECT) { + zval *old_val = val; val = Z_INDIRECT_P(val); if (Z_ISUNDEF_P(val)) { + val = old_val; zend_string_free(priv_name); goto undef_prop; } @@ -853,8 +874,10 @@ static void php_var_serialize_class(smart_str *buf, zval *struc, zval *retval_pt val = zend_hash_find(propers, prot_name); if (val != NULL) { if (Z_TYPE_P(val) == IS_INDIRECT) { + zval *old_val = val; val = Z_INDIRECT_P(val); if (Z_TYPE_P(val) == IS_UNDEF) { + val = old_val; zend_string_free(prot_name); goto undef_prop; } @@ -868,13 +891,25 @@ static void php_var_serialize_class(smart_str *buf, zval *struc, zval *retval_pt zend_string_free(prot_name); undef_prop: - php_var_serialize_string(buf, ZSTR_VAL(name), ZSTR_LEN(name)); - php_var_serialize_intern(buf, &nval, var_hash); php_error_docref(NULL, E_NOTICE, "\"%s\" returned as member variable from __sleep() but does not exist", ZSTR_VAL(name)); + if (val != NULL) { + ZEND_ASSERT(Z_TYPE_P(val) == IS_INDIRECT); + zend_property_info *info = zend_get_typed_property_info_for_slot(Z_OBJ_P(struc), Z_INDIRECT_P(val)); + if (UNEXPECTED(info)) { + num_elements--; + continue; + } + } + php_var_serialize_string(buf, ZSTR_VAL(name), ZSTR_LEN(name)); + smart_str_appendl(buf, "N;", 2); } ZEND_HASH_FOREACH_END(); smart_str_appendc(buf, '}'); + if (UNEXPECTED(num_elements != zend_hash_num_elements(&names))) { + rewrite_number(buf->s, smart_str_offset, zend_hash_num_elements(&names), num_elements); + } + zend_hash_destroy(&names); zend_release_properties(propers); } @@ -1010,7 +1045,7 @@ static void php_var_serialize_intern(smart_str *buf, zval *struc, php_serialize_ if (ce != PHP_IC_ENTRY && zend_hash_str_exists(&ce->function_table, "__sleep", sizeof("__sleep")-1)) { zval retval, tmp; - + Z_ADDREF_P(struc); ZVAL_OBJ(&tmp, Z_OBJ_P(struc));