From efd817bf137b492c5a426aeba6374f6c61a0e6c8 Mon Sep 17 00:00:00 2001 From: Etienne Kneuss Date: Mon, 25 Aug 2008 18:40:44 +0000 Subject: [PATCH] MFH: Fix #45826 (Custom serialization) --- NEWS | 1 + ext/spl/spl_array.c | 229 +++++++++++++++++++++++++++--------- ext/spl/tests/bug45826.phpt | 88 ++++++++++++++ 3 files changed, 263 insertions(+), 55 deletions(-) create mode 100644 ext/spl/tests/bug45826.phpt diff --git a/NEWS b/NEWS index c21b9d84d78..6234cf7721a 100644 --- a/NEWS +++ b/NEWS @@ -52,6 +52,7 @@ PHP NEWS - Fixed bug #43008 (php://filter uris ignore url encoded filternames and can't handle slashes). (Arnaud) - Fixed bug #35980 (touch() works on files but not on directories). (Pierre) +- Fixed bug #45826 (custom ArrayObject serialization). (Etienne) 01 Aug 2008, PHP 5.3.0 Alpha 1 - Upgraded bundled PCRE to version 7.7. (Nuno) diff --git a/ext/spl/spl_array.c b/ext/spl/spl_array.c index ca4f2afc9fb..ca65fce4574 100755 --- a/ext/spl/spl_array.c +++ b/ext/spl/spl_array.c @@ -59,18 +59,22 @@ PHPAPI zend_class_entry *spl_ce_RecursiveArrayIterator; #define SPL_ARRAY_CLONE_MASK 0x0300FFFF typedef struct _spl_array_object { - zend_object std; - zval *array; - zval *retval; - HashPosition pos; - int ar_flags; - int is_self; - zend_function *fptr_offset_get; - zend_function *fptr_offset_set; - zend_function *fptr_offset_has; - zend_function *fptr_offset_del; - zend_function *fptr_count; - zend_class_entry* ce_get_iterator; + zend_object std; + zval *array; + zval *retval; + HashPosition pos; + int ar_flags; + int is_self; + zend_function *fptr_offset_get; + zend_function *fptr_offset_set; + zend_function *fptr_offset_has; + zend_function *fptr_offset_del; + zend_function *fptr_count; + zend_function *fptr_serialize; + zend_function *fptr_unserialize; + zend_class_entry *ce_get_iterator; + php_serialize_data_t *serialize_data; + php_unserialize_data_t *unserialize_data; } spl_array_object; static inline HashTable *spl_array_get_hash_table(spl_array_object* intern, int check_std_props TSRMLS_DC) { /* {{{ */ @@ -124,6 +128,8 @@ static void spl_array_object_free_storage(void *object TSRMLS_DC) /* }}} */ zend_object_iterator *spl_array_get_iterator(zend_class_entry *ce, zval *object, int by_ref TSRMLS_DC); +int spl_array_serialize(zval *object, unsigned char **buffer, zend_uint *buf_len, zend_serialize_data *data TSRMLS_DC); +int spl_array_unserialize(zval **object, zend_class_entry *ce, const unsigned char *buf, zend_uint buf_len, zend_unserialize_data *data TSRMLS_DC); /* {{{ spl_array_object_new_ex */ static zend_object_value spl_array_object_new_ex(zend_class_entry *class_type, spl_array_object **obj, zval *orig, int clone_orig TSRMLS_DC) @@ -143,6 +149,8 @@ static zend_object_value spl_array_object_new_ex(zend_class_entry *class_type, s zend_hash_copy(intern->std.properties, &class_type->default_properties, (copy_ctor_func_t) zval_add_ref, (void *) &tmp, sizeof(zval *)); intern->ar_flags = 0; + intern->serialize_data = NULL; + intern->unserialize_data = NULL; intern->ce_get_iterator = spl_ce_ArrayIterator; if (orig) { spl_array_object *other = (spl_array_object*)zend_object_store_get_object(orig TSRMLS_CC); @@ -208,6 +216,14 @@ static zend_object_value spl_array_object_new_ex(zend_class_entry *class_type, s if (intern->fptr_count->common.scope == parent) { intern->fptr_count = NULL; } + zend_hash_find(&class_type->function_table, "serialize", sizeof("serialize"), (void **) &intern->fptr_serialize); + if (intern->fptr_serialize->common.scope == parent) { + intern->fptr_serialize = NULL; + } + zend_hash_find(&class_type->function_table, "unserialize", sizeof("unserialize"), (void **) &intern->fptr_unserialize); + if (intern->fptr_unserialize->common.scope == parent) { + intern->fptr_unserialize = NULL; + } } /* Cache iterator functions if ArrayIterator or derived. Check current's */ /* cache since only current is always required */ @@ -1440,32 +1456,27 @@ SPL_METHOD(Array, getChildren) } /* }}} */ -/* {{{ proto string ArrayObject::serialize() - * serialize the object - */ -SPL_METHOD(Array, serialize) -{ - zval *object = getThis(); - spl_array_object *intern = (spl_array_object*)zend_object_store_get_object(object TSRMLS_CC); +smart_str spl_array_serialize_helper(spl_array_object *intern, php_serialize_data_t *var_hash_p TSRMLS_DC) { /* {{{ */ HashTable *aht = spl_array_get_hash_table(intern, 0 TSRMLS_CC); zval members, *pmembers; - php_serialize_data_t var_hash; smart_str buf = {0}; + zval *flags; if (!aht) { php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Array was modified outside object and is no longer an array"); - return; + return buf; } - PHP_VAR_SERIALIZE_INIT(var_hash); + MAKE_STD_ZVAL(flags); + ZVAL_LONG(flags, (intern->ar_flags & SPL_ARRAY_CLONE_MASK)); /* storage */ - smart_str_appendl(&buf, "x:i:", 4); - smart_str_append_long(&buf, (intern->ar_flags & SPL_ARRAY_CLONE_MASK)); - smart_str_appendc(&buf, ';'); + smart_str_appendl(&buf, "x:", 2); + php_var_serialize(&buf, &flags, var_hash_p TSRMLS_CC); + zval_ptr_dtor(&flags); if (!(intern->ar_flags & SPL_ARRAY_IS_SELF)) { - php_var_serialize(&buf, &intern->array, &var_hash TSRMLS_CC); + php_var_serialize(&buf, &intern->array, var_hash_p TSRMLS_CC); smart_str_appendc(&buf, ';'); } @@ -1475,10 +1486,35 @@ SPL_METHOD(Array, serialize) Z_ARRVAL(members) = intern->std.properties; Z_TYPE(members) = IS_ARRAY; pmembers = &members; - php_var_serialize(&buf, &pmembers, &var_hash TSRMLS_CC); /* finishes the string */ + php_var_serialize(&buf, &pmembers, var_hash_p TSRMLS_CC); /* finishes the string */ /* done */ - PHP_VAR_SERIALIZE_DESTROY(var_hash); + return buf; +} +/* }}} */ + +/* {{{ proto string ArrayObject::serialize() + * serialize the object + */ +SPL_METHOD(Array, serialize) +{ + zval *object = getThis(); + spl_array_object *intern = (spl_array_object*)zend_object_store_get_object(object TSRMLS_CC); + int was_in_serialize = intern->serialize_data != NULL; + smart_str buf; + + if (!was_in_serialize) { + intern->serialize_data = emalloc(sizeof(php_serialize_data_t)); + PHP_VAR_SERIALIZE_INIT(*intern->serialize_data); + } + + buf = spl_array_serialize_helper(intern, intern->serialize_data TSRMLS_CC); + + if (!was_in_serialize) { + PHP_VAR_SERIALIZE_DESTROY(*intern->serialize_data); + efree(intern->serialize_data); + intern->serialize_data = NULL; + } if (buf.c) { RETURN_STRINGL(buf.c, buf.len, 0); @@ -1487,32 +1523,45 @@ SPL_METHOD(Array, serialize) RETURN_NULL(); } /* }}} */ -/* {{{ proto void ArrayObject::unserialize(string serialized) - * unserialize the object - */ -SPL_METHOD(Array, unserialize) -{ - spl_array_object *intern = (spl_array_object*)zend_object_store_get_object(getThis() TSRMLS_CC); +int spl_array_serialize(zval *object, unsigned char **buffer, zend_uint *buf_len, zend_serialize_data *data TSRMLS_DC) { /* {{{ */ + spl_array_object *intern = (spl_array_object*)zend_object_store_get_object(object TSRMLS_CC); - char *buf; - int buf_len; + if (intern->fptr_serialize) { + int retval; + php_serialize_data_t *before; + + before = intern->serialize_data; + intern->serialize_data = (php_serialize_data_t *)data; + + retval = zend_user_serialize(object, buffer, buf_len, data TSRMLS_CC); + + intern->serialize_data = before; + + return retval; + } else { + smart_str buf; + + buf = spl_array_serialize_helper(intern, (php_serialize_data_t *)data TSRMLS_CC); + + if (buf.c) { + *buffer = (unsigned char*)estrndup(buf.c, buf.len); + *buf_len = buf.len; + efree(buf.c); + return SUCCESS; + } else { + return FAILURE; + } + } +} +/* }}} */ + +void spl_array_unserialize_helper(spl_array_object *intern, const unsigned char *buf, int buf_len, php_unserialize_data_t *var_hash_p TSRMLS_DC) { /* {{{ */ const unsigned char *p, *s; - php_unserialize_data_t var_hash; zval *pmembers, *pflags = NULL; long flags; - - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &buf, &buf_len) == FAILURE) { - return; - } - - if (buf_len == 0) { - zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0 TSRMLS_CC, "Empty serialized string cannot be empty"); - return; - } /* storage */ - s = p = (const unsigned char*)buf; - PHP_VAR_UNSERIALIZE_INIT(var_hash); + s = p = buf; if (*p!= 'x' || *++p != ':') { goto outexcept; @@ -1520,7 +1569,7 @@ SPL_METHOD(Array, unserialize) ++p; ALLOC_INIT_ZVAL(pflags); - if (!php_var_unserialize(&pflags, &p, s + buf_len, &var_hash TSRMLS_CC) || Z_TYPE_P(pflags) != IS_LONG) { + if (!php_var_unserialize(&pflags, &p, s + buf_len, var_hash_p TSRMLS_CC) || Z_TYPE_P(pflags) != IS_LONG) { zval_ptr_dtor(&pflags); goto outexcept; } @@ -1546,7 +1595,7 @@ SPL_METHOD(Array, unserialize) intern->ar_flags |= flags & SPL_ARRAY_CLONE_MASK; zval_ptr_dtor(&intern->array); ALLOC_INIT_ZVAL(intern->array); - if (!php_var_unserialize(&intern->array, &p, s + buf_len, &var_hash TSRMLS_CC)) { + if (!php_var_unserialize(&intern->array, &p, s + buf_len, var_hash_p TSRMLS_CC)) { goto outexcept; } } @@ -1562,7 +1611,7 @@ SPL_METHOD(Array, unserialize) ++p; ALLOC_INIT_ZVAL(pmembers); - if (!php_var_unserialize(&pmembers, &p, s + buf_len, &var_hash TSRMLS_CC)) { + if (!php_var_unserialize(&pmembers, &p, s + buf_len, var_hash_p TSRMLS_CC)) { zval_ptr_dtor(&pmembers); goto outexcept; } @@ -1572,17 +1621,83 @@ SPL_METHOD(Array, unserialize) zval_ptr_dtor(&pmembers); /* done reading $serialized */ - - PHP_VAR_UNSERIALIZE_DESTROY(var_hash); return; outexcept: - PHP_VAR_UNSERIALIZE_DESTROY(var_hash); - zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0 TSRMLS_CC, "Error at offset %ld of %d bytes", (long)((char*)p - buf), buf_len); + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0 TSRMLS_CC, "Error at offset %ld of %d bytes", (long)((char*)p - (char *)buf), buf_len); return; +} +/* }}} */ + +/* {{{ proto void ArrayObject::unserialize(string serialized) + * + * + * unserialize the object + */ +SPL_METHOD(Array, unserialize) +{ + char *buf; + int buf_len; + spl_array_object *intern = (spl_array_object*)zend_object_store_get_object(getThis() TSRMLS_CC); + int was_in_unserialize = intern->unserialize_data != NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &buf, &buf_len) == FAILURE) { + return; + } + + if (buf_len == 0) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0 TSRMLS_CC, "Empty serialized string cannot be empty"); + return; + } + + if (!was_in_unserialize) { + intern->unserialize_data = emalloc(sizeof(php_unserialize_data_t)); + PHP_VAR_UNSERIALIZE_INIT(*intern->unserialize_data); + } + + spl_array_unserialize_helper(intern, (const unsigned char *)buf, buf_len, intern->unserialize_data TSRMLS_CC); + + if (!was_in_unserialize) { + PHP_VAR_UNSERIALIZE_DESTROY(*intern->unserialize_data); + efree(intern->unserialize_data); + intern->unserialize_data = NULL; + } } /* }}} */ +int spl_array_unserialize(zval **object, zend_class_entry *ce, int type, const zstr buf, zend_uint buf_len, zend_unserialize_data *data TSRMLS_DC) +{ + spl_array_object *intern; + + object_init_ex(*object, ce); + intern = (spl_array_object*)zend_object_store_get_object(*object TSRMLS_CC); + + if (intern->fptr_unserialize) { + zval *zdata; + php_unserialize_data_t *before; + MAKE_STD_ZVAL(zdata); + ZVAL_ZSTRL(zdata, type, buf, buf_len, 1); + + before = intern->unserialize_data; + intern->unserialize_data = (php_unserialize_data_t *)data; + + zend_call_method_with_1_params(object, ce, &ce->unserialize_func, "unserialize", NULL, zdata); + + intern->unserialize_data = before; + + zval_ptr_dtor(&zdata); + } else { + spl_array_unserialize_helper(intern, buf, buf_len, (php_unserialize_data_t *)data TSRMLS_CC); + } + + if (EG(exception)) { + return FAILURE; + } else { + return SUCCESS; + } +} +/* }}} */ + /* {{{ arginfo and function tbale */ static ZEND_BEGIN_ARG_INFO(arginfo_array___construct, 0) @@ -1705,6 +1820,8 @@ PHP_MINIT_FUNCTION(spl_array) REGISTER_SPL_IMPLEMENTS(ArrayObject, Aggregate); REGISTER_SPL_IMPLEMENTS(ArrayObject, ArrayAccess); REGISTER_SPL_IMPLEMENTS(ArrayObject, Serializable); + spl_ce_ArrayObject->serialize = spl_array_serialize; + spl_ce_ArrayObject->unserialize = spl_array_unserialize; memcpy(&spl_handler_ArrayObject, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); spl_handler_ArrayObject.clone_obj = spl_array_object_clone; @@ -1727,6 +1844,8 @@ PHP_MINIT_FUNCTION(spl_array) REGISTER_SPL_IMPLEMENTS(ArrayIterator, ArrayAccess); REGISTER_SPL_IMPLEMENTS(ArrayIterator, SeekableIterator); REGISTER_SPL_IMPLEMENTS(ArrayIterator, Serializable); + spl_ce_ArrayIterator->serialize = spl_array_serialize; + spl_ce_ArrayIterator->unserialize = spl_array_unserialize; memcpy(&spl_handler_ArrayIterator, &spl_handler_ArrayObject, sizeof(zend_object_handlers)); spl_ce_ArrayIterator->get_iterator = spl_array_get_iterator; diff --git a/ext/spl/tests/bug45826.phpt b/ext/spl/tests/bug45826.phpt new file mode 100644 index 00000000000..7993bfaa958 --- /dev/null +++ b/ext/spl/tests/bug45826.phpt @@ -0,0 +1,88 @@ +--TEST-- +ArrayObject/ArrayIterator : serialization +--FILE-- +append($y); +$o->append($y); +$o->append($o); + +var_dump($o[0] === $o[1]); +var_dump($o[2] === $o); + +$s1 = serialize($o); +$s2 = $o->serialize(); +var_dump($s1); +var_dump($s2); + +$o1 =unserialize($s1); + +var_dump($o1[0] === $o1[1]); +var_dump($o1[2] === $o1); + +$o2 = new ArrayObject(); +$o2->unserialize($s2); + +var_dump($o2[0] === $o2[1]); +var_dump($o2[2] !== $o2); +var_dump($o2[2][2] === $o2[2]); + +echo "#### Extending ArrayObject\n"; +unset($o,$x,$s1,$s2,$o1,$o2); +class ArrayObject2 extends ArrayObject { + public function serialize() { + return parent::serialize(); + } + + public function unserialize($s) { + return parent::unserialize($s); + } +} + +$o = new ArrayObject2(); +$y = new StdClass; +$o->append($y); +$o->append($y); +$o->append($o); + +var_dump($o[0] === $o[1]); +var_dump($o[2] === $o); + +$s1 = serialize($o); +$s2 = $o->serialize(); +var_dump($s1); +var_dump($s2); + +$o1 =unserialize($s1); + +var_dump($o1[0] === $o1[1]); +var_dump($o1[2] === $o1); + +$o2 = new ArrayObject2(); +$o2->unserialize($s2); + +var_dump($o2[0] === $o2[1]); +var_dump($o2[2] !== $o2); +var_dump($o2[2][2] === $o2[2]); +?> +--EXPECT-- +bool(true) +bool(true) +string(84) "C:11:"ArrayObject":60:{x:i:0;a:3:{i:0;O:8:"stdClass":0:{}i:1;r:4;i:2;r:1;};m:a:0:{}}" +string(125) "x:i:0;a:3:{i:0;O:8:"stdClass":0:{}i:1;r:3;i:2;C:11:"ArrayObject":45:{x:i:0;a:3:{i:0;r:3;i:1;r:3;i:2;r:5;};m:a:0:{}}};m:a:0:{}" +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +#### Extending ArrayObject +bool(true) +bool(true) +string(85) "C:12:"ArrayObject2":60:{x:i:0;a:3:{i:0;O:8:"stdClass":0:{}i:1;r:4;i:2;r:1;};m:a:0:{}}" +string(126) "x:i:0;a:3:{i:0;O:8:"stdClass":0:{}i:1;r:3;i:2;C:12:"ArrayObject2":45:{x:i:0;a:3:{i:0;r:3;i:1;r:3;i:2;r:5;};m:a:0:{}}};m:a:0:{}" +bool(true) +bool(true) +bool(true) +bool(true) +bool(true)