Skip to content

Commit 90fea67

Browse files
committed
Populate hashtable of __sleep() properties
Instead of populating a hashtable of property names and then directly serializing. This has the advantage of a) detecting duplicate properties more precisely and b) gives us the ability to discard values without rewriting the serialization string after the fact for GH-5027.
1 parent de0ca47 commit 90fea67

File tree

2 files changed

+76
-82
lines changed

2 files changed

+76
-82
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
--TEST--
2+
__sleep() returns properties clashing only after mangling
3+
--FILE--
4+
<?php
5+
class Test {
6+
private $priv;
7+
public function __sleep() {
8+
return ["\0Test\0priv", "priv"];
9+
}
10+
}
11+
$s = serialize(new Test);
12+
var_dump(str_replace("\0", '\0', $s));
13+
?>
14+
--EXPECTF--
15+
Notice: serialize(): "priv" is returned from __sleep multiple times in %s on line %d
16+
string(37) "O:4:"Test":1:{s:10:"\0Test\0priv";N;}"

ext/standard/var.c

Lines changed: 60 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -771,113 +771,80 @@ static int php_var_serialize_call_magic_serialize(zval *retval, zval *obj) /* {{
771771
}
772772
/* }}} */
773773

774-
static void php_var_serialize_collect_names(HashTable *ht, HashTable *src) /* {{{ */
774+
static int php_var_serialize_try_add_sleep_prop(
775+
HashTable *ht, HashTable *props, zend_string *name, zend_string *error_name) /* {{{ */
775776
{
776-
zval *val;
777-
zend_string *name, *tmp_name;
777+
zval *val = zend_hash_find(props, name);
778+
if (val == NULL) {
779+
return FAILURE;
780+
}
778781

779-
zend_hash_init(ht, zend_hash_num_elements(src), NULL, NULL, 0);
780-
ZEND_HASH_FOREACH_VAL(src, val) {
781-
ZVAL_DEREF(val);
782-
if (Z_TYPE_P(val) != IS_STRING) {
783-
php_error_docref(NULL, E_NOTICE,
784-
"__sleep should return an array only containing the names of instance-variables to serialize.");
782+
if (Z_TYPE_P(val) == IS_INDIRECT) {
783+
val = Z_INDIRECT_P(val);
784+
if (Z_TYPE_P(val) == IS_UNDEF) {
785+
return FAILURE;
785786
}
787+
}
786788

787-
name = zval_get_tmp_string(val, &tmp_name);
788-
if (zend_hash_exists(ht, name)) {
789-
php_error_docref(NULL, E_NOTICE,
790-
"\"%s\" is returned from __sleep multiple times", ZSTR_VAL(name));
791-
zend_tmp_string_release(tmp_name);
792-
continue;
793-
}
794-
zend_hash_add_empty_element(ht, name);
795-
zend_tmp_string_release(tmp_name);
796-
} ZEND_HASH_FOREACH_END();
789+
if (!zend_hash_add(ht, name, val)) {
790+
php_error_docref(NULL, E_NOTICE,
791+
"\"%s\" is returned from __sleep multiple times", ZSTR_VAL(error_name));
792+
return SUCCESS;
793+
}
794+
795+
Z_TRY_ADDREF_P(val);
796+
return SUCCESS;
797797
}
798798
/* }}} */
799799

800-
static void php_var_serialize_class(smart_str *buf, zval *struc, zval *retval_ptr, php_serialize_data_t var_hash) /* {{{ */
800+
static void php_var_serialize_get_sleep_props(
801+
HashTable *ht, zval *struc, HashTable *sleep_retval) /* {{{ */
801802
{
802803
zend_class_entry *ce = Z_OBJCE_P(struc);
803-
HashTable names, *propers;
804-
zval nval;
805-
zend_string *name;
804+
HashTable *props = zend_get_properties_for(struc, ZEND_PROP_PURPOSE_SERIALIZE);
805+
zval *name_val;
806806

807-
php_var_serialize_class_name(buf, struc);
808-
php_var_serialize_collect_names(&names, HASH_OF(retval_ptr));
807+
zend_hash_init(ht, zend_hash_num_elements(sleep_retval), NULL, ZVAL_PTR_DTOR, 0);
808+
ZEND_HASH_FOREACH_VAL(sleep_retval, name_val) {
809+
zend_string *name, *tmp_name, *priv_name, *prot_name;
809810

810-
smart_str_append_unsigned(buf, zend_hash_num_elements(&names));
811-
smart_str_appendl(buf, ":{", 2);
812-
813-
ZVAL_NULL(&nval);
814-
propers = zend_get_properties_for(struc, ZEND_PROP_PURPOSE_SERIALIZE);
815-
816-
ZEND_HASH_FOREACH_STR_KEY(&names, name) {
817-
zend_string *prot_name, *priv_name;
818-
819-
zval *val = zend_hash_find_ex(propers, name, 1);
820-
if (val != NULL) {
821-
if (Z_TYPE_P(val) == IS_INDIRECT) {
822-
val = Z_INDIRECT_P(val);
823-
if (Z_TYPE_P(val) == IS_UNDEF) {
824-
goto undef_prop;
825-
}
826-
}
811+
ZVAL_DEREF(name_val);
812+
if (Z_TYPE_P(name_val) != IS_STRING) {
813+
php_error_docref(NULL, E_NOTICE,
814+
"__sleep should return an array only containing the names of instance-variables to serialize.");
815+
}
827816

828-
php_var_serialize_string(buf, ZSTR_VAL(name), ZSTR_LEN(name));
829-
php_var_serialize_intern(buf, val, var_hash);
817+
name = zval_get_tmp_string(name_val, &tmp_name);
818+
if (php_var_serialize_try_add_sleep_prop(ht, props, name, name) == SUCCESS) {
819+
zend_tmp_string_release(tmp_name);
830820
continue;
831821
}
832822

833823
priv_name = zend_mangle_property_name(
834-
ZSTR_VAL(ce->name), ZSTR_LEN(ce->name), ZSTR_VAL(name), ZSTR_LEN(name), 0);
835-
val = zend_hash_find(propers, priv_name);
836-
if (val != NULL) {
837-
if (Z_TYPE_P(val) == IS_INDIRECT) {
838-
val = Z_INDIRECT_P(val);
839-
if (Z_ISUNDEF_P(val)) {
840-
zend_string_free(priv_name);
841-
goto undef_prop;
842-
}
843-
}
844-
845-
php_var_serialize_string(buf, ZSTR_VAL(priv_name), ZSTR_LEN(priv_name));
846-
zend_string_free(priv_name);
847-
php_var_serialize_intern(buf, val, var_hash);
824+
ZSTR_VAL(ce->name), ZSTR_LEN(ce->name),
825+
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) {
827+
zend_tmp_string_release(tmp_name);
828+
zend_string_release(priv_name);
848829
continue;
849830
}
850-
zend_string_free(priv_name);
831+
zend_string_release(priv_name);
851832

852833
prot_name = zend_mangle_property_name(
853-
"*", 1, ZSTR_VAL(name), ZSTR_LEN(name), 0);
854-
val = zend_hash_find(propers, prot_name);
855-
if (val != NULL) {
856-
if (Z_TYPE_P(val) == IS_INDIRECT) {
857-
val = Z_INDIRECT_P(val);
858-
if (Z_TYPE_P(val) == IS_UNDEF) {
859-
zend_string_free(prot_name);
860-
goto undef_prop;
861-
}
862-
}
863-
864-
php_var_serialize_string(buf, ZSTR_VAL(prot_name), ZSTR_LEN(prot_name));
865-
zend_string_free(prot_name);
866-
php_var_serialize_intern(buf, val, var_hash);
834+
"*", 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) {
836+
zend_tmp_string_release(tmp_name);
837+
zend_string_release(prot_name);
867838
continue;
868839
}
869-
zend_string_free(prot_name);
840+
zend_string_release(prot_name);
870841

871-
undef_prop:
872-
php_var_serialize_string(buf, ZSTR_VAL(name), ZSTR_LEN(name));
873-
php_var_serialize_intern(buf, &nval, var_hash);
874842
php_error_docref(NULL, E_NOTICE,
875-
"\"%s\" returned as member variable from __sleep() but does not exist", ZSTR_VAL(name));
843+
"\"%s\" returned as member variable from __sleep() but does not exist", ZSTR_VAL(name));
844+
zend_hash_add(ht, name, &EG(uninitialized_zval));
845+
zend_tmp_string_release(tmp_name);
876846
} ZEND_HASH_FOREACH_END();
877-
smart_str_appendc(buf, '}');
878-
879-
zend_hash_destroy(&names);
880-
zend_release_properties(propers);
847+
zend_release_properties(props);
881848
}
882849
/* }}} */
883850

@@ -930,6 +897,17 @@ static void php_var_serialize_nested_data(smart_str *buf, zval *struc, HashTable
930897
}
931898
/* }}} */
932899

900+
static void php_var_serialize_class(smart_str *buf, zval *struc, zval *retval_ptr, php_serialize_data_t var_hash) /* {{{ */
901+
{
902+
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);
907+
zend_hash_destroy(&props);
908+
}
909+
/* }}} */
910+
933911
static void php_var_serialize_intern(smart_str *buf, zval *struc, php_serialize_data_t var_hash) /* {{{ */
934912
{
935913
zend_long var_already;

0 commit comments

Comments
 (0)