summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNikita Popov <nikita.ppv@gmail.com>2020-01-03 15:35:10 +0100
committerNikita Popov <nikita.ppv@gmail.com>2020-01-06 18:47:27 +0100
commit846b6479537a112d1ded725e6484e46462048b35 (patch)
tree10a6c533aa98c75eef23a2d37ef07dbbc8f6ba75
parent56306cc4af0f7fad8c50d56bfc95a05e0ed920b6 (diff)
downloadphp-git-846b6479537a112d1ded725e6484e46462048b35.tar.gz
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.
-rw-r--r--NEWS2
-rw-r--r--ext/standard/tests/serialize/sleep_uninitialized_typed_prop.phpt60
-rw-r--r--ext/standard/var.c48
3 files changed, 101 insertions, 9 deletions
diff --git a/NEWS b/NEWS
index 0a4ccbd28b..9a28926ff6 100644
--- 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
index 0000000000..3d78e11f28
--- /dev/null
+++ b/ext/standard/tests/serialize/sleep_uninitialized_typed_prop.phpt
@@ -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)
+}
diff --git a/ext/standard/var.c b/ext/standard/var.c
index b611b121c9..c816dd05f9 100644
--- a/ext/standard/var.c
+++ b/ext/standard/var.c
@@ -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);
}
/* }}} */