diff --git a/igbinary.c b/igbinary.c index e7b4d0c..fb7aa25 100644 --- a/igbinary.c +++ b/igbinary.c @@ -138,7 +138,8 @@ struct igbinary_serialize_data { bool scalar; /**< Serializing scalar. */ bool compact_strings; /**< Check for duplicate strings. */ struct hash_si strings; /**< Hash of already serialized strings. */ - struct hash_si objects; /**< Hash of already serialized objects. */ + struct hash_si references; /**< Hash of already serialized potential references. */ + int references_id; /**< Number of things that the unserializer might think are references. >= length of references */ int string_count; /**< Serialized string count, used for back referencing */ int error; /**< Error number. Not used. */ struct igbinary_memory_manager mm; /**< Memory management functions. */ @@ -227,12 +228,12 @@ inline static int igbinary_unserialize_double(struct igbinary_unserialize_data * inline static int igbinary_unserialize_string(struct igbinary_unserialize_data *igsd, enum igbinary_type t, char **s, size_t *len TSRMLS_DC); inline static int igbinary_unserialize_chararray(struct igbinary_unserialize_data *igsd, enum igbinary_type t, char **s, size_t *len TSRMLS_DC); -inline static int igbinary_unserialize_array(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zval *z, int flags TSRMLS_DC); -inline static int igbinary_unserialize_object(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zval *z, int flags TSRMLS_DC); -inline static int igbinary_unserialize_object_ser(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zval *z, zend_class_entry *ce TSRMLS_DC); -inline static int igbinary_unserialize_ref(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zval *z TSRMLS_DC); +inline static int igbinary_unserialize_array(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zval *const z, int flags TSRMLS_DC); +inline static int igbinary_unserialize_object(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zval *const z, int flags TSRMLS_DC); +inline static int igbinary_unserialize_object_ser(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zval *const z, zend_class_entry *ce TSRMLS_DC); +inline static int igbinary_unserialize_ref(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zval *const z, int flags TSRMLS_DC); -static int igbinary_unserialize_zval(struct igbinary_unserialize_data *igsd, zval *z, int flags TSRMLS_DC); +static int igbinary_unserialize_zval(struct igbinary_unserialize_data *igsd, zval *const z, int flags TSRMLS_DC); /* }}} */ /* {{{ arginfo */ ZEND_BEGIN_ARG_INFO_EX(arginfo_igbinary_serialize, 0, 0, 1) @@ -248,7 +249,7 @@ ZEND_END_ARG_INFO() zend_function_entry igbinary_functions[] = { PHP_FE(igbinary_serialize, arginfo_igbinary_serialize) PHP_FE(igbinary_unserialize, arginfo_igbinary_unserialize) - {NULL, NULL, NULL} + PHP_FE_END }; /* }}} */ @@ -264,7 +265,7 @@ static const zend_module_dep igbinary_module_deps[] = { #elif defined(HAVE_APC_SUPPORT) ZEND_MOD_OPTIONAL("apc") #endif - {NULL, NULL, NULL} + ZEND_MOD_END }; #endif /* }}} */ @@ -375,6 +376,30 @@ PHP_MINFO_FUNCTION(igbinary) { DISPLAY_INI_ENTRIES(); } /* }}} */ + +/* {{{ igsd management */ +/* Append to list of references to take out later. Returns SIZE_MAX on allocation error. */ +static inline size_t igsd_append_ref(struct igbinary_unserialize_data *igsd, zval *z) +{ + size_t ref_n; + if (igsd->references_count + 1 >= igsd->references_capacity) { + while (igsd->references_count + 1 >= igsd->references_capacity) { + igsd->references_capacity *= 2; + } + + igsd->references = erealloc(igsd->references, sizeof(igsd->references[0]) * igsd->references_capacity); + if (igsd->references == NULL) { + return SIZE_MAX; + } + } + + + ref_n = igsd->references_count++; + IGB_REF_VAL(igsd, ref_n) = z; + return ref_n; +} +/* }}} */ + /* {{{ Memory allocator wrappers */ static inline void *igbinary_mm_wrapper_malloc(size_t size, void *context) { @@ -663,7 +688,8 @@ inline static int igbinary_serialize_data_init(struct igbinary_serialize_data *i igsd->scalar = scalar; if (!igsd->scalar) { hash_si_init(&igsd->strings, 16); - hash_si_init(&igsd->objects, 16); + hash_si_init(&igsd->references, 16); + igsd->references_id = 0; } igsd->compact_strings = (bool)IGBINARY_G(compact_strings); @@ -680,7 +706,7 @@ inline static void igbinary_serialize_data_deinit(struct igbinary_serialize_data if (!igsd->scalar) { hash_si_deinit(&igsd->strings); - hash_si_deinit(&igsd->objects); + hash_si_deinit(&igsd->references); } } /* }}} */ @@ -780,6 +806,7 @@ inline static int igbinary_serialize_bool(struct igbinary_serialize_data *igsd, /* }}} */ /* {{{ igbinary_serialize_long */ /** Serializes long. */ +/* FIXME this should probably be zend_long for 32-bit builds, php7 got rid of 32-bit longs */ inline static int igbinary_serialize_long(struct igbinary_serialize_data *igsd, long l TSRMLS_DC) { long k = l >= 0 ? l : -l; bool p = l >= 0 ? true : false; @@ -972,16 +999,19 @@ inline static int igbinary_serialize_chararray(struct igbinary_serialize_data *i /* {{{ igbinay_serialize_array */ /** Serializes array or objects inner properties. */ inline static int igbinary_serialize_array(struct igbinary_serialize_data *igsd, zval *z, bool object, bool incomplete_class TSRMLS_DC) { + /* If object=true: z is IS_OBJECT */ + /* If object=false: z is either IS_ARRAY, or IS_REFERENCE pointing to an IS_ARRAY. */ HashTable *h; - HashPosition pos; size_t n; zval *d; + zval *z_original; zend_string *key; - uint key_len; - int key_type; ulong key_index; - + + z_original = z; + ZVAL_DEREF(z); + /* hash */ h = object ? Z_OBJPROP_P(z) : HASH_OF(z); @@ -993,7 +1023,8 @@ inline static int igbinary_serialize_array(struct igbinary_serialize_data *igsd, --n; } - if (!object && igbinary_serialize_array_ref(igsd, z, object TSRMLS_CC) == 0) { + /* if it is an array or a reference to an array, then add a reference unique to that **reference** to that array */ + if (!object && igbinary_serialize_array_ref(igsd, z_original, false TSRMLS_CC) == 0) { return 0; } @@ -1051,6 +1082,7 @@ inline static int igbinary_serialize_array(struct igbinary_serialize_data *igsd, return 1; } + /* https://wiki.php.net/phpng-int - This is a weak pointer, completely different from a PHP reference (&$foo has a type of IS_REFERENCE) */ if (Z_TYPE_P(d) == IS_INDIRECT) { d = Z_INDIRECT_P(d); } @@ -1071,35 +1103,46 @@ inline static int igbinary_serialize_array(struct igbinary_serialize_data *igsd, } /* }}} */ /* {{{ igbinary_serialize_array_ref */ -/** Serializes array reference. */ +/** Serializes array reference (or reference in an object). Returns 0 on success. */ inline static int igbinary_serialize_array_ref(struct igbinary_serialize_data *igsd, zval *z, bool object TSRMLS_DC) { uint32_t t = 0; uint32_t *i = &t; - zend_value key = { 0 }; - - if (Z_TYPE_P(z) == IS_ARRAY) { - key = z->value; - return 1; - } else if (Z_REFCOUNTED_P(z)) { - key = z->value; - } else if (object && Z_TYPE_P(z) == IS_OBJECT) { - key.obj = Z_OBJ_P(z); + zend_ulong key = 0; /* The numeric value of the pointer to the zend_refcounted struct */ + + /* Similar to php_var_serialize_intern's first part, as well as php_add_var_hash, for printing R: (reference) or r:(object) */ + /* However, it differs from the built in serialize() in that references to objects are preserved when serializing and unserializing? (TODO check, test for backwards compatibility) */ + zend_bool is_ref = Z_ISREF_P(z); + /* Do I have to dereference object references so that reference ids will be the same as in php5? */ + /* If I do, then more tests fail. */ + /* is_ref || IS_OBJECT implies it has a unique refcounted struct */ + if (object && Z_TYPE_P(z) == IS_OBJECT) { + key = (zend_ulong) Z_OBJ_HANDLE_P(z); /* expand uint32_t to long */ + } else if (is_ref) { + /* NOTE: PHP removed switched from `zval*` to `zval` for the values stored in HashTables. If an array has two references to the same ZVAL, then those references will have different zvals. We use Z_COUNTED_P(ref), which will be the same iff the references are the same */ + /* IS_REF implies there is a unique reference counting pointer for the reference */ + key = (zend_ulong) (zend_uintptr_t) Z_COUNTED_P(z); } else if (Z_TYPE_P(z) == IS_ARRAY) { - /* FIXME: should be default action? */ - /* FIXME: could use Z_REFCOUNTED_P(z) ? */ - /* Complex types and pointer backed numbers are safe because */ - /* they should remain unique. Longs and doubles as user given */ - /* scalars are not, undef too. */ - key = z->value; + if (Z_REFCOUNTED_P(z)) { + key = (zend_ulong) (zend_uintptr_t) Z_COUNTED_P(z); + } else { /* Not sure if this could be a constant */ + key = (zend_ulong) (zend_uintptr_t) z; + } } else { - /* FIXME: in most cases a pointer to zval becomes useless in php 7 */ - key.zv = z; + /* Nothing else is going to reference this when this is serialized, this isn't ref counted or an object, shouldn't be reached. */ + /* Increment the reference id for the deserializer, give up. */ + ++igsd->references_id; + php_error_docref(NULL TSRMLS_CC, E_NOTICE, "igbinary_serialize_array_ref expected either object or reference (param object=%s), got neither (zend_type=%d)", object ? "true" : "false", (int)Z_TYPE_P(z)); return 1; } - if (hash_si_find(&igsd->objects, (char *)&key, sizeof(key), i) == 1) { - t = hash_si_size(&igsd->objects); - hash_si_insert(&igsd->objects, (char *)&key, sizeof(key), t); + if (hash_si_find(&igsd->references, (const char*) &key, sizeof(key), i) == 1) { + t = igsd->references_id++; + /* FIXME hack? If the top-level element was an array, we assume that it can't be a reference when we serialize it, */ + /* because that's the way it was serialized in php5. */ + /* Does this work with different forms of recursive arrays? */ + if (t > 0 || object) { + hash_si_insert(&igsd->references, (const char*) &key, sizeof(key), t); /* TODO: Add a specialization for fixed-length numeric keys? */ + } return 1; } else { enum igbinary_type type; @@ -1142,15 +1185,11 @@ inline static int igbinary_serialize_array_ref(struct igbinary_serialize_data *i /** Serializes object's properties array with __sleep -function. */ inline static int igbinary_serialize_array_sleep(struct igbinary_serialize_data *igsd, zval *z, HashTable *h, zend_class_entry *ce, bool incomplete_class TSRMLS_DC) { HashTable *object_properties; - HashPosition pos; size_t n = zend_hash_num_elements(h); zval *d; zval *v; zend_string *key; - uint key_len; - int key_type; - ulong key_index; /* Decrease array size by one, because of magic member (with class name) */ if (n > 0 && incomplete_class) { @@ -1190,7 +1229,7 @@ inline static int igbinary_serialize_array_sleep(struct igbinary_serialize_data object_properties = Z_OBJPROP_P(z); - ZEND_HASH_FOREACH_KEY_VAL(h, key_index, key, d) { + ZEND_HASH_FOREACH_STR_KEY_VAL(h, key, d) { /* skip magic member in incomplete classes */ if (incomplete_class && key != NULL && strcmp(ZSTR_VAL(key), MAGIC_MEMBER) == 0) { continue; @@ -1385,7 +1424,7 @@ inline static int igbinary_serialize_object(struct igbinary_serialize_data *igsd if (igbinary_serialize_array_ref(igsd, z, true TSRMLS_CC) == 0) { - return r; + return 0; } ce = Z_OBJCE_P(z); @@ -1528,11 +1567,19 @@ static int igbinary_serialize_zval(struct igbinary_serialize_data *igsd, zval *z return 1; } - /* Complex types serialize a reference, scalars do not... */ - /* FIXME: Absolutely wrong level to check this. */ - if (igbinary_serialize_array_ref(igsd, z, false TSRMLS_CC) == 0) { - return 0; + switch (Z_TYPE_P(Z_REFVAL_P(z))) { + case IS_ARRAY: + return igbinary_serialize_array(igsd, z, false, false TSRMLS_CC); + case IS_OBJECT: + break; /* Fall through */ + default: + /* Serialize a reference if zval already added */ + if (igbinary_serialize_array_ref(igsd, z, false TSRMLS_CC) == 0) { + return 0; + } + /* Fall through */ } + ZVAL_DEREF(z); } switch (Z_TYPE_P(z)) { @@ -1541,6 +1588,7 @@ static int igbinary_serialize_zval(struct igbinary_serialize_data *igsd, zval *z case IS_OBJECT: return igbinary_serialize_object(igsd, z TSRMLS_CC); case IS_ARRAY: + /* if is_ref, then php5 would have called igbinary_serialize_array_ref */ return igbinary_serialize_array(igsd, z, false, false TSRMLS_CC); case IS_STRING: return igbinary_serialize_string(igsd, Z_STRVAL_P(z), Z_STRLEN_P(z) TSRMLS_CC); @@ -1590,6 +1638,7 @@ inline static int igbinary_unserialize_data_init(struct igbinary_unserialize_dat igsd->strings = (struct igbinary_unserialize_string_pair *) emalloc(sizeof(struct igbinary_unserialize_string_pair) * igsd->strings_capacity); if (igsd->strings == NULL) { efree(igsd->references); + igsd->references = NULL; return 1; } @@ -1602,10 +1651,12 @@ inline static int igbinary_unserialize_data_init(struct igbinary_unserialize_dat inline static void igbinary_unserialize_data_deinit(struct igbinary_unserialize_data *igsd TSRMLS_DC) { if (igsd->strings) { efree(igsd->strings); + igsd->strings = NULL; } if (igsd->references) { efree(igsd->references); + igsd->references = NULL; } smart_string_free(&igsd->string0_buf); @@ -1876,12 +1927,15 @@ inline static int igbinary_unserialize_chararray(struct igbinary_unserialize_dat /* }}} */ /* {{{ igbinary_unserialize_array */ /** Unserializes array. */ -inline static int igbinary_unserialize_array(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zval *z, int flags TSRMLS_DC) { +inline static int igbinary_unserialize_array(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zval *const z, int flags TSRMLS_DC) { + /* WANT_OBJECT means that z will be an object (if dereferenced) */ + /* WANT_REF means that z will be wrapped by an IS_REFERENCE */ size_t n; size_t i; zval v; zval *vp; + zval *z_deref; char *key; size_t key_len = 0; @@ -1920,25 +1974,18 @@ inline static int igbinary_unserialize_array(struct igbinary_unserialize_data *i return 1; } + z_deref = z; + if (flags & WANT_REF) { + if (!Z_ISREF_P(z)) { + ZVAL_NEW_REF(z, z); + z_deref = Z_REFVAL_P(z); + } + } if ((flags & WANT_OBJECT) == 0) { - array_init_size(z, n + 1); - - if (flags & WANT_REF) { - /* references */ - if (igsd->references_count + 1 >= igsd->references_capacity) { - while (igsd->references_count + 1 >= igsd->references_capacity) { - igsd->references_capacity *= 2; - } - - igsd->references = erealloc(igsd->references, sizeof(igsd->references[0]) * igsd->references_capacity); - if (igsd->references == NULL) - return 1; - } - - /*ZVAL_COPY_VALUE(IGB_REF_VAL(igsd, igsd->references_count++), z);*/ - IGB_REF_VAL(igsd, igsd->references_count++) = z; - ZVAL_MAKE_REF(z); - ZVAL_DEREF(z); + array_init_size(z_deref, n + 1); + /* add the new array to the list of unserialized references */ + if (igsd_append_ref(igsd, z) == SIZE_MAX) { + return 1; } } @@ -1947,7 +1994,7 @@ inline static int igbinary_unserialize_array(struct igbinary_unserialize_data *i return 0; } - h = HASH_OF(z); + h = HASH_OF(z_deref); for (i = 0; i < n; i++) { key = NULL; @@ -2053,7 +2100,7 @@ inline static int igbinary_unserialize_array(struct igbinary_unserialize_data *i /* }}} */ /* {{{ igbinary_unserialize_object_ser */ /** Unserializes object's property array of objects implementing Serializable -interface. */ -inline static int igbinary_unserialize_object_ser(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zval *z, zend_class_entry *ce TSRMLS_DC) { +inline static int igbinary_unserialize_object_ser(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zval *const z, zend_class_entry *ce TSRMLS_DC) { size_t n; int ret; php_unserialize_data_t var_hash; @@ -2110,9 +2157,8 @@ inline static int igbinary_unserialize_object_ser(struct igbinary_unserialize_da /** Unserialize object. * @see ext/standard/var_unserializer.c */ -inline static int igbinary_unserialize_object(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zval *z, int flags TSRMLS_DC) { +inline static int igbinary_unserialize_object(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zval *const z, int flags TSRMLS_DC) { zend_class_entry *ce; - zend_class_entry **pce; zval h; zval f; @@ -2147,7 +2193,7 @@ inline static int igbinary_unserialize_object(struct igbinary_unserialize_data * do { /* Try to find class directly */ - if (ce = zend_lookup_class(class_name TSRMLS_CC)) { + if ((ce = zend_lookup_class(class_name TSRMLS_CC)) != NULL) { /* FIXME: lookup class may cause exception in load callback */ break; } @@ -2196,58 +2242,71 @@ inline static int igbinary_unserialize_object(struct igbinary_unserialize_data * return 1; } - - /* reference */ - if (igsd->references_count + 1 >= igsd->references_capacity) { - while (igsd->references_count + 1 >= igsd->references_capacity) { - igsd->references_capacity *= 2; - } - - igsd->references = erealloc(igsd->references, sizeof(igsd->references[0]) * igsd->references_capacity); - if (igsd->references == NULL) - return 1; + /* add this to the list of unserialized references, get the index */ + ref_n = igsd_append_ref(igsd, z); + if (ref_n == SIZE_MAX) { + return 1; } - ref_n = igsd->references_count++; - /*ZVAL_UNDEF(IGB_REF_VAL(igsd, ref_n));*/ - IGB_REF_VAL(igsd, ref_n) = z; - t = (enum igbinary_type) igbinary_unserialize8(igsd TSRMLS_CC); switch (t) { case igbinary_type_array8: case igbinary_type_array16: case igbinary_type_array32: - object_init_ex(IGB_REF_VAL(igsd, ref_n), ce); + if (object_init_ex(IGB_REF_VAL(igsd, ref_n), ce) != SUCCESS) { + php_error_docref(NULL TSRMLS_CC, E_NOTICE, "igbinary unable to create object for class entry"); + r = 1; + break; + } + /* TODO: This should be dereferenced if necessary */ if (incomplete_class) { php_store_class_name(IGB_REF_VAL(igsd, ref_n), name, name_len); } - r = igbinary_unserialize_array(igsd, t, IGB_REF_VAL(igsd, ref_n), WANT_OBJECT TSRMLS_CC); + r = igbinary_unserialize_array(igsd, t, IGB_REF_VAL(igsd, ref_n), flags|WANT_OBJECT TSRMLS_CC); break; case igbinary_type_object_ser8: case igbinary_type_object_ser16: case igbinary_type_object_ser32: + r = igbinary_unserialize_object_ser(igsd, t, IGB_REF_VAL(igsd, ref_n), ce TSRMLS_CC); - if (r == 0 && incomplete_class) { + if (r != 0) { + break; + } + if (incomplete_class) { php_store_class_name(IGB_REF_VAL(igsd, ref_n), name, name_len); } + if ((flags & WANT_REF) != 0) { + ZVAL_MAKE_REF(z); + } break; default: zend_error(E_WARNING, "igbinary_unserialize_object: unknown object inner type '%02x', position %zu", t, igsd->buffer_offset); r = 1; } - if (r == 0 && - Z_OBJCE_P(IGB_REF_VAL(igsd, ref_n)) != PHP_IC_ENTRY && - zend_hash_str_exists(&Z_OBJCE_P(IGB_REF_VAL(igsd, ref_n))->function_table, "__wakeup", sizeof("__wakeup") - 1)) { - ZVAL_UNDEF(&h); - ZVAL_STRINGL(&f, "__wakeup", sizeof("__wakeup") - 1); - call_user_function_ex(CG(function_table), IGB_REF_VAL(igsd, ref_n), &f, &h, 0, 0, 1, NULL TSRMLS_CC); + /* If unserialize was successful, call __wakeup if __wakeup exists for this object. */ + if (r == 0) { + zval *ztemp = IGB_REF_VAL(igsd, ref_n); + zend_class_entry *ztemp_ce; + /* May have created a reference while deserializing an object, if it was recursive. */ + ZVAL_DEREF(ztemp); + if (Z_TYPE_P(ztemp) != IS_OBJECT) { + zend_error(E_WARNING, "igbinary_unserialize_object preparing to __wakeup: created non-object somehow?", t, igsd->buffer_offset); + return 1; + } + ztemp_ce = Z_OBJCE_P(ztemp); + if (ztemp_ce != PHP_IC_ENTRY && + zend_hash_str_exists(&ztemp_ce->function_table, "__wakeup", sizeof("__wakeup") - 1)) { + ZVAL_UNDEF(&h); + ZVAL_STRINGL(&f, "__wakeup", sizeof("__wakeup") - 1); + call_user_function_ex(CG(function_table), ztemp, &f, &h, 0, 0, 1, NULL TSRMLS_CC); - zval_dtor(&f); - zval_ptr_dtor(&h); + zval_dtor(&f); + zval_ptr_dtor(&h); - if (EG(exception)) { - r = 1; + if (EG(exception)) { + r = 1; + } } } @@ -2258,8 +2317,9 @@ inline static int igbinary_unserialize_object(struct igbinary_unserialize_data * /* }}} */ /* {{{ igbinary_unserialize_ref */ /** Unserializes array or object by reference. */ -inline static int igbinary_unserialize_ref(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zval *z TSRMLS_DC) { +inline static int igbinary_unserialize_ref(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zval *const z, int flags TSRMLS_DC) { size_t n; + zval* z_ref = NULL; if (t == igbinary_type_ref8 || t == igbinary_type_objref8) { if (igsd->buffer_offset + 1 > igsd->buffer_size) { @@ -2285,7 +2345,7 @@ inline static int igbinary_unserialize_ref(struct igbinary_unserialize_data *igs } if (n >= igsd->references_count) { - zend_error(E_WARNING, "igbinary_unserialize_ref: invalid reference"); + zend_error(E_WARNING, "igbinary_unserialize_ref: invalid reference %zu >= %zu", (int) n, (int)igsd->references_count); return 1; } @@ -2295,11 +2355,24 @@ inline static int igbinary_unserialize_ref(struct igbinary_unserialize_data *igs ZVAL_UNDEF(z); } - ZVAL_COPY(z, IGB_REF_VAL(igsd, n)); - if (t == igbinary_type_objref8 || t == igbinary_type_objref16 || t == igbinary_type_objref32) { - /* FIXME: clear/set ref.. */ - /*Z_SET_ISREF_TO_P(z, false);*/ - /*ZVAL_MAKE_REF(z);*/ + z_ref = IGB_REF_VAL(igsd, n); + + /** + * Permanently convert the zval in IGB_REF_VAL() into a IS_REFERENCE if it wasn't already one. + * TODO: Can there properly be multiple reference groups to an object? + * Similar to https://github.com/php/php-src/blob/master/ext/standard/var_unserializer.re , for "R:" + * Using `flags` because igbinary_unserialize_ref might be used both for copy on writes ($a = $b = [2]) and by PHP references($a = &$b). + */ + if ((flags & WANT_REF) != 0) { + /* Want to create an IS_REFERENCE, not just to share the same value until modified. */ + ZVAL_COPY(z, z_ref); + if (!Z_ISREF_P(z)) { + ZVAL_MAKE_REF(z); /* Convert original zval data to a reference and replace the entry in IGB_REF_VAL with that. */ + IGB_REF_VAL(igsd, n) = z; + } + } else { + ZVAL_DEREF(z_ref); + ZVAL_COPY(z, z_ref); } return 0; @@ -2307,15 +2380,13 @@ inline static int igbinary_unserialize_ref(struct igbinary_unserialize_data *igs /* }}} */ /* {{{ igbinary_unserialize_zval */ /** Unserialize zval. */ -static int igbinary_unserialize_zval(struct igbinary_unserialize_data *igsd, zval *z, int flags TSRMLS_DC) { +static int igbinary_unserialize_zval(struct igbinary_unserialize_data *igsd, zval *const z, int flags TSRMLS_DC) { enum igbinary_type t; long tmp_long; double tmp_double; char *tmp_chararray; - zval *zp; size_t tmp_size_t; - size_t ref_n; if (igsd->buffer_offset + 1 > igsd->buffer_size) { zend_error(E_WARNING, "igbinary_unserialize_zval: end-of-data"); @@ -2330,27 +2401,31 @@ static int igbinary_unserialize_zval(struct igbinary_unserialize_data *igsd, zva return 1; } + /* If it is already a ref, nothing to do */ if (Z_ISREF_P(z)) { break; } - - /* reference */ - if (igsd->references_count + 1 >= igsd->references_capacity) { - while (igsd->references_count + 1 >= igsd->references_capacity) { - igsd->references_capacity *= 2; - } - - igsd->references = erealloc(igsd->references, sizeof(igsd->references[0]) * igsd->references_capacity); - if (igsd->references == NULL) - return 1; + switch (Z_TYPE_P(z)) { + case IS_STRING: + case IS_LONG: + case IS_NULL: + case IS_DOUBLE: + case IS_FALSE: + case IS_TRUE: + /* add the unserialized scalar to the list of unserialized references. Objects and arrays were already added in igbinary_unserialize_zval. */ + if (igsd_append_ref(igsd, z) == SIZE_MAX) { + return 1; + } + break; + default: + break; } - ref_n = igsd->references_count++; - - + /* Permanently convert the zval in IGB_REF_VAL() into a IS_REFERENCE if it wasn't already one. */ + /* TODO: Support multiple reference groups to the same object */ + /* Similar to https://github.com/php/php-src/blob/master/ext/standard/var_unserializer.re , for "R:" */ ZVAL_MAKE_REF(z); - IGB_REF_VAL(igsd, ref_n) = z; - /*ZVAL_COPY_VALUE(IGB_REF_VAL(igsd, ref_n), z);*/ + break; case igbinary_type_objref8: case igbinary_type_objref16: @@ -2358,7 +2433,7 @@ static int igbinary_unserialize_zval(struct igbinary_unserialize_data *igsd, zva case igbinary_type_ref8: case igbinary_type_ref16: case igbinary_type_ref32: - if (igbinary_unserialize_ref(igsd, t, z TSRMLS_CC)) { + if (igbinary_unserialize_ref(igsd, t, z, flags TSRMLS_CC)) { return 1; } break; diff --git a/tests/igbinary_009.phpt b/tests/igbinary_009.phpt index 76e5efd..49cac65 100644 --- a/tests/igbinary_009.phpt +++ b/tests/igbinary_009.phpt @@ -8,13 +8,21 @@ if(!extension_loaded('igbinary')) { --FILE-- + &string(1) "V" + [1]=> + &string(1) "V" + [2]=> + &string(1) "V" + [3]=> + &string(1) "V" +} diff --git a/tests/igbinary_046c.phpt b/tests/igbinary_046c.phpt new file mode 100644 index 0000000..fdfeb89 --- /dev/null +++ b/tests/igbinary_046c.phpt @@ -0,0 +1,33 @@ +--TEST-- +Correctly unserialize multiple array refs. +--SKIPIF-- +--INI-- +igbinary.compact_strings = On +--FILE-- + + &string(1) "V" + [1]=> + &string(1) "V" + [2]=> + &string(1) "V" + [3]=> + &string(1) "V" +} diff --git a/tests/igbinary_046d.phpt b/tests/igbinary_046d.phpt new file mode 100644 index 0000000..0f0761a --- /dev/null +++ b/tests/igbinary_046d.phpt @@ -0,0 +1,67 @@ +--TEST-- +Correctly unserialize multiple object refs and non-refs. +--SKIPIF-- +--INI-- +igbinary.compact_strings = On +--FILE-- + + object(stdClass)#%d (0) { + } + [1]=> + &object(stdClass)#%d (0) { + } + [2]=> + &object(stdClass)#%d (0) { + } + [3]=> + object(stdClass)#%d (0) { + } +} +a:4:{i:0;O:8:"stdClass":0:{}i:1;R:2;i:2;R:2;i:3;r:2;} +00000002140406001708737464436c61737314000601252201060225220106032201 +a:4:{i:0;O:8:"stdClass":0:{}i:1;R:2;i:2;R:2;i:3;r:2;} +array(4) { + [0]=> + object(stdClass)#%d (0) { + } + [1]=> + &object(stdClass)#%d (0) { + } + [2]=> + &object(stdClass)#%d (0) { + } + [3]=> + object(stdClass)#%d (0) { + } +} +array(4) { + [0]=> + object(stdClass)#%d (0) { + } + [1]=> + &string(1) "V" + [2]=> + &string(1) "V" + [3]=> + object(stdClass)#%d (0) { + } +} diff --git a/tests/igbinary_049.phpt b/tests/igbinary_049.phpt new file mode 100644 index 0000000..2aa0bf1 --- /dev/null +++ b/tests/igbinary_049.phpt @@ -0,0 +1,120 @@ +--TEST-- +Correctly unserialize multiple references in arrays +--SKIPIF-- +--INI-- +igbinary.compact_strings = On +--FILE-- +prop = &$a[8]; +$a[11] = &$a[10]; +$a[12] = $a[9]; +$ig_ser = igbinary_serialize($a); +printf("ig_ser=%s\n", bin2hex($ig_ser)); +$ig = igbinary_unserialize($ig_ser); +var_dump($ig); +$f = &$ig[3]; +$f = 'V'; +$g = &$ig[5]; +$g = 'H'; +$h = $ig[10]; +$h->prop = 'S'; +var_dump($ig); +--EXPECTF-- +ig_ser=00000002140d0600251101410601250101060225010106032501010604250406052501020606251703466f6f1400060725220306082522030609140106000621060a251708737464436c6173731401110470726f70252203060b252205060c0104 +array(13) { + [0]=> + &string(1) "A" + [1]=> + &string(1) "A" + [2]=> + &string(1) "A" + [3]=> + &string(1) "A" + [4]=> + &bool(false) + [5]=> + &bool(false) + [6]=> + &object(Foo)#%d (0) { + } + [7]=> + &object(Foo)#%d (0) { + } + [8]=> + &object(Foo)#%d (0) { + } + [9]=> + array(1) { + [0]=> + int(33) + } + [10]=> + &object(stdClass)#%d (1) { + ["prop"]=> + &object(Foo)#%d (0) { + } + } + [11]=> + &object(stdClass)#%d (1) { + ["prop"]=> + &object(Foo)#%d (0) { + } + } + [12]=> + array(1) { + [0]=> + int(33) + } +} +array(13) { + [0]=> + &string(1) "V" + [1]=> + &string(1) "V" + [2]=> + &string(1) "V" + [3]=> + &string(1) "V" + [4]=> + &string(1) "H" + [5]=> + &string(1) "H" + [6]=> + &string(1) "S" + [7]=> + &string(1) "S" + [8]=> + &string(1) "S" + [9]=> + array(1) { + [0]=> + int(33) + } + [10]=> + &object(stdClass)#%d (1) { + ["prop"]=> + &string(1) "S" + } + [11]=> + &object(stdClass)#%d (1) { + ["prop"]=> + &string(1) "S" + } + [12]=> + array(1) { + [0]=> + int(33) + } +} diff --git a/tests/igbinary_049b.phpt b/tests/igbinary_049b.phpt new file mode 100644 index 0000000..090e1a2 --- /dev/null +++ b/tests/igbinary_049b.phpt @@ -0,0 +1,75 @@ +--TEST-- +Correctly unserialize multiple references in objects +--SKIPIF-- +--INI-- +igbinary.compact_strings = On +--FILE-- +x0 = NULL; +$a->x1 = &$a->x0; +$a->x2 = &$a->x1; +$a->x3 = &$a->x2; +$a->x4 = false; +$a->x5 = &$a->x4; +$a->x6 = new Foo(); +$a->x7 = &$a->x6; +$a->x8 = &$a->x7; +$a->x9 = [33]; +$a->x10 = new stdClass(); +$a->x10->prop = &$a->x8; +$a->x11 = &$a->x10; +$a->x12 = $a->x9; +$ig_ser = igbinary_serialize($a); +printf("ig_ser=%s\n", bin2hex($ig_ser)); +$ig = igbinary_unserialize($ig_ser); +$f = &$ig->x3; +$f = 'V'; +$g = &$ig->x5; +$g = 'H'; +$h = $ig->x10; +$h->prop = 'S'; +var_dump($ig); +--EXPECTF-- +ig_ser=000000021708737464436c617373140d1102783025001102783125010111027832250101110278332501011102783425041102783525010211027836251703466f6f14001102783725220311027838252203110278391401060006211103783130251a001401110470726f70252203110378313125220511037831320104 +object(stdClass)#%d (13) { + ["x0"]=> + &string(1) "V" + ["x1"]=> + &string(1) "V" + ["x2"]=> + &string(1) "V" + ["x3"]=> + &string(1) "V" + ["x4"]=> + &string(1) "H" + ["x5"]=> + &string(1) "H" + ["x6"]=> + &string(1) "S" + ["x7"]=> + &string(1) "S" + ["x8"]=> + &string(1) "S" + ["x9"]=> + array(1) { + [0]=> + int(33) + } + ["x10"]=> + &object(stdClass)#%d (1) { + ["prop"]=> + &string(1) "S" + } + ["x11"]=> + &object(stdClass)#%d (1) { + ["prop"]=> + &string(1) "S" + } + ["x12"]=> + array(1) { + [0]=> + int(33) + } +} diff --git a/tests/igbinary_050.phpt b/tests/igbinary_050.phpt new file mode 100644 index 0000000..71c2a39 --- /dev/null +++ b/tests/igbinary_050.phpt @@ -0,0 +1,40 @@ +--TEST-- +Correctly unserialize cyclic object references +--SKIPIF-- +--INI-- +igbinary.compact_strings = On +--FILE-- +foo = &$a; +$a->bar = &$a; +$b = new stdClass(); +$b->cyclic = &$a; +printf("%s\n", serialize($b)); +$ig_ser = igbinary_serialize($b); +printf("%s\n", bin2hex($ig_ser)); +$ig = igbinary_unserialize($ig_ser); +printf("%s\n", serialize($ig)); +var_dump($ig); +$f = &$ig->cyclic->foo; +$f = 'V'; +var_dump($ig); +// Note: While the php7 unserializer consistently makes a distinction between refs to an object and non-refs, +// the php5 serializer does not yet. +--EXPECTF-- +O:8:"stdClass":1:{s:6:"cyclic";O:8:"stdClass":2:{s:3:"foo";R:2;s:3:"bar";R:2;}} +000000021708737464436c617373140111066379636c6963251a0014021103666f6f2522011103626172252201 +O:8:"stdClass":1:{s:6:"cyclic";O:8:"stdClass":2:{s:3:"foo";R:2;s:3:"bar";R:2;}} +object(stdClass)#3 (1) { + ["cyclic"]=> + &object(stdClass)#4 (2) { + ["foo"]=> + *RECURSION* + ["bar"]=> + *RECURSION* + } +} +object(stdClass)#3 (1) { + ["cyclic"]=> + &string(1) "V" +} diff --git a/tests/igbinary_051.phpt b/tests/igbinary_051.phpt new file mode 100644 index 0000000..58f1d0c --- /dev/null +++ b/tests/igbinary_051.phpt @@ -0,0 +1,46 @@ +--TEST-- +Object test, __wakeup (With multiple references) +--SKIPIF-- +--FILE-- +a = $a; + $this->b = $b; + } + + function __wakeup() { + $this->b = $this->a * 3; + } +} + +function main() { + $o = new Obj(1, 2); + $variable = [&$o, &$o]; + $serialized = igbinary_serialize($variable); + $unserialized = igbinary_unserialize($serialized); + + echo substr(bin2hex($serialized), 8), "\n"; + echo $unserialized[0]->b === 3 && $unserialized[0]->a === 1 ? 'OK' : 'ERROR'; + echo "\n"; + $unserialized[0] = 'a'; + var_dump($unserialized); +} + +main(); +--EXPECT-- +140206002517034f626a1402110161060111016206020601252201 +OK +array(2) { + [0]=> + &string(1) "a" + [1]=> + &string(1) "a" +} diff --git a/tests/igbinary_052.phpt b/tests/igbinary_052.phpt new file mode 100644 index 0000000..cc5b7cc --- /dev/null +++ b/tests/igbinary_052.phpt @@ -0,0 +1,81 @@ +--TEST-- +Object Serializable interface can be serialized in references +--SKIPIF-- +--FILE-- +a = $a; + $this->b = $b; + } + + public function serialize() { + $c = self::$count++; + echo "call serialize\n"; + return pack('NN', $this->a, $this->b); + } + + public function unserialize($serialized) { + $tmp = unpack('N*', $serialized); + $this->__construct($tmp[1], $tmp[2]); + $c = self::$count++; + echo "call unserialize\n"; + } +} + +function main() { + $a = new Obj(1, 0); + $b = new Obj(42, 43); + $variable = [&$a, &$a, $b]; + $serialized = igbinary_serialize($variable); + printf("%s\n", bin2hex($serialized)); + $unserialized = igbinary_unserialize($serialized); + var_dump($unserialized); + $unserialized[0] = 'A'; + var_dump($unserialized[1]); +} +main(); +--EXPECTF-- +call serialize +call serialize +00000002140306002517034f626a1d080000000100000000060125220106021a001d080000002a0000002b +call unserialize +call unserialize +array(3) { + [0]=> + &object(Obj)#%d (2) { + ["a"]=> + int(1) + ["b"]=> + int(0) + } + [1]=> + &object(Obj)#%d (2) { + ["a"]=> + int(1) + ["b"]=> + int(0) + } + [2]=> + object(Obj)#%d (2) { + ["a"]=> + int(42) + ["b"]=> + int(43) + } +} +string(1) "A" diff --git a/tests/igbinary_bug72134.phpt b/tests/igbinary_bug72134.phpt new file mode 100644 index 0000000..b126728 --- /dev/null +++ b/tests/igbinary_bug72134.phpt @@ -0,0 +1,47 @@ +--TEST-- +igbinary_unserialize causes segfault on 3rd call for objects with dynamic property +--FILE-- +i = 1; +$igb = igbinary_serialize($value); +for ($i=0; $i < 30; $i++) +{ + // This used to segfault at the third attempt + echo igbinary_unserialize($igb)->bar . PHP_EOL; +} +--EXPECT-- +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test