Skip to content

Introduce json encoder to fix globals related issues #2173

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 25 additions & 14 deletions ext/json/json.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
/* }}} */

Expand All @@ -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;
Expand All @@ -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();
}
/* }}} */

Expand Down
69 changes: 37 additions & 32 deletions ext/json/json_encoder.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) /* {{{ */
{
Expand Down Expand Up @@ -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);
}
}
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
Expand All @@ -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;

Expand All @@ -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) {
Expand All @@ -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, ',');
Expand All @@ -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);
Expand All @@ -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;
}
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
}
Expand Down Expand Up @@ -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);
Expand All @@ -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);
}
Expand All @@ -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));
Expand All @@ -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);
Expand All @@ -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))
Expand All @@ -554,28 +559,28 @@ int php_json_encode_zval(smart_str *buf, zval *val, int options) /* {{{ */
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);
}
Expand Down
15 changes: 14 additions & 1 deletion ext/json/php_json_encoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 19 additions & 0 deletions ext/json/tests/bug66025.phpt
Original file line number Diff line number Diff line change
@@ -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 changes: 21 additions & 0 deletions ext/json/tests/bug73254.phpt
Original file line number Diff line number Diff line change
@@ -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]"]