diff --git a/ext/json/json.c b/ext/json/json.c index 2d7846a5dbed9..7db827e50c6a9 100644 --- a/ext/json/json.c +++ b/ext/json/json.c @@ -152,6 +152,7 @@ PHP_JSON_API int php_json_encode_ex(smart_str *buf, zval *val, int options, zend return_code = php_json_encode_zval(buf, val, options, &encoder); JSON_G(error_code) = encoder.error_code; + php_json_encode_destroy(&encoder); return return_code; } /* }}} */ @@ -235,6 +236,7 @@ PHP_FUNCTION(json_encode) php_json_encode_init(&encoder); encoder.max_depth = (int)depth; php_json_encode_zval(&buf, parameter, (int)options, &encoder); + php_json_encode_destroy(&encoder); if (!(options & PHP_JSON_THROW_ON_ERROR) || (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) { JSON_G(error_code) = encoder.error_code; diff --git a/ext/json/json_encoder.c b/ext/json/json_encoder.c index b0f703041b068..fc11b4b07742f 100644 --- a/ext/json/json_encoder.c +++ b/ext/json/json_encoder.c @@ -93,19 +93,26 @@ static inline void php_json_encode_double(smart_str *buf, double d, int options) } /* }}} */ -#define PHP_JSON_HASH_PROTECT_RECURSION(_tmp_ht) \ - do { \ - if (_tmp_ht) { \ - GC_TRY_PROTECT_RECURSION(_tmp_ht); \ - } \ - } while (0) - -#define PHP_JSON_HASH_UNPROTECT_RECURSION(_tmp_ht) \ - do { \ - if (_tmp_ht) { \ - GC_TRY_UNPROTECT_RECURSION(_tmp_ht); \ - } \ - } while (0) +static inline zend_result php_json_protect_recursion(php_json_encoder *encoder, zend_refcounted *rc) +{ + if (GC_FLAGS(rc) & GC_IMMUTABLE) { + return SUCCESS; + } + if (zend_hash_index_add_empty_element(&encoder->recursive, (uintptr_t) rc)) { + GC_ADDREF(rc); + return SUCCESS; + } + return FAILURE; +} + +static inline void php_json_unprotect_recursion(php_json_encoder *encoder, zend_refcounted *rc) +{ + if (GC_FLAGS(rc) & GC_IMMUTABLE) { + return; + } + GC_DELREF(rc); + zend_hash_index_del(&encoder->recursive, (uintptr_t) rc); +} static int php_json_encode_array(smart_str *buf, zval *val, int options, php_json_encoder *encoder) /* {{{ */ { @@ -126,14 +133,12 @@ static int php_json_encode_array(smart_str *buf, zval *val, int options, php_jso zval *prop; int i; - if (GC_IS_RECURSIVE(obj)) { + if (php_json_protect_recursion(encoder, (zend_refcounted *) obj) == FAILURE) { encoder->error_code = PHP_JSON_ERROR_RECURSION; smart_str_appendl(buf, "null", 4); return FAILURE; } - PHP_JSON_HASH_PROTECT_RECURSION(obj); - smart_str_appendc(buf, '{'); ++encoder->depth; @@ -174,12 +179,12 @@ static int php_json_encode_array(smart_str *buf, zval *val, int options, php_jso if (php_json_encode_zval(buf, prop, options, encoder) == FAILURE && !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) { - PHP_JSON_HASH_UNPROTECT_RECURSION(obj); + php_json_unprotect_recursion(encoder, (zend_refcounted *) obj); return FAILURE; } } - PHP_JSON_HASH_UNPROTECT_RECURSION(obj); + php_json_unprotect_recursion(encoder, (zend_refcounted *) obj); if (encoder->depth > encoder->max_depth) { encoder->error_code = PHP_JSON_ERROR_DEPTH; if (!(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) { @@ -199,15 +204,13 @@ static int php_json_encode_array(smart_str *buf, zval *val, int options, php_jso r = PHP_JSON_OUTPUT_OBJECT; } - if (myht && GC_IS_RECURSIVE(myht)) { + if (myht && php_json_protect_recursion(encoder, (zend_refcounted *) myht)) { encoder->error_code = PHP_JSON_ERROR_RECURSION; smart_str_appendl(buf, "null", 4); zend_release_properties(prop_ht); return FAILURE; } - PHP_JSON_HASH_PROTECT_RECURSION(myht); - if (r == PHP_JSON_OUTPUT_ARRAY) { smart_str_appendc(buf, '['); } else { @@ -277,14 +280,18 @@ static int php_json_encode_array(smart_str *buf, zval *val, int options, php_jso if (php_json_encode_zval(buf, data, options, encoder) == FAILURE && !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) { - PHP_JSON_HASH_UNPROTECT_RECURSION(myht); + if (myht) { + php_json_unprotect_recursion(encoder, (zend_refcounted *) myht); + } zend_release_properties(prop_ht); return FAILURE; } } ZEND_HASH_FOREACH_END(); } - PHP_JSON_HASH_UNPROTECT_RECURSION(myht); + if (myht) { + php_json_unprotect_recursion(encoder, (zend_refcounted *) myht); + } if (encoder->depth > encoder->max_depth) { encoder->error_code = PHP_JSON_ERROR_DEPTH; @@ -529,11 +536,10 @@ static int php_json_escape_string( static int 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); zval retval, fname; int return_code; - if (myht && GC_IS_RECURSIVE(myht)) { + if (php_json_protect_recursion(encoder, Z_COUNTED_P(val)) == FAILURE) { encoder->error_code = PHP_JSON_ERROR_RECURSION; if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) { smart_str_appendl(buf, "null", 4); @@ -541,8 +547,6 @@ static int php_json_encode_serializable_object(smart_str *buf, zval *val, int op return FAILURE; } - PHP_JSON_HASH_PROTECT_RECURSION(myht); - ZVAL_STRING(&fname, "jsonSerialize"); if (FAILURE == call_user_function(NULL, val, &fname, &retval, 0, NULL) || Z_TYPE(retval) == IS_UNDEF) { @@ -554,7 +558,7 @@ static int php_json_encode_serializable_object(smart_str *buf, zval *val, int op if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) { smart_str_appendl(buf, "null", 4); } - PHP_JSON_HASH_UNPROTECT_RECURSION(myht); + php_json_unprotect_recursion(encoder, Z_COUNTED_P(val)); return FAILURE; } @@ -566,19 +570,19 @@ static int php_json_encode_serializable_object(smart_str *buf, zval *val, int op if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) { smart_str_appendl(buf, "null", 4); } - PHP_JSON_HASH_UNPROTECT_RECURSION(myht); + php_json_unprotect_recursion(encoder, Z_COUNTED_P(val)); 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); + php_json_unprotect_recursion(encoder, Z_COUNTED_P(val)); 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); + php_json_unprotect_recursion(encoder, Z_COUNTED_P(val)); } zval_ptr_dtor(&retval); diff --git a/ext/json/php_json_encoder.h b/ext/json/php_json_encoder.h index 51d2d6b59ab49..56d607d899fe6 100644 --- a/ext/json/php_json_encoder.h +++ b/ext/json/php_json_encoder.h @@ -26,11 +26,20 @@ struct _php_json_encoder { int depth; int max_depth; php_json_error_code error_code; + HashTable recursive; }; static inline void php_json_encode_init(php_json_encoder *encoder) { - memset(encoder, 0, sizeof(php_json_encoder)); + encoder->depth = 0; + encoder->max_depth = 0; + encoder->error_code = 0; + zend_hash_init(&encoder->recursive, 0, NULL, NULL, 0); +} + +static inline void php_json_encode_destroy(php_json_encoder *encoder) +{ + zend_hash_destroy(&encoder->recursive); } int php_json_encode_zval(smart_str *buf, zval *val, int options, php_json_encoder *encoder);