Skip to content

Change __sleep serialization for uninitialized typed properties #5027

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
--TEST--
__sleep() returning undefined declared typed properties
--FILE--
<?php

class Test {
public int $a;
public int $b;
public int $c;
public int $d;
public int $e;
public ?int $f;
public int $g;
public iterable $h;
public ?ArrayObject $i;
public stdClass $j;

public function __construct() {
unset($this->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)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
--TEST--
__sleep() returning undefined declared typed properties
--FILE--
<?php

class Test {
public int $pub;
protected ?int $prot;
private stdClass $priv;

public function __construct() {
unset($this->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)
}
47 changes: 41 additions & 6 deletions ext/standard/var.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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;
}
}
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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;
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should be special-casing typed properties here. Instead we should always omit the property if it doesn't exist, rather than setting it to a null value.

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);
}
Expand Down Expand Up @@ -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));

Expand Down