]> granicus.if.org Git - php/commitdiff
Fixed serialize/unserialize problems
authorDmitry Stogov <dmitry@zend.com>
Mon, 17 Mar 2014 13:23:27 +0000 (17:23 +0400)
committerDmitry Stogov <dmitry@zend.com>
Mon, 17 Mar 2014 13:23:27 +0000 (17:23 +0400)
Zend/zend_hash.c
Zend/zend_hash.h
ext/standard/var.c
ext/standard/var_unserializer.c
ext/standard/var_unserializer.re

index f06eb7d62457299186a198d8273eb8a3499381d2..be2b0c742b5bd4ee42ccaf7a60f49ed47ca283fc 100644 (file)
@@ -138,6 +138,13 @@ static void zend_hash_packed_grow(HashTable *ht)
        HANDLE_UNBLOCK_INTERRUPTIONS();
 }
 
+ZEND_API void zend_hash_real_init(HashTable *ht, int packed)
+{
+       IS_CONSISTENT(ht);
+
+       CHECK_INIT(ht, packed);
+}
+
 ZEND_API void zend_hash_packed_to_hash(HashTable *ht)
 {
        HANDLE_BLOCK_INTERRUPTIONS();
index 5942611955aa881faa42e83003d87bcf2cafc673..2718356497f19946a4d6b419a1b3670da0d2a965 100644 (file)
@@ -66,7 +66,8 @@ ZEND_API void zend_hash_clean(HashTable *ht);
 #define zend_hash_init(ht, nSize, pHashFunction, pDestructor, persistent)                                              _zend_hash_init((ht), (nSize), (pDestructor), (persistent) ZEND_FILE_LINE_CC)
 #define zend_hash_init_ex(ht, nSize, pHashFunction, pDestructor, persistent, bApplyProtection)         _zend_hash_init_ex((ht), (nSize), (pDestructor), (persistent), (bApplyProtection) ZEND_FILE_LINE_CC)
 
-ZEND_API  void zend_hash_packed_to_hash(HashTable *ht);
+ZEND_API void zend_hash_real_init(HashTable *ht, int packed);
+ZEND_API void zend_hash_packed_to_hash(HashTable *ht);
 ZEND_API void zend_hash_to_packed(HashTable *ht);
 
 /* additions/updates/changes */
index c7e5e2fd752920d8fe33bbaa7bfdcff3e594fe78..8c2694bef7dc13fe39f9fb96526488298b682c96 100644 (file)
@@ -735,7 +735,7 @@ static void php_var_serialize_intern(smart_str *buf, zval *struc, HashTable *var
 
        ZVAL_UNDEF(&var_already);
 
-       if (var_hash && 
+       if (var_hash &&
                php_add_var_hash(var_hash, Z_ISREF_P(struc)? Z_REFVAL_P(struc) : struc, &var_already TSRMLS_CC) == FAILURE) {
                if (Z_ISREF_P(struc)) {
                        smart_str_appendl(buf, "R:", 2);
@@ -818,7 +818,6 @@ again:
                                }
 
                                if (ce && ce != PHP_IC_ENTRY && zend_hash_str_exists(&ce->function_table, "__sleep", sizeof("__sleep")-1)) {
-//???                                  ZVAL_STRINGL(&fname, "__sleep", sizeof("__sleep") - 1, 0);
                                        ZVAL_STRINGL(&fname, "__sleep", sizeof("__sleep") - 1);
                                        BG(serialize_lock)++;
                                        res = call_user_function_ex(CG(function_table), struc, &fname, &retval, 0, 0, 1, NULL TSRMLS_CC);
@@ -894,7 +893,7 @@ again:
                                        /* we should still add element even if it's not OK,
                                         * since we already wrote the length of the array before */
                                        if ((data = zend_hash_get_current_data_ex(myht, &pos)) == NULL
-                                               || Z_ARR_P(data) == Z_ARR_P(struc)
+                                               || (Z_TYPE_P(data) == IS_ARRAY && Z_TYPE_P(struc) == IS_ARRAY && Z_ARR_P(data) == Z_ARR_P(struc))
                                                || (Z_TYPE_P(data) == IS_ARRAY && Z_ARRVAL_P(data)->nApplyCount > 1)
                                        ) {
                                                smart_str_appendl(buf, "N;", 2);
index 7c39411bffc6f9ad0e2ee100471b16c913436f2f..8e69338b021072f77a58984e21c11f72b77caa92 100644 (file)
 #define VAR_ENTRIES_DBG 0
 
 typedef struct {
-       zval data[VAR_ENTRIES_MAX];
+       zval *data[VAR_ENTRIES_MAX];
        long used_slots;
        void *next;
 } var_entries;
 
-static inline zval *var_push(php_unserialize_data_t *var_hashx, zval *rval)
+static inline void var_push(php_unserialize_data_t *var_hashx, zval *rval)
 {
        var_entries *var_hash = (*var_hashx)->last;
 #if VAR_ENTRIES_DBG
@@ -55,8 +55,7 @@ static inline zval *var_push(php_unserialize_data_t *var_hashx, zval *rval)
                (*var_hashx)->last = var_hash;
        }
 
-       ZVAL_COPY_VALUE(&var_hash->data[var_hash->used_slots], rval);
-       return &var_hash->data[var_hash->used_slots++];
+       var_hash->data[var_hash->used_slots++] = rval;
 }
 
 PHPAPI void var_push_dtor(php_unserialize_data_t *var_hashx, zval *rval)
@@ -80,8 +79,8 @@ PHPAPI void var_push_dtor(php_unserialize_data_t *var_hashx, zval *rval)
                (*var_hashx)->last_dtor = var_hash;
        }
 
-       ZVAL_COPY(&var_hash->data[var_hash->used_slots], rval);
-       var_hash->used_slots++;
+       Z_ADDREF_P(rval);
+       var_hash->data[var_hash->used_slots++] = rval;
 }
 
 PHPAPI void var_push_dtor_no_addref(php_unserialize_data_t *var_hashx, zval *rval)
@@ -105,8 +104,7 @@ PHPAPI void var_push_dtor_no_addref(php_unserialize_data_t *var_hashx, zval *rva
                (*var_hashx)->last_dtor = var_hash;
        }
 
-       ZVAL_COPY_VALUE(&var_hash->data[var_hash->used_slots], rval);
-       var_hash->used_slots++;
+       var_hash->data[var_hash->used_slots++] = rval;
 }
 
 static int var_access(php_unserialize_data_t *var_hashx, long id, zval **store)
@@ -125,7 +123,7 @@ static int var_access(php_unserialize_data_t *var_hashx, long id, zval **store)
 
        if (id < 0 || id >= var_hash->used_slots) return !SUCCESS;
 
-       *store = &var_hash->data[id];
+       *store = var_hash->data[id];
 
        return SUCCESS;
 }
@@ -149,7 +147,7 @@ PHPAPI void var_destroy(php_unserialize_data_t *var_hashx)
        
        while (var_hash) {
                for (i = 0; i < var_hash->used_slots; i++) {
-                       zval_ptr_dtor(&var_hash->data[i]);
+                       zval_ptr_dtor(var_hash->data[i]);
                }
                next = var_hash->next;
                efree(var_hash);
@@ -209,7 +207,7 @@ static char *unserialize_str(const unsigned char **p, size_t *len, size_t maxlen
 #define YYMARKER marker
 
 
-#line 217 "ext/standard/var_unserializer.re"
+#line 215 "ext/standard/var_unserializer.re"
 
 
 
@@ -277,7 +275,8 @@ static inline int process_nested_data(UNSERIALIZE_PARAMETER, HashTable *ht, long
        while (elements-- > 0) {
                zval key, *data, d, *old_data;
 
-               if (!php_var_unserialize_intern(&key, p, max, NULL TSRMLS_CC)) {
+               if (!php_var_unserialize(&key, p, max, NULL TSRMLS_CC)) {
+                       zval_dtor(&key);
                        return 0;
                }
 
@@ -286,17 +285,8 @@ static inline int process_nested_data(UNSERIALIZE_PARAMETER, HashTable *ht, long
                        return 0;
                }
 
-               if (var_hash && (*p)[0] != 'R') {
-                       data = var_push(var_hash, data);
-               } else {
-                       data = &d;
-               }
-
-               ZVAL_UNDEF(data);
-               if (!php_var_unserialize_intern(data, p, max, var_hash TSRMLS_CC)) {
-                       zval_dtor(&key);
-                       return 0;
-               }
+               data = NULL;
+               ZVAL_UNDEF(&d);
 
                if (!objprops) {
                        switch (Z_TYPE(key)) {
@@ -304,23 +294,27 @@ static inline int process_nested_data(UNSERIALIZE_PARAMETER, HashTable *ht, long
                                if ((old_data = zend_hash_index_find(ht, Z_LVAL(key))) != NULL) {
                                        var_push_dtor(var_hash, old_data);
                                }
-                               zend_hash_index_update(ht, Z_LVAL(key), data);
+                               data = zend_hash_index_update(ht, Z_LVAL(key), &d);
                                break;
                        case IS_STRING:
                                if ((old_data = zend_symtable_find(ht, Z_STR(key))) != NULL) {
                                        var_push_dtor(var_hash, old_data);
                                }
-                               zend_symtable_update(ht, Z_STR(key), data);
+                               data = zend_symtable_update(ht, Z_STR(key), &d);
                                break;
                        }
                } else {
                        /* object properties should include no integers */
                        convert_to_string(&key);
-                       zend_hash_update(ht, Z_STR(key), data);
+                       data = zend_hash_update(ht, Z_STR(key), &d);
                }
                
                zval_dtor(&key);
 
+               if (!php_var_unserialize(data, p, max, var_hash TSRMLS_CC)) {
+                       return 0;
+               }
+
                if (elements && *(*p-1) != ';' && *(*p-1) != '}') {
                        (*p)--;
                        return 0;
@@ -390,9 +384,12 @@ static inline int object_common2(UNSERIALIZE_PARAMETER, long elements)
                return 0;
        }
 
+       if (Z_ISREF_P(rval)) {
+               rval = Z_REFVAL_P(rval);
+       }
+
        if (Z_OBJCE_P(rval) != PHP_IC_ENTRY &&
                zend_hash_str_exists(&Z_OBJCE_P(rval)->function_table, "__wakeup", sizeof("__wakeup")-1)) {
-//???          ZVAL_STRINGL(&fname, "__wakeup", sizeof("__wakeup") - 1, 0);
                ZVAL_STRINGL(&fname, "__wakeup", sizeof("__wakeup") - 1);
                BG(serialize_lock)++;
                call_user_function_ex(CG(function_table), rval, &fname, &retval, 0, 0, 1, NULL TSRMLS_CC);
@@ -413,15 +410,6 @@ static inline int object_common2(UNSERIALIZE_PARAMETER, long elements)
 #endif
 
 PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
-{
-       if (var_hash && (*p)[0] != 'R') {
-               var_push(var_hash, rval);
-       }
-
-       return php_var_unserialize_intern(UNSERIALIZE_PASSTHRU);
-}
-
-PHPAPI int php_var_unserialize_intern(UNSERIALIZE_PARAMETER)
 {
        const unsigned char *cursor, *limit, *marker, *start;
        zval *rval_ref;
@@ -433,10 +421,14 @@ PHPAPI int php_var_unserialize_intern(UNSERIALIZE_PARAMETER)
                return 0;
        }
        
+       if (var_hash && (*p)[0] != 'R') {
+               var_push(var_hash, rval);
+       }
+
        start = cursor;
 
 
-#line 440 "ext/standard/var_unserializer.c"
+#line 432 "ext/standard/var_unserializer.c"
 {
        YYCTYPE yych;
        static const unsigned char yybm[] = {
@@ -496,9 +488,9 @@ yy2:
        yych = *(YYMARKER = ++YYCURSOR);
        if (yych == ':') goto yy95;
 yy3:
-#line 777 "ext/standard/var_unserializer.re"
+#line 778 "ext/standard/var_unserializer.re"
        { return 0; }
-#line 502 "ext/standard/var_unserializer.c"
+#line 494 "ext/standard/var_unserializer.c"
 yy4:
        yych = *(YYMARKER = ++YYCURSOR);
        if (yych == ':') goto yy89;
@@ -541,13 +533,13 @@ yy13:
        goto yy3;
 yy14:
        ++YYCURSOR;
-#line 771 "ext/standard/var_unserializer.re"
+#line 772 "ext/standard/var_unserializer.re"
        {
        /* this is the case where we have less data than planned */
        php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Unexpected end of serialized data");
        return 0; /* not sure if it should be 0 or 1 here? */
 }
-#line 551 "ext/standard/var_unserializer.c"
+#line 543 "ext/standard/var_unserializer.c"
 yy16:
        yych = *++YYCURSOR;
        goto yy3;
@@ -577,7 +569,7 @@ yy20:
        yych = *++YYCURSOR;
        if (yych != '"') goto yy18;
        ++YYCURSOR;
-#line 632 "ext/standard/var_unserializer.re"
+#line 633 "ext/standard/var_unserializer.re"
        {
        size_t len, len2, len3, maxlen;
        long elements;
@@ -716,7 +708,7 @@ yy20:
 
        return object_common2(UNSERIALIZE_PASSTHRU, elements);
 }
-#line 720 "ext/standard/var_unserializer.c"
+#line 712 "ext/standard/var_unserializer.c"
 yy25:
        yych = *++YYCURSOR;
        if (yych <= ',') {
@@ -741,7 +733,7 @@ yy27:
        yych = *++YYCURSOR;
        if (yych != '"') goto yy18;
        ++YYCURSOR;
-#line 624 "ext/standard/var_unserializer.re"
+#line 625 "ext/standard/var_unserializer.re"
        {
 
 //???  INIT_PZVAL(rval);
@@ -749,7 +741,7 @@ yy27:
        return object_common2(UNSERIALIZE_PASSTHRU,
                        object_common1(UNSERIALIZE_PASSTHRU, ZEND_STANDARD_CLASS_DEF_PTR));
 }
-#line 753 "ext/standard/var_unserializer.c"
+#line 745 "ext/standard/var_unserializer.c"
 yy32:
        yych = *++YYCURSOR;
        if (yych == '+') goto yy33;
@@ -770,7 +762,7 @@ yy34:
        yych = *++YYCURSOR;
        if (yych != '{') goto yy18;
        ++YYCURSOR;
-#line 606 "ext/standard/var_unserializer.re"
+#line 604 "ext/standard/var_unserializer.re"
        {
        long elements = parse_iv(start + 2);
        /* use iv() not uiv() in order to check data range */
@@ -781,6 +773,9 @@ yy34:
        }
 
        array_init_size(rval, elements);
+//??? we can't convert from packed to hash during unserialization, becaue
+//??? reference to some zvals might be keept in var_hash (to support references)
+       zend_hash_real_init(Z_ARRVAL_P(rval), 0);
 
        if (!process_nested_data(UNSERIALIZE_PASSTHRU, Z_ARRVAL_P(rval), elements, 0)) {
                return 0;
@@ -788,7 +783,7 @@ yy34:
 
        return finish_nested_data(UNSERIALIZE_PASSTHRU);
 }
-#line 792 "ext/standard/var_unserializer.c"
+#line 787 "ext/standard/var_unserializer.c"
 yy39:
        yych = *++YYCURSOR;
        if (yych == '+') goto yy40;
@@ -809,9 +804,10 @@ yy41:
        yych = *++YYCURSOR;
        if (yych != '"') goto yy18;
        ++YYCURSOR;
-#line 577 "ext/standard/var_unserializer.re"
+#line 574 "ext/standard/var_unserializer.re"
        {
        size_t len, maxlen;
+//??? TODO: use zend_string* instead of char*
        char *str;
 
        len = parse_uiv(start + 2);
@@ -834,11 +830,11 @@ yy41:
        YYCURSOR += 2;
        *p = YYCURSOR;
 
-//???  ZVAL_STRINGL(rval, str, len, 0);
        ZVAL_STRINGL(rval, str, len);
+       efree(str);
        return 1;
 }
-#line 842 "ext/standard/var_unserializer.c"
+#line 838 "ext/standard/var_unserializer.c"
 yy46:
        yych = *++YYCURSOR;
        if (yych == '+') goto yy47;
@@ -859,7 +855,7 @@ yy48:
        yych = *++YYCURSOR;
        if (yych != '"') goto yy18;
        ++YYCURSOR;
-#line 550 "ext/standard/var_unserializer.re"
+#line 547 "ext/standard/var_unserializer.re"
        {
        size_t len, maxlen;
        char *str;
@@ -886,7 +882,7 @@ yy48:
        ZVAL_STRINGL(rval, str, len);
        return 1;
 }
-#line 890 "ext/standard/var_unserializer.c"
+#line 886 "ext/standard/var_unserializer.c"
 yy53:
        yych = *++YYCURSOR;
        if (yych <= '/') {
@@ -974,7 +970,7 @@ yy61:
        }
 yy63:
        ++YYCURSOR;
-#line 541 "ext/standard/var_unserializer.re"
+#line 538 "ext/standard/var_unserializer.re"
        {
 #if SIZEOF_LONG == 4
 use_double:
@@ -983,7 +979,7 @@ use_double:
        ZVAL_DOUBLE(rval, zend_strtod((const char *)start + 2, NULL));
        return 1;
 }
-#line 987 "ext/standard/var_unserializer.c"
+#line 983 "ext/standard/var_unserializer.c"
 yy65:
        yych = *++YYCURSOR;
        if (yych <= ',') {
@@ -1042,7 +1038,7 @@ yy73:
        yych = *++YYCURSOR;
        if (yych != ';') goto yy18;
        ++YYCURSOR;
-#line 525 "ext/standard/var_unserializer.re"
+#line 522 "ext/standard/var_unserializer.re"
        {
        *p = YYCURSOR;
 
@@ -1058,7 +1054,7 @@ yy73:
 
        return 1;
 }
-#line 1062 "ext/standard/var_unserializer.c"
+#line 1058 "ext/standard/var_unserializer.c"
 yy76:
        yych = *++YYCURSOR;
        if (yych == 'N') goto yy73;
@@ -1085,7 +1081,7 @@ yy79:
        if (yych <= '9') goto yy79;
        if (yych != ';') goto yy18;
        ++YYCURSOR;
-#line 499 "ext/standard/var_unserializer.re"
+#line 496 "ext/standard/var_unserializer.re"
        {
 #if SIZEOF_LONG == 4
        int digits = YYCURSOR - start - 3;
@@ -1111,7 +1107,7 @@ yy79:
        ZVAL_LONG(rval, parse_iv(start + 2));
        return 1;
 }
-#line 1115 "ext/standard/var_unserializer.c"
+#line 1111 "ext/standard/var_unserializer.c"
 yy83:
        yych = *++YYCURSOR;
        if (yych <= '/') goto yy18;
@@ -1119,22 +1115,22 @@ yy83:
        yych = *++YYCURSOR;
        if (yych != ';') goto yy18;
        ++YYCURSOR;
-#line 493 "ext/standard/var_unserializer.re"
+#line 490 "ext/standard/var_unserializer.re"
        {
        *p = YYCURSOR;
        ZVAL_BOOL(rval, parse_iv(start + 2));
        return 1;
 }
-#line 1129 "ext/standard/var_unserializer.c"
+#line 1125 "ext/standard/var_unserializer.c"
 yy87:
        ++YYCURSOR;
-#line 487 "ext/standard/var_unserializer.re"
+#line 484 "ext/standard/var_unserializer.re"
        {
        *p = YYCURSOR;
        ZVAL_NULL(rval);
        return 1;
 }
-#line 1138 "ext/standard/var_unserializer.c"
+#line 1134 "ext/standard/var_unserializer.c"
 yy89:
        yych = *++YYCURSOR;
        if (yych <= ',') {
@@ -1157,7 +1153,7 @@ yy91:
        if (yych <= '9') goto yy91;
        if (yych != ';') goto yy18;
        ++YYCURSOR;
-#line 464 "ext/standard/var_unserializer.re"
+#line 461 "ext/standard/var_unserializer.re"
        {
        long id;
 
@@ -1170,7 +1166,7 @@ yy91:
        }
 
 //???
-       if (rval == rval_ref) return 0;
+//???  if (rval == rval_ref) return 0;
 
        if (!ZVAL_IS_UNDEF(rval)) {
                var_push_dtor_no_addref(var_hash, rval);
@@ -1180,7 +1176,7 @@ yy91:
        
        return 1;
 }
-#line 1184 "ext/standard/var_unserializer.c"
+#line 1180 "ext/standard/var_unserializer.c"
 yy95:
        yych = *++YYCURSOR;
        if (yych <= ',') {
@@ -1203,7 +1199,7 @@ yy97:
        if (yych <= '9') goto yy97;
        if (yych != ';') goto yy18;
        ++YYCURSOR;
-#line 444 "ext/standard/var_unserializer.re"
+#line 436 "ext/standard/var_unserializer.re"
        {
        long id;
 
@@ -1218,14 +1214,19 @@ yy97:
        if (!ZVAL_IS_UNDEF(rval)) {
                zval_ptr_dtor(rval);
        }
-       ZVAL_COPY(rval, rval_ref);
+       if (Z_ISREF_P(rval_ref)) {
+               ZVAL_COPY(rval, rval_ref);
+       } else {
+               ZVAL_NEW_REF(rval_ref, rval_ref);
+               ZVAL_COPY(rval, rval_ref);
+       }
 //???  Z_SET_ISREF_PP(rval);
        
        return 1;
 }
-#line 1227 "ext/standard/var_unserializer.c"
+#line 1228 "ext/standard/var_unserializer.c"
 }
-#line 779 "ext/standard/var_unserializer.re"
+#line 780 "ext/standard/var_unserializer.re"
 
 
        return 0;
index 0a890698866a7152409f76261cd52216ab7ca4b5..ae195ae88bf7491946e40f3563f689804383c82c 100644 (file)
 #define VAR_ENTRIES_DBG 0
 
 typedef struct {
-       zval data[VAR_ENTRIES_MAX];
+       zval *data[VAR_ENTRIES_MAX];
        long used_slots;
        void *next;
 } var_entries;
 
-static inline zval *var_push(php_unserialize_data_t *var_hashx, zval *rval)
+static inline void var_push(php_unserialize_data_t *var_hashx, zval *rval)
 {
        var_entries *var_hash = (*var_hashx)->last;
 #if VAR_ENTRIES_DBG
@@ -53,8 +53,7 @@ static inline zval *var_push(php_unserialize_data_t *var_hashx, zval *rval)
                (*var_hashx)->last = var_hash;
        }
 
-       ZVAL_COPY_VALUE(&var_hash->data[var_hash->used_slots], rval);
-       return &var_hash->data[var_hash->used_slots++];
+       var_hash->data[var_hash->used_slots++] = rval;
 }
 
 PHPAPI void var_push_dtor(php_unserialize_data_t *var_hashx, zval *rval)
@@ -78,8 +77,8 @@ PHPAPI void var_push_dtor(php_unserialize_data_t *var_hashx, zval *rval)
                (*var_hashx)->last_dtor = var_hash;
        }
 
-       ZVAL_COPY(&var_hash->data[var_hash->used_slots], rval);
-       var_hash->used_slots++;
+       Z_ADDREF_P(rval);
+       var_hash->data[var_hash->used_slots++] = rval;
 }
 
 PHPAPI void var_push_dtor_no_addref(php_unserialize_data_t *var_hashx, zval *rval)
@@ -103,8 +102,7 @@ PHPAPI void var_push_dtor_no_addref(php_unserialize_data_t *var_hashx, zval *rva
                (*var_hashx)->last_dtor = var_hash;
        }
 
-       ZVAL_COPY_VALUE(&var_hash->data[var_hash->used_slots], rval);
-       var_hash->used_slots++;
+       var_hash->data[var_hash->used_slots++] = rval;
 }
 
 static int var_access(php_unserialize_data_t *var_hashx, long id, zval **store)
@@ -123,7 +121,7 @@ static int var_access(php_unserialize_data_t *var_hashx, long id, zval **store)
 
        if (id < 0 || id >= var_hash->used_slots) return !SUCCESS;
 
-       *store = &var_hash->data[id];
+       *store = var_hash->data[id];
 
        return SUCCESS;
 }
@@ -147,7 +145,7 @@ PHPAPI void var_destroy(php_unserialize_data_t *var_hashx)
        
        while (var_hash) {
                for (i = 0; i < var_hash->used_slots; i++) {
-                       zval_ptr_dtor(&var_hash->data[i]);
+                       zval_ptr_dtor(var_hash->data[i]);
                }
                next = var_hash->next;
                efree(var_hash);
@@ -281,7 +279,8 @@ static inline int process_nested_data(UNSERIALIZE_PARAMETER, HashTable *ht, long
        while (elements-- > 0) {
                zval key, *data, d, *old_data;
 
-               if (!php_var_unserialize_intern(&key, p, max, NULL TSRMLS_CC)) {
+               if (!php_var_unserialize(&key, p, max, NULL TSRMLS_CC)) {
+                       zval_dtor(&key);
                        return 0;
                }
 
@@ -290,17 +289,8 @@ static inline int process_nested_data(UNSERIALIZE_PARAMETER, HashTable *ht, long
                        return 0;
                }
 
-               if (var_hash && (*p)[0] != 'R') {
-                       data = var_push(var_hash, data);
-               } else {
-                       data = &d;
-               }
-
-               ZVAL_UNDEF(data);
-               if (!php_var_unserialize_intern(data, p, max, var_hash TSRMLS_CC)) {
-                       zval_dtor(&key);
-                       return 0;
-               }
+               data = NULL;
+               ZVAL_UNDEF(&d);
 
                if (!objprops) {
                        switch (Z_TYPE(key)) {
@@ -308,23 +298,27 @@ static inline int process_nested_data(UNSERIALIZE_PARAMETER, HashTable *ht, long
                                if ((old_data = zend_hash_index_find(ht, Z_LVAL(key))) != NULL) {
                                        var_push_dtor(var_hash, old_data);
                                }
-                               zend_hash_index_update(ht, Z_LVAL(key), data);
+                               data = zend_hash_index_update(ht, Z_LVAL(key), &d);
                                break;
                        case IS_STRING:
                                if ((old_data = zend_symtable_find(ht, Z_STR(key))) != NULL) {
                                        var_push_dtor(var_hash, old_data);
                                }
-                               zend_symtable_update(ht, Z_STR(key), data);
+                               data = zend_symtable_update(ht, Z_STR(key), &d);
                                break;
                        }
                } else {
                        /* object properties should include no integers */
                        convert_to_string(&key);
-                       zend_hash_update(ht, Z_STR(key), data);
+                       data = zend_hash_update(ht, Z_STR(key), &d);
                }
                
                zval_dtor(&key);
 
+               if (!php_var_unserialize(data, p, max, var_hash TSRMLS_CC)) {
+                       return 0;
+               }
+
                if (elements && *(*p-1) != ';' && *(*p-1) != '}') {
                        (*p)--;
                        return 0;
@@ -394,9 +388,12 @@ static inline int object_common2(UNSERIALIZE_PARAMETER, long elements)
                return 0;
        }
 
+       if (Z_ISREF_P(rval)) {
+               rval = Z_REFVAL_P(rval);
+       }
+
        if (Z_OBJCE_P(rval) != PHP_IC_ENTRY &&
                zend_hash_str_exists(&Z_OBJCE_P(rval)->function_table, "__wakeup", sizeof("__wakeup")-1)) {
-//???          ZVAL_STRINGL(&fname, "__wakeup", sizeof("__wakeup") - 1, 0);
                ZVAL_STRINGL(&fname, "__wakeup", sizeof("__wakeup") - 1);
                BG(serialize_lock)++;
                call_user_function_ex(CG(function_table), rval, &fname, &retval, 0, 0, 1, NULL TSRMLS_CC);
@@ -417,15 +414,6 @@ static inline int object_common2(UNSERIALIZE_PARAMETER, long elements)
 #endif
 
 PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
-{
-       if (var_hash && (*p)[0] != 'R') {
-               var_push(var_hash, rval);
-       }
-
-       return php_var_unserialize_intern(UNSERIALIZE_PASSTHRU);
-}
-
-PHPAPI int php_var_unserialize_intern(UNSERIALIZE_PARAMETER)
 {
        const unsigned char *cursor, *limit, *marker, *start;
        zval *rval_ref;
@@ -437,6 +425,10 @@ PHPAPI int php_var_unserialize_intern(UNSERIALIZE_PARAMETER)
                return 0;
        }
        
+       if (var_hash && (*p)[0] != 'R') {
+               var_push(var_hash, rval);
+       }
+
        start = cursor;
 
 /*!re2c
@@ -455,7 +447,12 @@ PHPAPI int php_var_unserialize_intern(UNSERIALIZE_PARAMETER)
        if (!ZVAL_IS_UNDEF(rval)) {
                zval_ptr_dtor(rval);
        }
-       ZVAL_COPY(rval, rval_ref);
+       if (Z_ISREF_P(rval_ref)) {
+               ZVAL_COPY(rval, rval_ref);
+       } else {
+               ZVAL_NEW_REF(rval_ref, rval_ref);
+               ZVAL_COPY(rval, rval_ref);
+       }
 //???  Z_SET_ISREF_PP(rval);
        
        return 1;
@@ -473,7 +470,7 @@ PHPAPI int php_var_unserialize_intern(UNSERIALIZE_PARAMETER)
        }
 
 //???
-       if (rval == rval_ref) return 0;
+//???  if (rval == rval_ref) return 0;
 
        if (!ZVAL_IS_UNDEF(rval)) {
                var_push_dtor_no_addref(var_hash, rval);
@@ -576,6 +573,7 @@ use_double:
 
 "S:" uiv ":" ["]       {
        size_t len, maxlen;
+//??? TODO: use zend_string* instead of char*
        char *str;
 
        len = parse_uiv(start + 2);
@@ -598,8 +596,8 @@ use_double:
        YYCURSOR += 2;
        *p = YYCURSOR;
 
-//???  ZVAL_STRINGL(rval, str, len, 0);
        ZVAL_STRINGL(rval, str, len);
+       efree(str);
        return 1;
 }
 
@@ -613,6 +611,9 @@ use_double:
        }
 
        array_init_size(rval, elements);
+//??? we can't convert from packed to hash during unserialization, becaue
+//??? reference to some zvals might be keept in var_hash (to support references)
+       zend_hash_real_init(Z_ARRVAL_P(rval), 0);
 
        if (!process_nested_data(UNSERIALIZE_PASSTHRU, Z_ARRVAL_P(rval), elements, 0)) {
                return 0;