diff options
author | Jakub Zelenka <bukka@php.net> | 2016-10-30 13:20:10 +0000 |
---|---|---|
committer | Jakub Zelenka <bukka@php.net> | 2016-10-30 13:20:10 +0000 |
commit | c34de0b61c68bd77e324b869760a53abead4c5ed (patch) | |
tree | 54dc1cb22510d6c992e47f9fef7a78e1290fa85e | |
parent | be6bf71274a4670d7c7f45142890eb0651359c64 (diff) | |
download | php-git-c34de0b61c68bd77e324b869760a53abead4c5ed.tar.gz |
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.
-rw-r--r-- | NEWS | 4 | ||||
-rw-r--r-- | ext/json/json.c | 39 | ||||
-rw-r--r-- | ext/json/json_encoder.c | 69 | ||||
-rw-r--r-- | ext/json/php_json_encoder.h | 15 | ||||
-rw-r--r-- | ext/json/tests/bug66025.phpt | 19 | ||||
-rw-r--r-- | ext/json/tests/bug73254.phpt | 21 |
6 files changed, 120 insertions, 47 deletions
@@ -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: diff --git a/ext/json/json.c b/ext/json/json.c index 395f11db53..202296c65c 100644 --- a/ext/json/json.c +++ b/ext/json/json.c @@ -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,22 +230,22 @@ static PHP_FUNCTION(json_encode) return; } - JSON_G(error_code) = PHP_JSON_ERROR_NONE; - - JSON_G(encode_max_depth) = (int)depth; + 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; - 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 { - smart_str_0(&buf); /* copy? */ - if (buf.s) { - RETURN_NEW_STR(buf.s); - } - RETURN_EMPTY_STRING(); + RETURN_FALSE; + } + + smart_str_0(&buf); /* copy? */ + if (buf.s) { + RETURN_NEW_STR(buf.s); } + RETURN_EMPTY_STRING(); } /* }}} */ diff --git a/ext/json/json_encoder.c b/ext/json/json_encoder.c index 473726c569..ca41f383be 100644 --- a/ext/json/json_encoder.c +++ b/ext/json/json_encoder.c @@ -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); } diff --git a/ext/json/php_json_encoder.h b/ext/json/php_json_encoder.h index 0fec9174b5..68ce8b2de2 100644 --- a/ext/json/php_json_encoder.h +++ b/ext/json/php_json_encoder.h @@ -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 */ diff --git a/ext/json/tests/bug66025.phpt b/ext/json/tests/bug66025.phpt new file mode 100644 index 0000000000..9322d39b66 --- /dev/null +++ b/ext/json/tests/bug66025.phpt @@ -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]"] diff --git a/ext/json/tests/bug73254.phpt b/ext/json/tests/bug73254.phpt new file mode 100644 index 0000000000..b043330cb7 --- /dev/null +++ b/ext/json/tests/bug73254.phpt @@ -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]"] |