mirror of
https://github.com/php/php-src.git
synced 2025-08-15 21:48:51 +02:00
Introduce json encoder to fix globals related issues
It fixes bugs #66025 and #73254 by replacing globals with a passed structure holding depth and error code. In addition it fixes #72069 in a more generic way.
This commit is contained in:
parent
be6bf71274
commit
c34de0b61c
6 changed files with 120 additions and 47 deletions
4
NEWS
4
NEWS
|
@ -10,6 +10,10 @@ PHP NEWS
|
|||
. Fixed bug #73392 (A use-after-free in zend allocator management).
|
||||
(Laruence)
|
||||
|
||||
- JSON:
|
||||
. Introduced encoder struct instead of global which fixes bugs #66025 and
|
||||
#73254 related to pretty print indentation. (Jakub Zelenka)
|
||||
|
||||
27 Oct 2016, PHP 7.1.0RC5
|
||||
|
||||
- Core:
|
||||
|
|
|
@ -186,7 +186,17 @@ static PHP_MINFO_FUNCTION(json)
|
|||
|
||||
PHP_JSON_API int php_json_encode(smart_str *buf, zval *val, int options) /* {{{ */
|
||||
{
|
||||
return php_json_encode_zval(buf, val, options);
|
||||
php_json_encoder encoder;
|
||||
int return_code;
|
||||
|
||||
php_json_encode_init(&encoder);
|
||||
encoder.max_depth = JSON_G(encode_max_depth);
|
||||
encoder.error_code = PHP_JSON_ERROR_NONE;
|
||||
|
||||
return_code = php_json_encode_zval(buf, val, options, &encoder);
|
||||
JSON_G(error_code) = encoder.error_code;
|
||||
|
||||
return return_code;
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
|
@ -211,6 +221,7 @@ PHP_JSON_API int php_json_decode_ex(zval *return_value, char *str, size_t str_le
|
|||
static PHP_FUNCTION(json_encode)
|
||||
{
|
||||
zval *parameter;
|
||||
php_json_encoder encoder;
|
||||
smart_str buf = {0};
|
||||
zend_long options = 0;
|
||||
zend_long depth = PHP_JSON_PARSER_DEFAULT_DEPTH;
|
||||
|
@ -219,23 +230,23 @@ static PHP_FUNCTION(json_encode)
|
|||
return;
|
||||
}
|
||||
|
||||
JSON_G(error_code) = PHP_JSON_ERROR_NONE;
|
||||
php_json_encode_init(&encoder);
|
||||
encoder.max_depth = (int)depth;
|
||||
encoder.error_code = PHP_JSON_ERROR_NONE;
|
||||
php_json_encode_zval(&buf, parameter, (int)options, &encoder);
|
||||
JSON_G(error_code) = encoder.error_code;
|
||||
|
||||
JSON_G(encode_max_depth) = (int)depth;
|
||||
|
||||
php_json_encode(&buf, parameter, (int)options);
|
||||
|
||||
if (JSON_G(error_code) != PHP_JSON_ERROR_NONE && !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
|
||||
if (encoder.error_code != PHP_JSON_ERROR_NONE && !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
|
||||
smart_str_free(&buf);
|
||||
ZVAL_FALSE(return_value);
|
||||
} else {
|
||||
RETURN_FALSE;
|
||||
}
|
||||
|
||||
smart_str_0(&buf); /* copy? */
|
||||
if (buf.s) {
|
||||
RETURN_NEW_STR(buf.s);
|
||||
}
|
||||
RETURN_EMPTY_STRING();
|
||||
}
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ proto mixed json_decode(string json [, bool assoc [, long depth]])
|
||||
|
|
|
@ -29,11 +29,14 @@
|
|||
#include "ext/standard/html.h"
|
||||
#include "zend_smart_str.h"
|
||||
#include "php_json.h"
|
||||
#include "php_json_encoder.h"
|
||||
#include <zend_exceptions.h>
|
||||
|
||||
static const char digits[] = "0123456789abcdef";
|
||||
|
||||
static int php_json_escape_string(smart_str *buf, char *s, size_t len, int options);
|
||||
static int php_json_escape_string(
|
||||
smart_str *buf, char *s, size_t len,
|
||||
int options, php_json_encoder *encoder);
|
||||
|
||||
static int php_json_determine_array_type(zval *val) /* {{{ */
|
||||
{
|
||||
|
@ -76,12 +79,12 @@ static inline void php_json_pretty_print_char(smart_str *buf, int options, char
|
|||
}
|
||||
/* }}} */
|
||||
|
||||
static inline void php_json_pretty_print_indent(smart_str *buf, int options) /* {{{ */
|
||||
static inline void php_json_pretty_print_indent(smart_str *buf, int options, php_json_encoder *encoder) /* {{{ */
|
||||
{
|
||||
int i;
|
||||
|
||||
if (options & PHP_JSON_PRETTY_PRINT) {
|
||||
for (i = 0; i < JSON_G(encoder_depth); ++i) {
|
||||
for (i = 0; i < encoder->depth; ++i) {
|
||||
smart_str_appendl(buf, " ", 4);
|
||||
}
|
||||
}
|
||||
|
@ -126,7 +129,7 @@ static inline void php_json_encode_double(smart_str *buf, double d, int options)
|
|||
} \
|
||||
} while (0)
|
||||
|
||||
static int php_json_encode_array(smart_str *buf, zval *val, int options) /* {{{ */
|
||||
static int php_json_encode_array(smart_str *buf, zval *val, int options, php_json_encoder *encoder) /* {{{ */
|
||||
{
|
||||
int i, r, need_comma = 0;
|
||||
HashTable *myht;
|
||||
|
@ -140,7 +143,7 @@ static int php_json_encode_array(smart_str *buf, zval *val, int options) /* {{{
|
|||
}
|
||||
|
||||
if (myht && ZEND_HASH_GET_APPLY_COUNT(myht) > 1) {
|
||||
JSON_G(error_code) = PHP_JSON_ERROR_RECURSION;
|
||||
encoder->error_code = PHP_JSON_ERROR_RECURSION;
|
||||
smart_str_appendl(buf, "null", 4);
|
||||
return FAILURE;
|
||||
}
|
||||
|
@ -151,7 +154,7 @@ static int php_json_encode_array(smart_str *buf, zval *val, int options) /* {{{
|
|||
smart_str_appendc(buf, '{');
|
||||
}
|
||||
|
||||
++JSON_G(encoder_depth);
|
||||
++encoder->depth;
|
||||
|
||||
i = myht ? zend_hash_num_elements(myht) : 0;
|
||||
|
||||
|
@ -174,7 +177,7 @@ static int php_json_encode_array(smart_str *buf, zval *val, int options) /* {{{
|
|||
}
|
||||
|
||||
php_json_pretty_print_char(buf, options, '\n');
|
||||
php_json_pretty_print_indent(buf, options);
|
||||
php_json_pretty_print_indent(buf, options, encoder);
|
||||
} else if (r == PHP_JSON_OUTPUT_OBJECT) {
|
||||
if (key) {
|
||||
if (ZSTR_VAL(key)[0] == '\0' && ZSTR_LEN(key) > 0 && Z_TYPE_P(val) == IS_OBJECT) {
|
||||
|
@ -190,9 +193,10 @@ static int php_json_encode_array(smart_str *buf, zval *val, int options) /* {{{
|
|||
}
|
||||
|
||||
php_json_pretty_print_char(buf, options, '\n');
|
||||
php_json_pretty_print_indent(buf, options);
|
||||
php_json_pretty_print_indent(buf, options, encoder);
|
||||
|
||||
php_json_escape_string(buf, ZSTR_VAL(key), ZSTR_LEN(key), options & ~PHP_JSON_NUMERIC_CHECK);
|
||||
php_json_escape_string(buf, ZSTR_VAL(key), ZSTR_LEN(key),
|
||||
options & ~PHP_JSON_NUMERIC_CHECK, encoder);
|
||||
} else {
|
||||
if (need_comma) {
|
||||
smart_str_appendc(buf, ',');
|
||||
|
@ -201,7 +205,7 @@ static int php_json_encode_array(smart_str *buf, zval *val, int options) /* {{{
|
|||
}
|
||||
|
||||
php_json_pretty_print_char(buf, options, '\n');
|
||||
php_json_pretty_print_indent(buf, options);
|
||||
php_json_pretty_print_indent(buf, options, encoder);
|
||||
|
||||
smart_str_appendc(buf, '"');
|
||||
smart_str_append_long(buf, (zend_long) index);
|
||||
|
@ -212,7 +216,8 @@ static int php_json_encode_array(smart_str *buf, zval *val, int options) /* {{{
|
|||
php_json_pretty_print_char(buf, options, ' ');
|
||||
}
|
||||
|
||||
if (php_json_encode(buf, data, options) == FAILURE && !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
|
||||
if (php_json_encode_zval(buf, data, options, encoder) == FAILURE &&
|
||||
!(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
|
||||
PHP_JSON_HASH_APPLY_PROTECTION_DEC(tmp_ht);
|
||||
return FAILURE;
|
||||
}
|
||||
|
@ -221,18 +226,18 @@ static int php_json_encode_array(smart_str *buf, zval *val, int options) /* {{{
|
|||
} ZEND_HASH_FOREACH_END();
|
||||
}
|
||||
|
||||
if (JSON_G(encoder_depth) > JSON_G(encode_max_depth)) {
|
||||
JSON_G(error_code) = PHP_JSON_ERROR_DEPTH;
|
||||
if (encoder->depth > encoder->max_depth) {
|
||||
encoder->error_code = PHP_JSON_ERROR_DEPTH;
|
||||
if (!(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
|
||||
return FAILURE;
|
||||
}
|
||||
}
|
||||
--JSON_G(encoder_depth);
|
||||
--encoder->depth;
|
||||
|
||||
/* Only keep closing bracket on same line for empty arrays/objects */
|
||||
if (need_comma) {
|
||||
php_json_pretty_print_char(buf, options, '\n');
|
||||
php_json_pretty_print_indent(buf, options);
|
||||
php_json_pretty_print_indent(buf, options, encoder);
|
||||
}
|
||||
|
||||
if (r == PHP_JSON_OUTPUT_ARRAY) {
|
||||
|
@ -282,7 +287,9 @@ static int php_json_utf8_to_utf16(unsigned short *utf16, char utf8[], size_t len
|
|||
}
|
||||
/* }}} */
|
||||
|
||||
static int php_json_escape_string(smart_str *buf, char *s, size_t len, int options) /* {{{ */
|
||||
static int php_json_escape_string(
|
||||
smart_str *buf, char *s, size_t len,
|
||||
int options, php_json_encoder *encoder) /* {{{ */
|
||||
{
|
||||
int status;
|
||||
unsigned int us;
|
||||
|
@ -313,7 +320,7 @@ static int php_json_escape_string(smart_str *buf, char *s, size_t len, int optio
|
|||
if (options & PHP_JSON_UNESCAPED_UNICODE) {
|
||||
/* validate UTF-8 string first */
|
||||
if (php_json_utf8_to_utf16(NULL, s, len) < 0) {
|
||||
JSON_G(error_code) = PHP_JSON_ERROR_UTF8;
|
||||
encoder->error_code = PHP_JSON_ERROR_UTF8;
|
||||
if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
|
||||
smart_str_appendl(buf, "null", 4);
|
||||
}
|
||||
|
@ -337,7 +344,7 @@ static int php_json_escape_string(smart_str *buf, char *s, size_t len, int optio
|
|||
if (buf->s) {
|
||||
ZSTR_LEN(buf->s) = checkpoint;
|
||||
}
|
||||
JSON_G(error_code) = PHP_JSON_ERROR_UTF8;
|
||||
encoder->error_code = PHP_JSON_ERROR_UTF8;
|
||||
if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
|
||||
smart_str_appendl(buf, "null", 4);
|
||||
}
|
||||
|
@ -465,12 +472,12 @@ static int php_json_escape_string(smart_str *buf, char *s, size_t len, int optio
|
|||
}
|
||||
/* }}} */
|
||||
|
||||
static int php_json_encode_serializable_object(smart_str *buf, zval *val, int options) /* {{{ */
|
||||
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);
|
||||
zval retval, fname;
|
||||
HashTable* myht;
|
||||
int origin_error_code;
|
||||
int return_code;
|
||||
|
||||
if (Z_TYPE_P(val) == IS_ARRAY) {
|
||||
myht = Z_ARRVAL_P(val);
|
||||
|
@ -479,7 +486,7 @@ static int php_json_encode_serializable_object(smart_str *buf, zval *val, int op
|
|||
}
|
||||
|
||||
if (myht && ZEND_HASH_GET_APPLY_COUNT(myht) > 1) {
|
||||
JSON_G(error_code) = PHP_JSON_ERROR_RECURSION;
|
||||
encoder->error_code = PHP_JSON_ERROR_RECURSION;
|
||||
if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
|
||||
smart_str_appendl(buf, "null", 4);
|
||||
}
|
||||
|
@ -489,7 +496,6 @@ static int php_json_encode_serializable_object(smart_str *buf, zval *val, int op
|
|||
|
||||
ZVAL_STRING(&fname, "jsonSerialize");
|
||||
|
||||
origin_error_code = JSON_G(error_code);
|
||||
if (FAILURE == call_user_function_ex(EG(function_table), val, &fname, &retval, 0, NULL, 1, NULL) || Z_TYPE(retval) == IS_UNDEF) {
|
||||
if (!EG(exception)) {
|
||||
zend_throw_exception_ex(NULL, 0, "Failed calling %s::jsonSerialize()", ZSTR_VAL(ce->name));
|
||||
|
@ -502,7 +508,6 @@ static int php_json_encode_serializable_object(smart_str *buf, zval *val, int op
|
|||
return FAILURE;
|
||||
}
|
||||
|
||||
JSON_G(error_code) = origin_error_code;
|
||||
if (EG(exception)) {
|
||||
/* Error already raised */
|
||||
zval_ptr_dtor(&retval);
|
||||
|
@ -517,20 +522,20 @@ static int php_json_encode_serializable_object(smart_str *buf, zval *val, int op
|
|||
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_encode_array(buf, &retval, options);
|
||||
return_code = php_json_encode_array(buf, &retval, options, encoder);
|
||||
} else {
|
||||
/* All other types, encode as normal */
|
||||
php_json_encode(buf, &retval, options);
|
||||
return_code = php_json_encode_zval(buf, &retval, options, encoder);
|
||||
}
|
||||
|
||||
zval_ptr_dtor(&retval);
|
||||
zval_ptr_dtor(&fname);
|
||||
|
||||
return SUCCESS;
|
||||
return return_code;
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
int php_json_encode_zval(smart_str *buf, zval *val, int options) /* {{{ */
|
||||
int php_json_encode_zval(smart_str *buf, zval *val, int options, php_json_encoder *encoder) /* {{{ */
|
||||
{
|
||||
again:
|
||||
switch (Z_TYPE_P(val))
|
||||
|
@ -554,28 +559,28 @@ again:
|
|||
if (php_json_is_valid_double(Z_DVAL_P(val))) {
|
||||
php_json_encode_double(buf, Z_DVAL_P(val), options);
|
||||
} else {
|
||||
JSON_G(error_code) = PHP_JSON_ERROR_INF_OR_NAN;
|
||||
encoder->error_code = PHP_JSON_ERROR_INF_OR_NAN;
|
||||
smart_str_appendc(buf, '0');
|
||||
}
|
||||
break;
|
||||
|
||||
case IS_STRING:
|
||||
return php_json_escape_string(buf, Z_STRVAL_P(val), Z_STRLEN_P(val), options);
|
||||
return php_json_escape_string(buf, Z_STRVAL_P(val), Z_STRLEN_P(val), options, encoder);
|
||||
|
||||
case IS_OBJECT:
|
||||
if (instanceof_function(Z_OBJCE_P(val), php_json_serializable_ce)) {
|
||||
return php_json_encode_serializable_object(buf, val, options);
|
||||
return php_json_encode_serializable_object(buf, val, options, encoder);
|
||||
}
|
||||
/* fallthrough -- Non-serializable object */
|
||||
case IS_ARRAY:
|
||||
return php_json_encode_array(buf, val, options);
|
||||
return php_json_encode_array(buf, val, options, encoder);
|
||||
|
||||
case IS_REFERENCE:
|
||||
val = Z_REFVAL_P(val);
|
||||
goto again;
|
||||
|
||||
default:
|
||||
JSON_G(error_code) = PHP_JSON_ERROR_UNSUPPORTED_TYPE;
|
||||
encoder->error_code = PHP_JSON_ERROR_UNSUPPORTED_TYPE;
|
||||
if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
|
||||
smart_str_appendl(buf, "null", 4);
|
||||
}
|
||||
|
|
|
@ -22,6 +22,19 @@
|
|||
#include "php.h"
|
||||
#include "zend_smart_str.h"
|
||||
|
||||
int php_json_encode_zval(smart_str *buf, zval *val, int options);
|
||||
typedef struct _php_json_encoder php_json_encoder;
|
||||
|
||||
struct _php_json_encoder {
|
||||
int depth;
|
||||
int max_depth;
|
||||
php_json_error_code error_code;
|
||||
};
|
||||
|
||||
static inline void php_json_encode_init(php_json_encoder *encoder)
|
||||
{
|
||||
memset(encoder, 0, sizeof(php_json_encoder));
|
||||
}
|
||||
|
||||
int php_json_encode_zval(smart_str *buf, zval *val, int options, php_json_encoder *encoder);
|
||||
|
||||
#endif /* PHP_JSON_ENCODER_H */
|
||||
|
|
19
ext/json/tests/bug66025.phpt
Normal file
19
ext/json/tests/bug66025.phpt
Normal file
|
@ -0,0 +1,19 @@
|
|||
--TEST--
|
||||
Bug #66025 (Indent wrong when json_encode() called from jsonSerialize function)
|
||||
--SKIPIF--
|
||||
<?php
|
||||
if (!extension_loaded('json')) die('skip');
|
||||
?>
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Foo implements JsonSerializable {
|
||||
public function jsonSerialize() {
|
||||
return json_encode([1], JSON_PRETTY_PRINT);
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode([new Foo]), "\n";
|
||||
?>
|
||||
--EXPECT--
|
||||
["[\n 1\n]"]
|
21
ext/json/tests/bug73254.phpt
Normal file
21
ext/json/tests/bug73254.phpt
Normal file
|
@ -0,0 +1,21 @@
|
|||
--TEST--
|
||||
Bug #73254 (Incorrect indentation generated by json_encode() with JSON_PRETTY_PRINT)
|
||||
--SKIPIF--
|
||||
<?php
|
||||
if (!extension_loaded('json')) die('skip');
|
||||
?>
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
echo json_encode([json_encode([1], JSON_PRETTY_PRINT)]), "\n";
|
||||
|
||||
$fp = fopen('php://temp', 'r');
|
||||
$data = ['a' => $fp];
|
||||
echo json_encode($data), "\n";
|
||||
echo json_encode([json_encode([1], JSON_PRETTY_PRINT)]), "\n";
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
["[\n 1\n]"]
|
||||
|
||||
["[\n 1\n]"]
|
Loading…
Add table
Add a link
Reference in a new issue