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:
Jakub Zelenka 2016-10-30 13:20:10 +00:00
parent be6bf71274
commit c34de0b61c
6 changed files with 120 additions and 47 deletions

4
NEWS
View file

@ -10,6 +10,10 @@ PHP NEWS
. Fixed bug #73392 (A use-after-free in zend allocator management). . Fixed bug #73392 (A use-after-free in zend allocator management).
(Laruence) (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 27 Oct 2016, PHP 7.1.0RC5
- Core: - Core:

View file

@ -186,7 +186,17 @@ static PHP_MINFO_FUNCTION(json)
PHP_JSON_API int php_json_encode(smart_str *buf, zval *val, int options) /* {{{ */ 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) static PHP_FUNCTION(json_encode)
{ {
zval *parameter; zval *parameter;
php_json_encoder encoder;
smart_str buf = {0}; smart_str buf = {0};
zend_long options = 0; zend_long options = 0;
zend_long depth = PHP_JSON_PARSER_DEFAULT_DEPTH; zend_long depth = PHP_JSON_PARSER_DEFAULT_DEPTH;
@ -219,23 +230,23 @@ static PHP_FUNCTION(json_encode)
return; 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; if (encoder.error_code != PHP_JSON_ERROR_NONE && !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
php_json_encode(&buf, parameter, (int)options);
if (JSON_G(error_code) != PHP_JSON_ERROR_NONE && !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
smart_str_free(&buf); smart_str_free(&buf);
ZVAL_FALSE(return_value); RETURN_FALSE;
} else { }
smart_str_0(&buf); /* copy? */ smart_str_0(&buf); /* copy? */
if (buf.s) { if (buf.s) {
RETURN_NEW_STR(buf.s); RETURN_NEW_STR(buf.s);
} }
RETURN_EMPTY_STRING(); RETURN_EMPTY_STRING();
} }
}
/* }}} */ /* }}} */
/* {{{ proto mixed json_decode(string json [, bool assoc [, long depth]]) /* {{{ proto mixed json_decode(string json [, bool assoc [, long depth]])

View file

@ -29,11 +29,14 @@
#include "ext/standard/html.h" #include "ext/standard/html.h"
#include "zend_smart_str.h" #include "zend_smart_str.h"
#include "php_json.h" #include "php_json.h"
#include "php_json_encoder.h"
#include <zend_exceptions.h> #include <zend_exceptions.h>
static const char digits[] = "0123456789abcdef"; 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) /* {{{ */ 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; int i;
if (options & PHP_JSON_PRETTY_PRINT) { 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); 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) } 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; int i, r, need_comma = 0;
HashTable *myht; 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) { 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); smart_str_appendl(buf, "null", 4);
return FAILURE; return FAILURE;
} }
@ -151,7 +154,7 @@ static int php_json_encode_array(smart_str *buf, zval *val, int options) /* {{{
smart_str_appendc(buf, '{'); smart_str_appendc(buf, '{');
} }
++JSON_G(encoder_depth); ++encoder->depth;
i = myht ? zend_hash_num_elements(myht) : 0; 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_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) { } else if (r == PHP_JSON_OUTPUT_OBJECT) {
if (key) { if (key) {
if (ZSTR_VAL(key)[0] == '\0' && ZSTR_LEN(key) > 0 && Z_TYPE_P(val) == IS_OBJECT) { 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_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 { } else {
if (need_comma) { if (need_comma) {
smart_str_appendc(buf, ','); 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_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_appendc(buf, '"');
smart_str_append_long(buf, (zend_long) index); 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, ' '); 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); PHP_JSON_HASH_APPLY_PROTECTION_DEC(tmp_ht);
return FAILURE; return FAILURE;
} }
@ -221,18 +226,18 @@ static int php_json_encode_array(smart_str *buf, zval *val, int options) /* {{{
} ZEND_HASH_FOREACH_END(); } ZEND_HASH_FOREACH_END();
} }
if (JSON_G(encoder_depth) > JSON_G(encode_max_depth)) { if (encoder->depth > encoder->max_depth) {
JSON_G(error_code) = PHP_JSON_ERROR_DEPTH; encoder->error_code = PHP_JSON_ERROR_DEPTH;
if (!(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) { if (!(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
return FAILURE; return FAILURE;
} }
} }
--JSON_G(encoder_depth); --encoder->depth;
/* Only keep closing bracket on same line for empty arrays/objects */ /* Only keep closing bracket on same line for empty arrays/objects */
if (need_comma) { if (need_comma) {
php_json_pretty_print_char(buf, options, '\n'); 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) { 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; int status;
unsigned int us; 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) { if (options & PHP_JSON_UNESCAPED_UNICODE) {
/* validate UTF-8 string first */ /* validate UTF-8 string first */
if (php_json_utf8_to_utf16(NULL, s, len) < 0) { 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) { if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
smart_str_appendl(buf, "null", 4); 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) { if (buf->s) {
ZSTR_LEN(buf->s) = checkpoint; 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) { if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
smart_str_appendl(buf, "null", 4); 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); zend_class_entry *ce = Z_OBJCE_P(val);
zval retval, fname; zval retval, fname;
HashTable* myht; HashTable* myht;
int origin_error_code; int return_code;
if (Z_TYPE_P(val) == IS_ARRAY) { if (Z_TYPE_P(val) == IS_ARRAY) {
myht = Z_ARRVAL_P(val); 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) { 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) { if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
smart_str_appendl(buf, "null", 4); 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"); 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 (FAILURE == call_user_function_ex(EG(function_table), val, &fname, &retval, 0, NULL, 1, NULL) || Z_TYPE(retval) == IS_UNDEF) {
if (!EG(exception)) { if (!EG(exception)) {
zend_throw_exception_ex(NULL, 0, "Failed calling %s::jsonSerialize()", ZSTR_VAL(ce->name)); 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; return FAILURE;
} }
JSON_G(error_code) = origin_error_code;
if (EG(exception)) { if (EG(exception)) {
/* Error already raised */ /* Error already raised */
zval_ptr_dtor(&retval); 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) && 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_encode_array(buf, &retval, options); return_code = php_json_encode_array(buf, &retval, options, encoder);
} else { } else {
/* All other types, encode as normal */ /* 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(&retval);
zval_ptr_dtor(&fname); 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: again:
switch (Z_TYPE_P(val)) switch (Z_TYPE_P(val))
@ -554,28 +559,28 @@ again:
if (php_json_is_valid_double(Z_DVAL_P(val))) { if (php_json_is_valid_double(Z_DVAL_P(val))) {
php_json_encode_double(buf, Z_DVAL_P(val), options); php_json_encode_double(buf, Z_DVAL_P(val), options);
} else { } 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'); smart_str_appendc(buf, '0');
} }
break; break;
case IS_STRING: 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: case IS_OBJECT:
if (instanceof_function(Z_OBJCE_P(val), php_json_serializable_ce)) { 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 */ /* fallthrough -- Non-serializable object */
case IS_ARRAY: case IS_ARRAY:
return php_json_encode_array(buf, val, options); return php_json_encode_array(buf, val, options, encoder);
case IS_REFERENCE: case IS_REFERENCE:
val = Z_REFVAL_P(val); val = Z_REFVAL_P(val);
goto again; goto again;
default: 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) { if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
smart_str_appendl(buf, "null", 4); smart_str_appendl(buf, "null", 4);
} }

View file

@ -22,6 +22,19 @@
#include "php.h" #include "php.h"
#include "zend_smart_str.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 */ #endif /* PHP_JSON_ENCODER_H */

View 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]"]

View 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]"]