mirror of
https://github.com/php/php-src.git
synced 2025-08-15 13:38:49 +02:00
Introduce Zend guard recursion protection
This PR introduces a new way of recursion protection in JSON, var_dump and friends. It fixes issue in master for __debugInfo and also improves perf for jsonSerializable in some cases. More info can be found in GH-10020. Closes GH-11812
This commit is contained in:
parent
fd462b1e0f
commit
53aa53f42f
16 changed files with 322 additions and 37 deletions
2
NEWS
2
NEWS
|
@ -4,6 +4,8 @@ PHP NEWS
|
||||||
|
|
||||||
- Core:
|
- Core:
|
||||||
. Fixed bug GH-11937 (Constant ASTs containing objects). (ilutov)
|
. Fixed bug GH-11937 (Constant ASTs containing objects). (ilutov)
|
||||||
|
. Introduced Zend guard recursion protection to fix __debugInfo issue.
|
||||||
|
(Jakub Zelenka)
|
||||||
|
|
||||||
17 Aug 2023, PHP 8.3.0beta3
|
17 Aug 2023, PHP 8.3.0beta3
|
||||||
|
|
||||||
|
|
|
@ -546,6 +546,7 @@ static void zend_print_zval_r_to_buf(smart_str *buf, zval *expr, int indent) /*
|
||||||
HashTable *properties;
|
HashTable *properties;
|
||||||
|
|
||||||
zend_object *zobj = Z_OBJ_P(expr);
|
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);
|
zend_string *class_name = Z_OBJ_HANDLER_P(expr, get_class_name)(zobj);
|
||||||
smart_str_appends(buf, ZSTR_VAL(class_name));
|
smart_str_appends(buf, ZSTR_VAL(class_name));
|
||||||
zend_string_release_ex(class_name, 0);
|
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');
|
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*");
|
smart_str_appends(buf, " *RECURSION*");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -571,9 +572,9 @@ static void zend_print_zval_r_to_buf(smart_str *buf, zval *expr, int indent) /*
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
GC_PROTECT_RECURSION(Z_OBJ_P(expr));
|
ZEND_GUARD_OR_GC_PROTECT_RECURSION(guard, DEBUG, zobj);
|
||||||
print_hash(buf, properties, indent, 1);
|
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);
|
zend_release_properties(properties);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -2746,6 +2746,7 @@ ZEND_API void zend_add_magic_method(zend_class_entry *ce, zend_function *fptr, z
|
||||||
ce->__tostring = fptr;
|
ce->__tostring = fptr;
|
||||||
} else if (zend_string_equals_literal(lcname, ZEND_DEBUGINFO_FUNC_NAME)) {
|
} else if (zend_string_equals_literal(lcname, ZEND_DEBUGINFO_FUNC_NAME)) {
|
||||||
ce->__debugInfo = fptr;
|
ce->__debugInfo = fptr;
|
||||||
|
ce->ce_flags |= ZEND_ACC_USE_GUARDS;
|
||||||
} else if (zend_string_equals_literal(lcname, "__serialize")) {
|
} else if (zend_string_equals_literal(lcname, "__serialize")) {
|
||||||
ce->__serialize = fptr;
|
ce->__serialize = fptr;
|
||||||
} else if (zend_string_equals_literal(lcname, "__unserialize")) {
|
} else if (zend_string_equals_literal(lcname, "__unserialize")) {
|
||||||
|
|
|
@ -36,10 +36,10 @@
|
||||||
#define ZEND_WRONG_PROPERTY_OFFSET 0
|
#define ZEND_WRONG_PROPERTY_OFFSET 0
|
||||||
|
|
||||||
/* guard flags */
|
/* guard flags */
|
||||||
#define IN_GET (1<<0)
|
#define IN_GET ZEND_GUARD_PROPERTY_GET
|
||||||
#define IN_SET (1<<1)
|
#define IN_SET ZEND_GUARD_PROPERTY_SET
|
||||||
#define IN_UNSET (1<<2)
|
#define IN_UNSET ZEND_GUARD_PROPERTY_UNSET
|
||||||
#define IN_ISSET (1<<3)
|
#define IN_ISSET ZEND_GUARD_PROPERTY_ISSET
|
||||||
|
|
||||||
/*
|
/*
|
||||||
__X accessors explanation:
|
__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) /* {{{ */
|
ZEND_API uint32_t *zend_get_property_guard(zend_object *zobj, zend_string *member) /* {{{ */
|
||||||
{
|
{
|
||||||
HashTable *guards;
|
HashTable *guards;
|
||||||
zval *zv;
|
zval *zv;
|
||||||
uint32_t *ptr;
|
uint32_t *ptr;
|
||||||
|
|
||||||
|
|
||||||
ZEND_ASSERT(zobj->ce->ce_flags & ZEND_ACC_USE_GUARDS);
|
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)) {
|
if (EXPECTED(Z_TYPE_P(zv) == IS_STRING)) {
|
||||||
zend_string *str = Z_STR_P(zv);
|
zend_string *str = Z_STR_P(zv);
|
||||||
if (EXPECTED(str == member) ||
|
if (EXPECTED(str == member) ||
|
||||||
/* str and member don't necessarily have a pre-calculated hash value here */
|
/* str and member don't necessarily have a pre-calculated hash value here */
|
||||||
EXPECTED(zend_string_equal_content(str, member))) {
|
EXPECTED(zend_string_equal_content(str, member))) {
|
||||||
return &Z_PROPERTY_GUARD_P(zv);
|
return &Z_GUARD_P(zv);
|
||||||
} else if (EXPECTED(Z_PROPERTY_GUARD_P(zv) == 0)) {
|
} else if (EXPECTED(Z_GUARD_P(zv) == 0)) {
|
||||||
zval_ptr_dtor_str(zv);
|
zval_ptr_dtor_str(zv);
|
||||||
ZVAL_STR_COPY(zv, member);
|
ZVAL_STR_COPY(zv, member);
|
||||||
return &Z_PROPERTY_GUARD_P(zv);
|
return &Z_GUARD_P(zv);
|
||||||
} else {
|
} else {
|
||||||
ALLOC_HASHTABLE(guards);
|
ALLOC_HASHTABLE(guards);
|
||||||
zend_hash_init(guards, 8, NULL, zend_property_guard_dtor, 0);
|
zend_hash_init(guards, 8, NULL, zend_property_guard_dtor, 0);
|
||||||
/* mark pointer as "special" using low bit */
|
/* mark pointer as "special" using low bit */
|
||||||
zend_hash_add_new_ptr(guards, str,
|
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_ptr_dtor_str(zv);
|
||||||
ZVAL_ARR(zv, guards);
|
ZVAL_ARR(zv, guards);
|
||||||
}
|
}
|
||||||
|
@ -579,8 +585,8 @@ ZEND_API uint32_t *zend_get_property_guard(zend_object *zobj, zend_string *membe
|
||||||
} else {
|
} else {
|
||||||
ZEND_ASSERT(Z_TYPE_P(zv) == IS_UNDEF);
|
ZEND_ASSERT(Z_TYPE_P(zv) == IS_UNDEF);
|
||||||
ZVAL_STR_COPY(zv, member);
|
ZVAL_STR_COPY(zv, member);
|
||||||
Z_PROPERTY_GUARD_P(zv) = 0;
|
Z_GUARD_P(zv) &= ~ZEND_GUARD_PROPERTY_MASK;
|
||||||
return &Z_PROPERTY_GUARD_P(zv);
|
return &Z_GUARD_P(zv);
|
||||||
}
|
}
|
||||||
/* we have to allocate uint32_t separately because ht->arData may be reallocated */
|
/* we have to allocate uint32_t separately because ht->arData may be reallocated */
|
||||||
ptr = (uint32_t*)emalloc(sizeof(uint32_t));
|
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) /* {{{ */
|
ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int type, void **cache_slot, zval *rv) /* {{{ */
|
||||||
{
|
{
|
||||||
zval *retval;
|
zval *retval;
|
||||||
|
|
|
@ -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_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
|
/* Default behavior for get_properties_for. For use as a fallback in custom
|
||||||
* get_properties_for implementations. */
|
* get_properties_for implementations. */
|
||||||
ZEND_API HashTable *zend_std_get_properties_for(zend_object *obj, zend_prop_purpose purpose);
|
ZEND_API HashTable *zend_std_get_properties_for(zend_object *obj, zend_prop_purpose purpose);
|
||||||
|
|
|
@ -35,7 +35,9 @@ static zend_always_inline void _zend_object_std_init(zend_object *object, zend_c
|
||||||
object->properties = NULL;
|
object->properties = NULL;
|
||||||
zend_objects_store_put(object);
|
zend_objects_store_put(object);
|
||||||
if (UNEXPECTED(ce->ce_flags & ZEND_ACC_USE_GUARDS)) {
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -345,7 +345,7 @@ struct _zval_struct {
|
||||||
uint32_t num_args; /* arguments number for EX(This) */
|
uint32_t num_args; /* arguments number for EX(This) */
|
||||||
uint32_t fe_pos; /* foreach position */
|
uint32_t fe_pos; /* foreach position */
|
||||||
uint32_t fe_iter_idx; /* foreach iterator index */
|
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 constant_flags; /* constant flags */
|
||||||
uint32_t extra; /* not further specified */
|
uint32_t extra; /* not further specified */
|
||||||
} u2;
|
} u2;
|
||||||
|
@ -619,6 +619,22 @@ struct _zend_ast_ref {
|
||||||
#define _IS_BOOL 18
|
#define _IS_BOOL 18
|
||||||
#define _IS_NUMBER 19
|
#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) {
|
static zend_always_inline uint8_t zval_get_type(const zval* pz) {
|
||||||
return pz->u1.v.type;
|
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(zval) (zval).u2.fe_iter_idx
|
||||||
#define Z_FE_ITER_P(zval_p) Z_FE_ITER(*(zval_p))
|
#define Z_FE_ITER_P(zval_p) Z_FE_ITER(*(zval_p))
|
||||||
|
|
||||||
#define Z_PROPERTY_GUARD(zval) (zval).u2.property_guard
|
#define Z_GUARD(zval) (zval).u2.guard
|
||||||
#define Z_PROPERTY_GUARD_P(zval_p) Z_PROPERTY_GUARD(*(zval_p))
|
#define Z_GUARD_P(zval_p) Z_GUARD(*(zval_p))
|
||||||
|
|
||||||
#define Z_CONSTANT_FLAGS(zval) (zval).u2.constant_flags
|
#define Z_CONSTANT_FLAGS(zval) (zval).u2.constant_flags
|
||||||
#define Z_CONSTANT_FLAGS_P(zval_p) Z_CONSTANT_FLAGS(*(zval_p))
|
#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_PROTECT_RECURSION_P(zv) Z_PROTECT_RECURSION(*(zv))
|
||||||
#define Z_UNPROTECT_RECURSION_P(zv) Z_UNPROTECT_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 */
|
/* All data types < IS_STRING have their constructor/destructors skipped */
|
||||||
#define Z_CONSTANT(zval) (Z_TYPE(zval) == IS_CONSTANT_AST)
|
#define Z_CONSTANT(zval) (Z_TYPE(zval) == IS_CONSTANT_AST)
|
||||||
#define Z_CONSTANT_P(zval_p) Z_CONSTANT(*(zval_p))
|
#define Z_CONSTANT_P(zval_p) Z_CONSTANT(*(zval_p))
|
||||||
|
|
|
@ -37,10 +37,17 @@ PHP_JSON_API zend_class_entry *php_json_exception_ce;
|
||||||
|
|
||||||
PHP_JSON_API ZEND_DECLARE_MODULE_GLOBALS(json)
|
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 */
|
/* {{{ MINIT */
|
||||||
static PHP_MINIT_FUNCTION(json)
|
static PHP_MINIT_FUNCTION(json)
|
||||||
{
|
{
|
||||||
php_json_serializable_ce = register_class_JsonSerializable();
|
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);
|
php_json_exception_ce = register_class_JsonException(zend_ce_exception);
|
||||||
|
|
||||||
|
|
|
@ -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) /* {{{ */
|
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);
|
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;
|
zval retval, fname;
|
||||||
zend_result return_code;
|
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;
|
encoder->error_code = PHP_JSON_ERROR_RECURSION;
|
||||||
if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
|
if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
|
||||||
smart_str_appendl(buf, "null", 4);
|
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;
|
return FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
PHP_JSON_HASH_PROTECT_RECURSION(myht);
|
ZEND_GUARD_PROTECT_RECURSION(guard, JSON);
|
||||||
|
|
||||||
ZVAL_STRING(&fname, "jsonSerialize");
|
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) {
|
if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
|
||||||
smart_str_appendl(buf, "null", 4);
|
smart_str_appendl(buf, "null", 4);
|
||||||
}
|
}
|
||||||
PHP_JSON_HASH_UNPROTECT_RECURSION(myht);
|
ZEND_GUARD_UNPROTECT_RECURSION(guard, JSON);
|
||||||
return FAILURE;
|
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) {
|
if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
|
||||||
smart_str_appendl(buf, "null", 4);
|
smart_str_appendl(buf, "null", 4);
|
||||||
}
|
}
|
||||||
PHP_JSON_HASH_UNPROTECT_RECURSION(myht);
|
ZEND_GUARD_UNPROTECT_RECURSION(guard, JSON);
|
||||||
return FAILURE;
|
return FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((Z_TYPE(retval) == IS_OBJECT) &&
|
if ((Z_TYPE(retval) == IS_OBJECT) &&
|
||||||
(Z_OBJ(retval) == Z_OBJ_P(val))) {
|
(Z_OBJ(retval) == Z_OBJ_P(val))) {
|
||||||
/* Handle the case where jsonSerialize does: return $this; by going straight to encode array */
|
/* 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);
|
return_code = php_json_encode_array(buf, &retval, options, encoder);
|
||||||
} else {
|
} else {
|
||||||
/* All other types, encode as normal */
|
/* All other types, encode as normal */
|
||||||
return_code = php_json_encode_zval(buf, &retval, options, encoder);
|
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);
|
zval_ptr_dtor(&retval);
|
||||||
|
|
26
ext/json/tests/json_encode_recursion_01.phpt
Normal file
26
ext/json/tests/json_encode_recursion_01.phpt
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
--TEST--
|
||||||
|
json_encode() Recursion test with just JsonSerializable
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class SerializingTest implements JsonSerializable
|
||||||
|
{
|
||||||
|
public $a = 1;
|
||||||
|
|
||||||
|
private $b = 'hide';
|
||||||
|
|
||||||
|
protected $c = 'protect';
|
||||||
|
|
||||||
|
public function jsonSerialize(): mixed
|
||||||
|
{
|
||||||
|
$result = json_encode($this);
|
||||||
|
var_dump($result);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var_dump(json_encode(new SerializingTest()));
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
bool(false)
|
||||||
|
string(7) "{"a":1}"
|
25
ext/json/tests/json_encode_recursion_02.phpt
Normal file
25
ext/json/tests/json_encode_recursion_02.phpt
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
--TEST--
|
||||||
|
json_encode() Recursion test with JsonSerializable and var_dump simple
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class SerializingTest implements JsonSerializable
|
||||||
|
{
|
||||||
|
public $a = 1;
|
||||||
|
|
||||||
|
public function jsonSerialize(): mixed
|
||||||
|
{
|
||||||
|
var_dump($this);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var_dump(json_encode(new SerializingTest()));
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
object(SerializingTest)#1 (1) {
|
||||||
|
["a"]=>
|
||||||
|
int(1)
|
||||||
|
}
|
||||||
|
string(7) "{"a":1}"
|
38
ext/json/tests/json_encode_recursion_03.phpt
Normal file
38
ext/json/tests/json_encode_recursion_03.phpt
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
--TEST--
|
||||||
|
json_encode() Recursion test with JsonSerializable and __debugInfo
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class SerializingTest implements JsonSerializable
|
||||||
|
{
|
||||||
|
public $a = 1;
|
||||||
|
|
||||||
|
public function __debugInfo()
|
||||||
|
{
|
||||||
|
return [ 'result' => 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}"
|
||||||
|
}
|
41
ext/json/tests/json_encode_recursion_04.phpt
Normal file
41
ext/json/tests/json_encode_recursion_04.phpt
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
--TEST--
|
||||||
|
json_encode() Recursion test with JsonSerializable, __debugInfo and var_export
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class SerializingTest implements JsonSerializable
|
||||||
|
{
|
||||||
|
public $a = 1;
|
||||||
|
|
||||||
|
public function __debugInfo()
|
||||||
|
{
|
||||||
|
return [ 'result' => 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,
|
||||||
|
))"
|
||||||
|
}
|
37
ext/json/tests/json_encode_recursion_05.phpt
Normal file
37
ext/json/tests/json_encode_recursion_05.phpt
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
--TEST--
|
||||||
|
json_encode() Recursion test with JsonSerializable, __debugInfo and print_r
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class SerializingTest implements JsonSerializable
|
||||||
|
{
|
||||||
|
public $a = 1;
|
||||||
|
|
||||||
|
public function __debugInfo()
|
||||||
|
{
|
||||||
|
return [ 'result' => $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)
|
||||||
|
}
|
41
ext/json/tests/json_encode_recursion_06.phpt
Normal file
41
ext/json/tests/json_encode_recursion_06.phpt
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
--TEST--
|
||||||
|
json_encode() Recursion test with JsonSerializable and serialize
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class JsonEncodeFirstTest implements JsonSerializable
|
||||||
|
{
|
||||||
|
public $a = 1;
|
||||||
|
|
||||||
|
public function __serialize()
|
||||||
|
{
|
||||||
|
return [ 'result' => $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;}"}";}"
|
|
@ -153,12 +153,13 @@ again:
|
||||||
php_printf("%senum(%s::%s)\n", COMMON, ZSTR_VAL(ce->name), Z_STRVAL_P(case_name_zval));
|
php_printf("%senum(%s::%s)\n", COMMON, ZSTR_VAL(ce->name), Z_STRVAL_P(case_name_zval));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
zend_object *zobj = Z_OBJ_P(struc);
|
||||||
if (Z_IS_RECURSIVE_P(struc)) {
|
uint32_t *guard = zend_get_recursion_guard(zobj);
|
||||||
|
if (ZEND_GUARD_OR_GC_IS_RECURSIVE(guard, DEBUG, zobj)) {
|
||||||
PUTS("*RECURSION*\n");
|
PUTS("*RECURSION*\n");
|
||||||
return;
|
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);
|
myht = zend_get_properties_for(struc, ZEND_PROP_PURPOSE_DEBUG);
|
||||||
class_name = Z_OBJ_HANDLER_P(struc, get_class_name)(Z_OBJ_P(struc));
|
class_name = Z_OBJ_HANDLER_P(struc, get_class_name)(Z_OBJ_P(struc));
|
||||||
|
@ -190,7 +191,7 @@ again:
|
||||||
php_printf("%*c", level-1, ' ');
|
php_printf("%*c", level-1, ' ');
|
||||||
}
|
}
|
||||||
PUTS("}\n");
|
PUTS("}\n");
|
||||||
Z_UNPROTECT_RECURSION_P(struc);
|
ZEND_GUARD_OR_GC_UNPROTECT_RECURSION(guard, DEBUG, zobj);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case IS_RESOURCE: {
|
case IS_RESOURCE: {
|
||||||
|
@ -342,16 +343,18 @@ PHPAPI void php_debug_zval_dump(zval *struc, int level) /* {{{ */
|
||||||
}
|
}
|
||||||
PUTS("}\n");
|
PUTS("}\n");
|
||||||
break;
|
break;
|
||||||
case IS_OBJECT:
|
case IS_OBJECT: {
|
||||||
/* Check if this is already recursing on the object before calling zend_get_properties_for,
|
/* 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,
|
* 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
|
* 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 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");
|
PUTS("*RECURSION*\n");
|
||||||
return;
|
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);
|
myht = zend_get_properties_for(struc, ZEND_PROP_PURPOSE_DEBUG);
|
||||||
class_name = Z_OBJ_HANDLER_P(struc, get_class_name)(Z_OBJ_P(struc));
|
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, ' ');
|
php_printf("%*c", level - 1, ' ');
|
||||||
}
|
}
|
||||||
PUTS("}\n");
|
PUTS("}\n");
|
||||||
Z_UNPROTECT_RECURSION_P(struc);
|
ZEND_GUARD_OR_GC_UNPROTECT_RECURSION(guard, DEBUG, zobj);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case IS_RESOURCE: {
|
case IS_RESOURCE: {
|
||||||
const char *type_name = zend_rsrc_list_get_rsrc_type(Z_RES_P(struc));
|
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));
|
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 @@ again:
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case IS_OBJECT:
|
case IS_OBJECT: {
|
||||||
/* Check if this is already recursing on the object before calling zend_get_properties_for,
|
/* 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,
|
* 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
|
* 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 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);
|
smart_str_appendl(buf, "NULL", 4);
|
||||||
zend_error(E_WARNING, "var_export does not handle circular references");
|
zend_error(E_WARNING, "var_export does not handle circular references");
|
||||||
return;
|
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);
|
myht = zend_get_properties_for(struc, ZEND_PROP_PURPOSE_VAR_EXPORT);
|
||||||
if (level > 1) {
|
if (level > 1) {
|
||||||
smart_str_appendc(buf, '\n');
|
smart_str_appendc(buf, '\n');
|
||||||
|
@ -597,7 +603,7 @@ again:
|
||||||
}
|
}
|
||||||
zend_release_properties(myht);
|
zend_release_properties(myht);
|
||||||
}
|
}
|
||||||
Z_UNPROTECT_RECURSION_P(struc);
|
ZEND_GUARD_OR_GC_UNPROTECT_RECURSION(guard, EXPORT, zobj);
|
||||||
if (level > 1 && !is_enum) {
|
if (level > 1 && !is_enum) {
|
||||||
buffer_append_spaces(buf, level - 1);
|
buffer_append_spaces(buf, level - 1);
|
||||||
}
|
}
|
||||||
|
@ -608,6 +614,7 @@ again:
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case IS_REFERENCE:
|
case IS_REFERENCE:
|
||||||
struc = Z_REFVAL_P(struc);
|
struc = Z_REFVAL_P(struc);
|
||||||
goto again;
|
goto again;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue