Skip to content

Commit 846b647

Browse files
committed
Throw Error when referencing uninit typed prop in __sleep
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.
1 parent 56306cc commit 846b647

File tree

3 files changed

+101
-9
lines changed

3 files changed

+101
-9
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ PHP NEWS
1616
(Dmitry)
1717
. Fixed bug #79008 (General performance regression with PHP 7.4 on Windows).
1818
(cmb)
19+
. Fixed bug #79002 (Serializing uninitialized typed properties with __sleep
20+
makes unserialize throw). (Nikita)
1921

2022
- CURL:
2123
. Fixed bug #79033 (Curl timeout error with specific url and post). (cmb)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
--TEST--
2+
Referencing an uninitialized typed property in __sleep() should result in Error
3+
--FILE--
4+
<?php
5+
6+
class Test {
7+
public int $x;
8+
protected int $y;
9+
private int $z;
10+
11+
public function __sleep() {
12+
return ['x', 'y', 'z'];
13+
}
14+
15+
public function __set($name, $val) {
16+
$this->$name = $val;
17+
}
18+
}
19+
20+
$t = new Test;
21+
try {
22+
serialize($t);
23+
} catch (Error $e) {
24+
echo $e->getMessage(), "\n";
25+
}
26+
27+
$t->x = 1;
28+
try {
29+
serialize($t);
30+
} catch (Error $e) {
31+
echo $e->getMessage(), "\n";
32+
}
33+
34+
$t->y = 2;
35+
try {
36+
serialize($t);
37+
} catch (Error $e) {
38+
echo $e->getMessage(), "\n";
39+
}
40+
41+
$t->z = 3;
42+
try {
43+
var_dump(unserialize(serialize($t)));
44+
} catch (Error $e) {
45+
echo $e->getMessage(), "\n";
46+
}
47+
48+
?>
49+
--EXPECT--
50+
Typed property Test::$x must not be accessed before initialization (in __sleep)
51+
Typed property Test::$y must not be accessed before initialization (in __sleep)
52+
Typed property Test::$z must not be accessed before initialization (in __sleep)
53+
object(Test)#3 (3) {
54+
["x"]=>
55+
int(1)
56+
["y":protected]=>
57+
int(2)
58+
["z":"Test":private]=>
59+
int(3)
60+
}

ext/standard/var.c

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -772,7 +772,7 @@ static int php_var_serialize_call_magic_serialize(zval *retval, zval *obj) /* {{
772772
/* }}} */
773773

774774
static int php_var_serialize_try_add_sleep_prop(
775-
HashTable *ht, HashTable *props, zend_string *name, zend_string *error_name) /* {{{ */
775+
HashTable *ht, HashTable *props, zend_string *name, zend_string *error_name, zval *struc) /* {{{ */
776776
{
777777
zval *val = zend_hash_find(props, name);
778778
if (val == NULL) {
@@ -782,6 +782,12 @@ static int php_var_serialize_try_add_sleep_prop(
782782
if (Z_TYPE_P(val) == IS_INDIRECT) {
783783
val = Z_INDIRECT_P(val);
784784
if (Z_TYPE_P(val) == IS_UNDEF) {
785+
zend_property_info *info = zend_get_typed_property_info_for_slot(Z_OBJ_P(struc), val);
786+
if (info) {
787+
zend_throw_error(NULL,
788+
"Typed property %s::$%s must not be accessed before initialization (in __sleep)",
789+
ZSTR_VAL(Z_OBJCE_P(struc)->name), ZSTR_VAL(error_name));
790+
}
785791
return FAILURE;
786792
}
787793
}
@@ -797,14 +803,17 @@ static int php_var_serialize_try_add_sleep_prop(
797803
}
798804
/* }}} */
799805

800-
static void php_var_serialize_get_sleep_props(
806+
static int php_var_serialize_get_sleep_props(
801807
HashTable *ht, zval *struc, HashTable *sleep_retval) /* {{{ */
802808
{
803809
zend_class_entry *ce = Z_OBJCE_P(struc);
804810
HashTable *props = zend_get_properties_for(struc, ZEND_PROP_PURPOSE_SERIALIZE);
805811
zval *name_val;
812+
int retval = SUCCESS;
806813

807814
zend_hash_init(ht, zend_hash_num_elements(sleep_retval), NULL, ZVAL_PTR_DTOR, 0);
815+
/* TODO: Rewrite this by fetching the property info instead of trying out different
816+
* name manglings? */
808817
ZEND_HASH_FOREACH_VAL(sleep_retval, name_val) {
809818
zend_string *name, *tmp_name, *priv_name, *prot_name;
810819

@@ -815,36 +824,56 @@ static void php_var_serialize_get_sleep_props(
815824
}
816825

817826
name = zval_get_tmp_string(name_val, &tmp_name);
818-
if (php_var_serialize_try_add_sleep_prop(ht, props, name, name) == SUCCESS) {
827+
if (php_var_serialize_try_add_sleep_prop(ht, props, name, name, struc) == SUCCESS) {
819828
zend_tmp_string_release(tmp_name);
820829
continue;
821830
}
822831

832+
if (EG(exception)) {
833+
zend_tmp_string_release(tmp_name);
834+
retval = FAILURE;
835+
break;
836+
}
837+
823838
priv_name = zend_mangle_property_name(
824839
ZSTR_VAL(ce->name), ZSTR_LEN(ce->name),
825840
ZSTR_VAL(name), ZSTR_LEN(name), ce->type & ZEND_INTERNAL_CLASS);
826-
if (php_var_serialize_try_add_sleep_prop(ht, props, priv_name, name) == SUCCESS) {
841+
if (php_var_serialize_try_add_sleep_prop(ht, props, priv_name, name, struc) == SUCCESS) {
827842
zend_tmp_string_release(tmp_name);
828843
zend_string_release(priv_name);
829844
continue;
830845
}
831846
zend_string_release(priv_name);
832847

848+
if (EG(exception)) {
849+
zend_tmp_string_release(tmp_name);
850+
retval = FAILURE;
851+
break;
852+
}
853+
833854
prot_name = zend_mangle_property_name(
834855
"*", 1, ZSTR_VAL(name), ZSTR_LEN(name), ce->type & ZEND_INTERNAL_CLASS);
835-
if (php_var_serialize_try_add_sleep_prop(ht, props, prot_name, name) == SUCCESS) {
856+
if (php_var_serialize_try_add_sleep_prop(ht, props, prot_name, name, struc) == SUCCESS) {
836857
zend_tmp_string_release(tmp_name);
837858
zend_string_release(prot_name);
838859
continue;
839860
}
840861
zend_string_release(prot_name);
841862

863+
if (EG(exception)) {
864+
zend_tmp_string_release(tmp_name);
865+
retval = FAILURE;
866+
break;
867+
}
868+
842869
php_error_docref(NULL, E_NOTICE,
843870
"\"%s\" returned as member variable from __sleep() but does not exist", ZSTR_VAL(name));
844871
zend_hash_add(ht, name, &EG(uninitialized_zval));
845872
zend_tmp_string_release(tmp_name);
846873
} ZEND_HASH_FOREACH_END();
874+
847875
zend_release_properties(props);
876+
return retval;
848877
}
849878
/* }}} */
850879

@@ -900,10 +929,11 @@ static void php_var_serialize_nested_data(smart_str *buf, zval *struc, HashTable
900929
static void php_var_serialize_class(smart_str *buf, zval *struc, zval *retval_ptr, php_serialize_data_t var_hash) /* {{{ */
901930
{
902931
HashTable props;
903-
php_var_serialize_get_sleep_props(&props, struc, HASH_OF(retval_ptr));
904-
php_var_serialize_class_name(buf, struc);
905-
php_var_serialize_nested_data(
906-
buf, struc, &props, zend_hash_num_elements(&props), /* incomplete_class */ 0, var_hash);
932+
if (php_var_serialize_get_sleep_props(&props, struc, HASH_OF(retval_ptr)) == SUCCESS) {
933+
php_var_serialize_class_name(buf, struc);
934+
php_var_serialize_nested_data(
935+
buf, struc, &props, zend_hash_num_elements(&props), /* incomplete_class */ 0, var_hash);
936+
}
907937
zend_hash_destroy(&props);
908938
}
909939
/* }}} */

0 commit comments

Comments
 (0)