diff --git a/Zend/zend.c b/Zend/zend.c index 8c3351e177f22..318b8b8b1a40d 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -546,6 +546,7 @@ static void zend_print_zval_r_to_buf(smart_str *buf, zval *expr, int indent) /* HashTable *properties; zend_object *zobj = Z_OBJ_P(expr); + uint32_t *guard = zend_get_recursion_guard(zobj); zend_string *class_name = Z_OBJ_HANDLER_P(expr, get_class_name)(zobj); smart_str_appends(buf, ZSTR_VAL(class_name)); zend_string_release_ex(class_name, 0); @@ -561,7 +562,7 @@ static void zend_print_zval_r_to_buf(smart_str *buf, zval *expr, int indent) /* smart_str_appendc(buf, '\n'); } - if (GC_IS_RECURSIVE(Z_OBJ_P(expr))) { + if (ZEND_GUARD_OR_GC_IS_RECURSIVE(guard, DEBUG, zobj)) { smart_str_appends(buf, " *RECURSION*"); return; } @@ -571,9 +572,9 @@ static void zend_print_zval_r_to_buf(smart_str *buf, zval *expr, int indent) /* break; } - GC_PROTECT_RECURSION(Z_OBJ_P(expr)); + ZEND_GUARD_OR_GC_PROTECT_RECURSION(guard, DEBUG, zobj); print_hash(buf, properties, indent, 1); - GC_UNPROTECT_RECURSION(Z_OBJ_P(expr)); + ZEND_GUARD_OR_GC_UNPROTECT_RECURSION(guard, DEBUG, zobj); zend_release_properties(properties); break; diff --git a/Zend/zend_API.c b/Zend/zend_API.c index d0b863335e291..d629b7166a58b 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -2746,6 +2746,7 @@ ZEND_API void zend_add_magic_method(zend_class_entry *ce, zend_function *fptr, z ce->__tostring = fptr; } else if (zend_string_equals_literal(lcname, ZEND_DEBUGINFO_FUNC_NAME)) { ce->__debugInfo = fptr; + ce->ce_flags |= ZEND_ACC_USE_GUARDS; } else if (zend_string_equals_literal(lcname, "__serialize")) { ce->__serialize = fptr; } else if (zend_string_equals_literal(lcname, "__unserialize")) { diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index b18d0f46f80a6..8985bb9edb5e4 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -36,10 +36,10 @@ #define ZEND_WRONG_PROPERTY_OFFSET 0 /* guard flags */ -#define IN_GET (1<<0) -#define IN_SET (1<<1) -#define IN_UNSET (1<<2) -#define IN_ISSET (1<<3) +#define IN_GET ZEND_GUARD_PROPERTY_GET +#define IN_SET ZEND_GUARD_PROPERTY_SET +#define IN_UNSET ZEND_GUARD_PROPERTY_UNSET +#define IN_ISSET ZEND_GUARD_PROPERTY_ISSET /* __X accessors explanation: @@ -542,30 +542,36 @@ static void zend_property_guard_dtor(zval *el) /* {{{ */ { } /* }}} */ +static zend_always_inline zval *zend_get_guard_value(zend_object *zobj) +{ + return zobj->properties_table + zobj->ce->default_properties_count; +} + ZEND_API uint32_t *zend_get_property_guard(zend_object *zobj, zend_string *member) /* {{{ */ { HashTable *guards; zval *zv; uint32_t *ptr; + ZEND_ASSERT(zobj->ce->ce_flags & ZEND_ACC_USE_GUARDS); - zv = zobj->properties_table + zobj->ce->default_properties_count; + zv = zend_get_guard_value(zobj); if (EXPECTED(Z_TYPE_P(zv) == IS_STRING)) { zend_string *str = Z_STR_P(zv); if (EXPECTED(str == member) || /* str and member don't necessarily have a pre-calculated hash value here */ EXPECTED(zend_string_equal_content(str, member))) { - return &Z_PROPERTY_GUARD_P(zv); - } else if (EXPECTED(Z_PROPERTY_GUARD_P(zv) == 0)) { + return &Z_GUARD_P(zv); + } else if (EXPECTED(Z_GUARD_P(zv) == 0)) { zval_ptr_dtor_str(zv); ZVAL_STR_COPY(zv, member); - return &Z_PROPERTY_GUARD_P(zv); + return &Z_GUARD_P(zv); } else { ALLOC_HASHTABLE(guards); zend_hash_init(guards, 8, NULL, zend_property_guard_dtor, 0); /* mark pointer as "special" using low bit */ zend_hash_add_new_ptr(guards, str, - (void*)(((uintptr_t)&Z_PROPERTY_GUARD_P(zv)) | 1)); + (void*)(((uintptr_t)&Z_GUARD_P(zv)) | 1)); zval_ptr_dtor_str(zv); ZVAL_ARR(zv, guards); } @@ -579,8 +585,8 @@ ZEND_API uint32_t *zend_get_property_guard(zend_object *zobj, zend_string *membe } else { ZEND_ASSERT(Z_TYPE_P(zv) == IS_UNDEF); ZVAL_STR_COPY(zv, member); - Z_PROPERTY_GUARD_P(zv) = 0; - return &Z_PROPERTY_GUARD_P(zv); + Z_GUARD_P(zv) &= ~ZEND_GUARD_PROPERTY_MASK; + return &Z_GUARD_P(zv); } /* we have to allocate uint32_t separately because ht->arData may be reallocated */ ptr = (uint32_t*)emalloc(sizeof(uint32_t)); @@ -589,6 +595,15 @@ ZEND_API uint32_t *zend_get_property_guard(zend_object *zobj, zend_string *membe } /* }}} */ +ZEND_API uint32_t *zend_get_recursion_guard(zend_object *zobj) +{ + if (!(zobj->ce->ce_flags & ZEND_ACC_USE_GUARDS)) { + return NULL; + } + zval *zv = zend_get_guard_value(zobj); + return &Z_GUARD_P(zv); +} + ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int type, void **cache_slot, zval *rv) /* {{{ */ { zval *retval; diff --git a/Zend/zend_object_handlers.h b/Zend/zend_object_handlers.h index 475ba6263825d..23a6c18241603 100644 --- a/Zend/zend_object_handlers.h +++ b/Zend/zend_object_handlers.h @@ -241,6 +241,10 @@ ZEND_API zend_function *zend_get_call_trampoline_func(const zend_class_entry *ce ZEND_API uint32_t *zend_get_property_guard(zend_object *zobj, zend_string *member); +ZEND_API uint32_t *zend_get_property_guard(zend_object *zobj, zend_string *member); + +ZEND_API uint32_t *zend_get_recursion_guard(zend_object *zobj); + /* Default behavior for get_properties_for. For use as a fallback in custom * get_properties_for implementations. */ ZEND_API HashTable *zend_std_get_properties_for(zend_object *obj, zend_prop_purpose purpose); diff --git a/Zend/zend_objects.c b/Zend/zend_objects.c index 4d40b76854df0..4c4b3cf30c13d 100644 --- a/Zend/zend_objects.c +++ b/Zend/zend_objects.c @@ -35,7 +35,9 @@ static zend_always_inline void _zend_object_std_init(zend_object *object, zend_c object->properties = NULL; zend_objects_store_put(object); if (UNEXPECTED(ce->ce_flags & ZEND_ACC_USE_GUARDS)) { - ZVAL_UNDEF(object->properties_table + object->ce->default_properties_count); + zval *guard_value = object->properties_table + object->ce->default_properties_count; + ZVAL_UNDEF(guard_value); + Z_GUARD_P(guard_value) = 0; } } diff --git a/Zend/zend_types.h b/Zend/zend_types.h index bf4fd9d18a41a..a7d12a3af8abf 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -345,7 +345,7 @@ struct _zval_struct { uint32_t num_args; /* arguments number for EX(This) */ uint32_t fe_pos; /* foreach position */ uint32_t fe_iter_idx; /* foreach iterator index */ - uint32_t property_guard; /* single property guard */ + uint32_t guard; /* recursion and single property guard */ uint32_t constant_flags; /* constant flags */ uint32_t extra; /* not further specified */ } u2; @@ -619,6 +619,22 @@ struct _zend_ast_ref { #define _IS_BOOL 18 #define _IS_NUMBER 19 +/* guard flags */ +#define ZEND_GUARD_PROPERTY_GET (1<<0) +#define ZEND_GUARD_PROPERTY_SET (1<<1) +#define ZEND_GUARD_PROPERTY_UNSET (1<<2) +#define ZEND_GUARD_PROPERTY_ISSET (1<<3) +#define ZEND_GUARD_PROPERTY_MASK 15 +#define ZEND_GUARD_RECURSION_DEBUG (1<<4) +#define ZEND_GUARD_RECURSION_EXPORT (1<<5) +#define ZEND_GUARD_RECURSION_JSON (1<<6) + +#define ZEND_GUARD_RECURSION_TYPE(t) ZEND_GUARD_RECURSION_ ## t + +#define ZEND_GUARD_IS_RECURSIVE(pg, t) ((*pg & ZEND_GUARD_RECURSION_TYPE(t)) != 0) +#define ZEND_GUARD_PROTECT_RECURSION(pg, t) *pg |= ZEND_GUARD_RECURSION_TYPE(t) +#define ZEND_GUARD_UNPROTECT_RECURSION(pg, t) *pg &= ~ZEND_GUARD_RECURSION_TYPE(t) + static zend_always_inline uint8_t zval_get_type(const zval* pz) { return pz->u1.v.type; } @@ -659,8 +675,8 @@ static zend_always_inline uint8_t zval_get_type(const zval* pz) { #define Z_FE_ITER(zval) (zval).u2.fe_iter_idx #define Z_FE_ITER_P(zval_p) Z_FE_ITER(*(zval_p)) -#define Z_PROPERTY_GUARD(zval) (zval).u2.property_guard -#define Z_PROPERTY_GUARD_P(zval_p) Z_PROPERTY_GUARD(*(zval_p)) +#define Z_GUARD(zval) (zval).u2.guard +#define Z_GUARD_P(zval_p) Z_GUARD(*(zval_p)) #define Z_CONSTANT_FLAGS(zval) (zval).u2.constant_flags #define Z_CONSTANT_FLAGS_P(zval_p) Z_CONSTANT_FLAGS(*(zval_p)) @@ -859,6 +875,25 @@ static zend_always_inline uint32_t zval_gc_info(uint32_t gc_type_info) { #define Z_PROTECT_RECURSION_P(zv) Z_PROTECT_RECURSION(*(zv)) #define Z_UNPROTECT_RECURSION_P(zv) Z_UNPROTECT_RECURSION(*(zv)) +#define ZEND_GUARD_OR_GC_IS_RECURSIVE(pg, t, zobj) \ + (pg ? ZEND_GUARD_IS_RECURSIVE(pg, t) : GC_IS_RECURSIVE(zobj)) + +#define ZEND_GUARD_OR_GC_PROTECT_RECURSION(pg, t, zobj) do { \ + if (pg) { \ + ZEND_GUARD_PROTECT_RECURSION(pg, t); \ + } else { \ + GC_PROTECT_RECURSION(zobj); \ + } \ + } while(0) + +#define ZEND_GUARD_OR_GC_UNPROTECT_RECURSION(pg, t, zobj) do { \ + if (pg) { \ + ZEND_GUARD_UNPROTECT_RECURSION(pg, t); \ + } else { \ + GC_UNPROTECT_RECURSION(zobj); \ + } \ + } while(0) + /* All data types < IS_STRING have their constructor/destructors skipped */ #define Z_CONSTANT(zval) (Z_TYPE(zval) == IS_CONSTANT_AST) #define Z_CONSTANT_P(zval_p) Z_CONSTANT(*(zval_p)) diff --git a/ext/json/json.c b/ext/json/json.c index d102edebb23a4..31ed76703ec12 100644 --- a/ext/json/json.c +++ b/ext/json/json.c @@ -37,10 +37,17 @@ PHP_JSON_API zend_class_entry *php_json_exception_ce; PHP_JSON_API ZEND_DECLARE_MODULE_GLOBALS(json) +static int php_json_implement_json_serializable(zend_class_entry *interface, zend_class_entry *class_type) +{ + class_type->ce_flags |= ZEND_ACC_USE_GUARDS; + return SUCCESS; +} + /* {{{ MINIT */ static PHP_MINIT_FUNCTION(json) { php_json_serializable_ce = register_class_JsonSerializable(); + php_json_serializable_ce->interface_gets_implemented = php_json_implement_json_serializable; php_json_exception_ce = register_class_JsonException(zend_ce_exception); diff --git a/ext/json/json_encoder.c b/ext/json/json_encoder.c index 14fd86d73426c..4709c0e2be4a7 100644 --- a/ext/json/json_encoder.c +++ b/ext/json/json_encoder.c @@ -531,11 +531,14 @@ zend_result php_json_escape_string( static zend_result php_json_encode_serializable_object(smart_str *buf, zval *val, int options, php_json_encoder *encoder) /* {{{ */ { zend_class_entry *ce = Z_OBJCE_P(val); - HashTable* myht = Z_OBJPROP_P(val); + zend_object *obj = Z_OBJ_P(val); + uint32_t *guard = zend_get_recursion_guard(obj); zval retval, fname; zend_result return_code; - if (myht && GC_IS_RECURSIVE(myht)) { + ZEND_ASSERT(guard != NULL); + + if (ZEND_GUARD_IS_RECURSIVE(guard, JSON)) { encoder->error_code = PHP_JSON_ERROR_RECURSION; if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) { smart_str_appendl(buf, "null", 4); @@ -543,7 +546,7 @@ static zend_result php_json_encode_serializable_object(smart_str *buf, zval *val return FAILURE; } - PHP_JSON_HASH_PROTECT_RECURSION(myht); + ZEND_GUARD_PROTECT_RECURSION(guard, JSON); ZVAL_STRING(&fname, "jsonSerialize"); @@ -556,7 +559,7 @@ static zend_result php_json_encode_serializable_object(smart_str *buf, zval *val if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) { smart_str_appendl(buf, "null", 4); } - PHP_JSON_HASH_UNPROTECT_RECURSION(myht); + ZEND_GUARD_UNPROTECT_RECURSION(guard, JSON); return FAILURE; } @@ -568,19 +571,19 @@ static zend_result php_json_encode_serializable_object(smart_str *buf, zval *val if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) { smart_str_appendl(buf, "null", 4); } - PHP_JSON_HASH_UNPROTECT_RECURSION(myht); + ZEND_GUARD_UNPROTECT_RECURSION(guard, JSON); return FAILURE; } if ((Z_TYPE(retval) == IS_OBJECT) && (Z_OBJ(retval) == Z_OBJ_P(val))) { /* Handle the case where jsonSerialize does: return $this; by going straight to encode array */ - PHP_JSON_HASH_UNPROTECT_RECURSION(myht); + ZEND_GUARD_UNPROTECT_RECURSION(guard, JSON); return_code = php_json_encode_array(buf, &retval, options, encoder); } else { /* All other types, encode as normal */ return_code = php_json_encode_zval(buf, &retval, options, encoder); - PHP_JSON_HASH_UNPROTECT_RECURSION(myht); + ZEND_GUARD_UNPROTECT_RECURSION(guard, JSON); } zval_ptr_dtor(&retval); diff --git a/ext/json/tests/json_encode_recursion_01.phpt b/ext/json/tests/json_encode_recursion_01.phpt new file mode 100644 index 0000000000000..f1bbf9155bc71 --- /dev/null +++ b/ext/json/tests/json_encode_recursion_01.phpt @@ -0,0 +1,26 @@ +--TEST-- +json_encode() Recursion test with just JsonSerializable +--FILE-- + +--EXPECT-- +bool(false) +string(7) "{"a":1}" diff --git a/ext/json/tests/json_encode_recursion_02.phpt b/ext/json/tests/json_encode_recursion_02.phpt new file mode 100644 index 0000000000000..aa525f5a30717 --- /dev/null +++ b/ext/json/tests/json_encode_recursion_02.phpt @@ -0,0 +1,25 @@ +--TEST-- +json_encode() Recursion test with JsonSerializable and var_dump simple +--FILE-- + +--EXPECT-- +object(SerializingTest)#1 (1) { + ["a"]=> + int(1) +} +string(7) "{"a":1}" diff --git a/ext/json/tests/json_encode_recursion_03.phpt b/ext/json/tests/json_encode_recursion_03.phpt new file mode 100644 index 0000000000000..dac0b0ea313eb --- /dev/null +++ b/ext/json/tests/json_encode_recursion_03.phpt @@ -0,0 +1,38 @@ +--TEST-- +json_encode() Recursion test with JsonSerializable and __debugInfo +--FILE-- + json_encode($this) ]; + } + + public function jsonSerialize(): mixed + { + var_dump($this); + return $this; + } +} + +var_dump(json_encode(new SerializingTest())); +echo "---------\n"; +var_dump(new SerializingTest()); + +?> +--EXPECT-- +object(SerializingTest)#1 (1) { + ["result"]=> + bool(false) +} +string(7) "{"a":1}" +--------- +*RECURSION* +object(SerializingTest)#1 (1) { + ["result"]=> + string(7) "{"a":1}" +} diff --git a/ext/json/tests/json_encode_recursion_04.phpt b/ext/json/tests/json_encode_recursion_04.phpt new file mode 100644 index 0000000000000..1dde4e4f4a095 --- /dev/null +++ b/ext/json/tests/json_encode_recursion_04.phpt @@ -0,0 +1,41 @@ +--TEST-- +json_encode() Recursion test with JsonSerializable, __debugInfo and var_export +--FILE-- + var_export($this, true) ]; + } + + public function jsonSerialize(): mixed + { + var_dump($this); + return $this; + } +} + +var_dump(json_encode(new SerializingTest())); +echo "---------\n"; +var_dump(new SerializingTest()); + +?> +--EXPECT-- +object(SerializingTest)#1 (1) { + ["result"]=> + string(52) "\SerializingTest::__set_state(array( + 'a' => 1, +))" +} +string(7) "{"a":1}" +--------- +object(SerializingTest)#1 (1) { + ["result"]=> + string(52) "\SerializingTest::__set_state(array( + 'a' => 1, +))" +} diff --git a/ext/json/tests/json_encode_recursion_05.phpt b/ext/json/tests/json_encode_recursion_05.phpt new file mode 100644 index 0000000000000..3cbc2000d32e9 --- /dev/null +++ b/ext/json/tests/json_encode_recursion_05.phpt @@ -0,0 +1,37 @@ +--TEST-- +json_encode() Recursion test with JsonSerializable, __debugInfo and print_r +--FILE-- + $this->a ]; + } + + public function jsonSerialize(): mixed + { + print_r($this); + return $this; + } +} + +var_dump(json_encode(new SerializingTest())); +echo "---------\n"; +var_dump(new SerializingTest()); + +?> +--EXPECT-- +SerializingTest Object +( + [result] => 1 +) +string(7) "{"a":1}" +--------- +object(SerializingTest)#1 (1) { + ["result"]=> + int(1) +} diff --git a/ext/json/tests/json_encode_recursion_06.phpt b/ext/json/tests/json_encode_recursion_06.phpt new file mode 100644 index 0000000000000..d8e6fea226e6f --- /dev/null +++ b/ext/json/tests/json_encode_recursion_06.phpt @@ -0,0 +1,41 @@ +--TEST-- +json_encode() Recursion test with JsonSerializable and serialize +--FILE-- + $this->a ]; + } + + public function jsonSerialize(): mixed + { + return [ 'serialize' => serialize($this) ]; + } +} + +class SerializeFirstTest implements JsonSerializable +{ + public $a = 1; + + public function __serialize() + { + return [ 'result' => json_encode($this) ]; + } + + public function jsonSerialize(): mixed + { + return [ 'json' => serialize($this) ]; + } +} + +var_dump(json_encode(new JsonEncodeFirstTest())); +var_dump(serialize(new SerializeFirstTest())); +?> +--EXPECT-- +string(68) "{"serialize":"O:19:\"JsonEncodeFirstTest\":1:{s:6:\"result\";i:1;}"}" +string(113) "O:18:"SerializeFirstTest":1:{s:6:"result";s:62:"{"json":"O:18:\"SerializeFirstTest\":1:{s:6:\"result\";b:0;}"}";}" diff --git a/ext/standard/var.c b/ext/standard/var.c index 7b3fcbc6ae3c2..4f04ff6c0deb3 100644 --- a/ext/standard/var.c +++ b/ext/standard/var.c @@ -153,12 +153,13 @@ PHPAPI void php_var_dump(zval *struc, int level) /* {{{ */ php_printf("%senum(%s::%s)\n", COMMON, ZSTR_VAL(ce->name), Z_STRVAL_P(case_name_zval)); return; } - - if (Z_IS_RECURSIVE_P(struc)) { + zend_object *zobj = Z_OBJ_P(struc); + uint32_t *guard = zend_get_recursion_guard(zobj); + if (ZEND_GUARD_OR_GC_IS_RECURSIVE(guard, DEBUG, zobj)) { PUTS("*RECURSION*\n"); return; } - Z_PROTECT_RECURSION_P(struc); + ZEND_GUARD_OR_GC_PROTECT_RECURSION(guard, DEBUG, zobj); myht = zend_get_properties_for(struc, ZEND_PROP_PURPOSE_DEBUG); class_name = Z_OBJ_HANDLER_P(struc, get_class_name)(Z_OBJ_P(struc)); @@ -190,7 +191,7 @@ PHPAPI void php_var_dump(zval *struc, int level) /* {{{ */ php_printf("%*c", level-1, ' '); } PUTS("}\n"); - Z_UNPROTECT_RECURSION_P(struc); + ZEND_GUARD_OR_GC_UNPROTECT_RECURSION(guard, DEBUG, zobj); break; } case IS_RESOURCE: { @@ -342,16 +343,18 @@ PHPAPI void php_debug_zval_dump(zval *struc, int level) /* {{{ */ } PUTS("}\n"); break; - case IS_OBJECT: + case IS_OBJECT: { /* Check if this is already recursing on the object before calling zend_get_properties_for, * to allow infinite recursion detection to work even if classes return temporary arrays, * and to avoid the need to update the properties table in place to reflect the state * if the result won't be used. (https://github.com/php/php-src/issues/8044) */ - if (Z_IS_RECURSIVE_P(struc)) { + zend_object *zobj = Z_OBJ_P(struc); + uint32_t *guard = zend_get_recursion_guard(zobj); + if (ZEND_GUARD_OR_GC_IS_RECURSIVE(guard, DEBUG, zobj)) { PUTS("*RECURSION*\n"); return; } - Z_PROTECT_RECURSION_P(struc); + ZEND_GUARD_OR_GC_PROTECT_RECURSION(guard, DEBUG, zobj); myht = zend_get_properties_for(struc, ZEND_PROP_PURPOSE_DEBUG); class_name = Z_OBJ_HANDLER_P(struc, get_class_name)(Z_OBJ_P(struc)); @@ -378,8 +381,9 @@ PHPAPI void php_debug_zval_dump(zval *struc, int level) /* {{{ */ php_printf("%*c", level - 1, ' '); } PUTS("}\n"); - Z_UNPROTECT_RECURSION_P(struc); + ZEND_GUARD_OR_GC_UNPROTECT_RECURSION(guard, DEBUG, zobj); break; + } case IS_RESOURCE: { const char *type_name = zend_rsrc_list_get_rsrc_type(Z_RES_P(struc)); php_printf("resource(" ZEND_LONG_FMT ") of type (%s) refcount(%u)\n", Z_RES_P(struc)->handle, type_name ? type_name : "Unknown", Z_REFCOUNT_P(struc)); @@ -553,17 +557,19 @@ PHPAPI void php_var_export_ex(zval *struc, int level, smart_str *buf) /* {{{ */ break; - case IS_OBJECT: + case IS_OBJECT: { /* Check if this is already recursing on the object before calling zend_get_properties_for, * to allow infinite recursion detection to work even if classes return temporary arrays, * and to avoid the need to update the properties table in place to reflect the state * if the result won't be used. (https://github.com/php/php-src/issues/8044) */ - if (Z_IS_RECURSIVE_P(struc)) { + zend_object *zobj = Z_OBJ_P(struc); + uint32_t *guard = zend_get_recursion_guard(zobj); + if (ZEND_GUARD_OR_GC_IS_RECURSIVE(guard, EXPORT, zobj)) { smart_str_appendl(buf, "NULL", 4); zend_error(E_WARNING, "var_export does not handle circular references"); return; } - Z_PROTECT_RECURSION_P(struc); + ZEND_GUARD_OR_GC_PROTECT_RECURSION(guard, EXPORT, zobj); myht = zend_get_properties_for(struc, ZEND_PROP_PURPOSE_VAR_EXPORT); if (level > 1) { smart_str_appendc(buf, '\n'); @@ -597,7 +603,7 @@ PHPAPI void php_var_export_ex(zval *struc, int level, smart_str *buf) /* {{{ */ } zend_release_properties(myht); } - Z_UNPROTECT_RECURSION_P(struc); + ZEND_GUARD_OR_GC_UNPROTECT_RECURSION(guard, EXPORT, zobj); if (level > 1 && !is_enum) { buffer_append_spaces(buf, level - 1); } @@ -608,6 +614,7 @@ PHPAPI void php_var_export_ex(zval *struc, int level, smart_str *buf) /* {{{ */ } break; + } case IS_REFERENCE: struc = Z_REFVAL_P(struc); goto again;