Skip to content

Commit c34de0b

Browse files
committed
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.
1 parent be6bf71 commit c34de0b

File tree

6 files changed

+120
-47
lines changed

6 files changed

+120
-47
lines changed

NEWS

+4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ PHP NEWS
1010
. Fixed bug #73392 (A use-after-free in zend allocator management).
1111
(Laruence)
1212

13+
- JSON:
14+
. Introduced encoder struct instead of global which fixes bugs #66025 and
15+
#73254 related to pretty print indentation. (Jakub Zelenka)
16+
1317
27 Oct 2016, PHP 7.1.0RC5
1418

1519
- Core:

ext/json/json.c

+25-14
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,17 @@ static PHP_MINFO_FUNCTION(json)
186186

187187
PHP_JSON_API int php_json_encode(smart_str *buf, zval *val, int options) /* {{{ */
188188
{
189-
return php_json_encode_zval(buf, val, options);
189+
php_json_encoder encoder;
190+
int return_code;
191+
192+
php_json_encode_init(&encoder);
193+
encoder.max_depth = JSON_G(encode_max_depth);
194+
encoder.error_code = PHP_JSON_ERROR_NONE;
195+
196+
return_code = php_json_encode_zval(buf, val, options, &encoder);
197+
JSON_G(error_code) = encoder.error_code;
198+
199+
return return_code;
190200
}
191201
/* }}} */
192202

@@ -211,6 +221,7 @@ PHP_JSON_API int php_json_decode_ex(zval *return_value, char *str, size_t str_le
211221
static PHP_FUNCTION(json_encode)
212222
{
213223
zval *parameter;
224+
php_json_encoder encoder;
214225
smart_str buf = {0};
215226
zend_long options = 0;
216227
zend_long depth = PHP_JSON_PARSER_DEFAULT_DEPTH;
@@ -219,22 +230,22 @@ static PHP_FUNCTION(json_encode)
219230
return;
220231
}
221232

222-
JSON_G(error_code) = PHP_JSON_ERROR_NONE;
223-
224-
JSON_G(encode_max_depth) = (int)depth;
233+
php_json_encode_init(&encoder);
234+
encoder.max_depth = (int)depth;
235+
encoder.error_code = PHP_JSON_ERROR_NONE;
236+
php_json_encode_zval(&buf, parameter, (int)options, &encoder);
237+
JSON_G(error_code) = encoder.error_code;
225238

226-
php_json_encode(&buf, parameter, (int)options);
227-
228-
if (JSON_G(error_code) != PHP_JSON_ERROR_NONE && !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
239+
if (encoder.error_code != PHP_JSON_ERROR_NONE && !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
229240
smart_str_free(&buf);
230-
ZVAL_FALSE(return_value);
231-
} else {
232-
smart_str_0(&buf); /* copy? */
233-
if (buf.s) {
234-
RETURN_NEW_STR(buf.s);
235-
}
236-
RETURN_EMPTY_STRING();
241+
RETURN_FALSE;
242+
}
243+
244+
smart_str_0(&buf); /* copy? */
245+
if (buf.s) {
246+
RETURN_NEW_STR(buf.s);
237247
}
248+
RETURN_EMPTY_STRING();
238249
}
239250
/* }}} */
240251

ext/json/json_encoder.c

+37-32
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,14 @@
2929
#include "ext/standard/html.h"
3030
#include "zend_smart_str.h"
3131
#include "php_json.h"
32+
#include "php_json_encoder.h"
3233
#include <zend_exceptions.h>
3334

3435
static const char digits[] = "0123456789abcdef";
3536

36-
static int php_json_escape_string(smart_str *buf, char *s, size_t len, int options);
37+
static int php_json_escape_string(
38+
smart_str *buf, char *s, size_t len,
39+
int options, php_json_encoder *encoder);
3740

3841
static int php_json_determine_array_type(zval *val) /* {{{ */
3942
{
@@ -76,12 +79,12 @@ static inline void php_json_pretty_print_char(smart_str *buf, int options, char
7679
}
7780
/* }}} */
7881

79-
static inline void php_json_pretty_print_indent(smart_str *buf, int options) /* {{{ */
82+
static inline void php_json_pretty_print_indent(smart_str *buf, int options, php_json_encoder *encoder) /* {{{ */
8083
{
8184
int i;
8285

8386
if (options & PHP_JSON_PRETTY_PRINT) {
84-
for (i = 0; i < JSON_G(encoder_depth); ++i) {
87+
for (i = 0; i < encoder->depth; ++i) {
8588
smart_str_appendl(buf, " ", 4);
8689
}
8790
}
@@ -126,7 +129,7 @@ static inline void php_json_encode_double(smart_str *buf, double d, int options)
126129
} \
127130
} while (0)
128131

129-
static int php_json_encode_array(smart_str *buf, zval *val, int options) /* {{{ */
132+
static int php_json_encode_array(smart_str *buf, zval *val, int options, php_json_encoder *encoder) /* {{{ */
130133
{
131134
int i, r, need_comma = 0;
132135
HashTable *myht;
@@ -140,7 +143,7 @@ static int php_json_encode_array(smart_str *buf, zval *val, int options) /* {{{
140143
}
141144

142145
if (myht && ZEND_HASH_GET_APPLY_COUNT(myht) > 1) {
143-
JSON_G(error_code) = PHP_JSON_ERROR_RECURSION;
146+
encoder->error_code = PHP_JSON_ERROR_RECURSION;
144147
smart_str_appendl(buf, "null", 4);
145148
return FAILURE;
146149
}
@@ -151,7 +154,7 @@ static int php_json_encode_array(smart_str *buf, zval *val, int options) /* {{{
151154
smart_str_appendc(buf, '{');
152155
}
153156

154-
++JSON_G(encoder_depth);
157+
++encoder->depth;
155158

156159
i = myht ? zend_hash_num_elements(myht) : 0;
157160

@@ -174,7 +177,7 @@ static int php_json_encode_array(smart_str *buf, zval *val, int options) /* {{{
174177
}
175178

176179
php_json_pretty_print_char(buf, options, '\n');
177-
php_json_pretty_print_indent(buf, options);
180+
php_json_pretty_print_indent(buf, options, encoder);
178181
} else if (r == PHP_JSON_OUTPUT_OBJECT) {
179182
if (key) {
180183
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) /* {{{
190193
}
191194

192195
php_json_pretty_print_char(buf, options, '\n');
193-
php_json_pretty_print_indent(buf, options);
196+
php_json_pretty_print_indent(buf, options, encoder);
194197

195-
php_json_escape_string(buf, ZSTR_VAL(key), ZSTR_LEN(key), options & ~PHP_JSON_NUMERIC_CHECK);
198+
php_json_escape_string(buf, ZSTR_VAL(key), ZSTR_LEN(key),
199+
options & ~PHP_JSON_NUMERIC_CHECK, encoder);
196200
} else {
197201
if (need_comma) {
198202
smart_str_appendc(buf, ',');
@@ -201,7 +205,7 @@ static int php_json_encode_array(smart_str *buf, zval *val, int options) /* {{{
201205
}
202206

203207
php_json_pretty_print_char(buf, options, '\n');
204-
php_json_pretty_print_indent(buf, options);
208+
php_json_pretty_print_indent(buf, options, encoder);
205209

206210
smart_str_appendc(buf, '"');
207211
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) /* {{{
212216
php_json_pretty_print_char(buf, options, ' ');
213217
}
214218

215-
if (php_json_encode(buf, data, options) == FAILURE && !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
219+
if (php_json_encode_zval(buf, data, options, encoder) == FAILURE &&
220+
!(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
216221
PHP_JSON_HASH_APPLY_PROTECTION_DEC(tmp_ht);
217222
return FAILURE;
218223
}
@@ -221,18 +226,18 @@ static int php_json_encode_array(smart_str *buf, zval *val, int options) /* {{{
221226
} ZEND_HASH_FOREACH_END();
222227
}
223228

224-
if (JSON_G(encoder_depth) > JSON_G(encode_max_depth)) {
225-
JSON_G(error_code) = PHP_JSON_ERROR_DEPTH;
229+
if (encoder->depth > encoder->max_depth) {
230+
encoder->error_code = PHP_JSON_ERROR_DEPTH;
226231
if (!(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
227232
return FAILURE;
228233
}
229234
}
230-
--JSON_G(encoder_depth);
235+
--encoder->depth;
231236

232237
/* Only keep closing bracket on same line for empty arrays/objects */
233238
if (need_comma) {
234239
php_json_pretty_print_char(buf, options, '\n');
235-
php_json_pretty_print_indent(buf, options);
240+
php_json_pretty_print_indent(buf, options, encoder);
236241
}
237242

238243
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
282287
}
283288
/* }}} */
284289

285-
static int php_json_escape_string(smart_str *buf, char *s, size_t len, int options) /* {{{ */
290+
static int php_json_escape_string(
291+
smart_str *buf, char *s, size_t len,
292+
int options, php_json_encoder *encoder) /* {{{ */
286293
{
287294
int status;
288295
unsigned int us;
@@ -313,7 +320,7 @@ static int php_json_escape_string(smart_str *buf, char *s, size_t len, int optio
313320
if (options & PHP_JSON_UNESCAPED_UNICODE) {
314321
/* validate UTF-8 string first */
315322
if (php_json_utf8_to_utf16(NULL, s, len) < 0) {
316-
JSON_G(error_code) = PHP_JSON_ERROR_UTF8;
323+
encoder->error_code = PHP_JSON_ERROR_UTF8;
317324
if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
318325
smart_str_appendl(buf, "null", 4);
319326
}
@@ -337,7 +344,7 @@ static int php_json_escape_string(smart_str *buf, char *s, size_t len, int optio
337344
if (buf->s) {
338345
ZSTR_LEN(buf->s) = checkpoint;
339346
}
340-
JSON_G(error_code) = PHP_JSON_ERROR_UTF8;
347+
encoder->error_code = PHP_JSON_ERROR_UTF8;
341348
if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
342349
smart_str_appendl(buf, "null", 4);
343350
}
@@ -465,12 +472,12 @@ static int php_json_escape_string(smart_str *buf, char *s, size_t len, int optio
465472
}
466473
/* }}} */
467474

468-
static int php_json_encode_serializable_object(smart_str *buf, zval *val, int options) /* {{{ */
475+
static int php_json_encode_serializable_object(smart_str *buf, zval *val, int options, php_json_encoder *encoder) /* {{{ */
469476
{
470477
zend_class_entry *ce = Z_OBJCE_P(val);
471478
zval retval, fname;
472479
HashTable* myht;
473-
int origin_error_code;
480+
int return_code;
474481

475482
if (Z_TYPE_P(val) == IS_ARRAY) {
476483
myht = Z_ARRVAL_P(val);
@@ -479,7 +486,7 @@ static int php_json_encode_serializable_object(smart_str *buf, zval *val, int op
479486
}
480487

481488
if (myht && ZEND_HASH_GET_APPLY_COUNT(myht) > 1) {
482-
JSON_G(error_code) = PHP_JSON_ERROR_RECURSION;
489+
encoder->error_code = PHP_JSON_ERROR_RECURSION;
483490
if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
484491
smart_str_appendl(buf, "null", 4);
485492
}
@@ -489,7 +496,6 @@ static int php_json_encode_serializable_object(smart_str *buf, zval *val, int op
489496

490497
ZVAL_STRING(&fname, "jsonSerialize");
491498

492-
origin_error_code = JSON_G(error_code);
493499
if (FAILURE == call_user_function_ex(EG(function_table), val, &fname, &retval, 0, NULL, 1, NULL) || Z_TYPE(retval) == IS_UNDEF) {
494500
if (!EG(exception)) {
495501
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
502508
return FAILURE;
503509
}
504510

505-
JSON_G(error_code) = origin_error_code;
506511
if (EG(exception)) {
507512
/* Error already raised */
508513
zval_ptr_dtor(&retval);
@@ -517,20 +522,20 @@ static int php_json_encode_serializable_object(smart_str *buf, zval *val, int op
517522
if ((Z_TYPE(retval) == IS_OBJECT) &&
518523
(Z_OBJ(retval) == Z_OBJ_P(val))) {
519524
/* Handle the case where jsonSerialize does: return $this; by going straight to encode array */
520-
php_json_encode_array(buf, &retval, options);
525+
return_code = php_json_encode_array(buf, &retval, options, encoder);
521526
} else {
522527
/* All other types, encode as normal */
523-
php_json_encode(buf, &retval, options);
528+
return_code = php_json_encode_zval(buf, &retval, options, encoder);
524529
}
525530

526531
zval_ptr_dtor(&retval);
527532
zval_ptr_dtor(&fname);
528533

529-
return SUCCESS;
534+
return return_code;
530535
}
531536
/* }}} */
532537

533-
int php_json_encode_zval(smart_str *buf, zval *val, int options) /* {{{ */
538+
int php_json_encode_zval(smart_str *buf, zval *val, int options, php_json_encoder *encoder) /* {{{ */
534539
{
535540
again:
536541
switch (Z_TYPE_P(val))
@@ -554,28 +559,28 @@ int php_json_encode_zval(smart_str *buf, zval *val, int options) /* {{{ */
554559
if (php_json_is_valid_double(Z_DVAL_P(val))) {
555560
php_json_encode_double(buf, Z_DVAL_P(val), options);
556561
} else {
557-
JSON_G(error_code) = PHP_JSON_ERROR_INF_OR_NAN;
562+
encoder->error_code = PHP_JSON_ERROR_INF_OR_NAN;
558563
smart_str_appendc(buf, '0');
559564
}
560565
break;
561566

562567
case IS_STRING:
563-
return php_json_escape_string(buf, Z_STRVAL_P(val), Z_STRLEN_P(val), options);
568+
return php_json_escape_string(buf, Z_STRVAL_P(val), Z_STRLEN_P(val), options, encoder);
564569

565570
case IS_OBJECT:
566571
if (instanceof_function(Z_OBJCE_P(val), php_json_serializable_ce)) {
567-
return php_json_encode_serializable_object(buf, val, options);
572+
return php_json_encode_serializable_object(buf, val, options, encoder);
568573
}
569574
/* fallthrough -- Non-serializable object */
570575
case IS_ARRAY:
571-
return php_json_encode_array(buf, val, options);
576+
return php_json_encode_array(buf, val, options, encoder);
572577

573578
case IS_REFERENCE:
574579
val = Z_REFVAL_P(val);
575580
goto again;
576581

577582
default:
578-
JSON_G(error_code) = PHP_JSON_ERROR_UNSUPPORTED_TYPE;
583+
encoder->error_code = PHP_JSON_ERROR_UNSUPPORTED_TYPE;
579584
if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
580585
smart_str_appendl(buf, "null", 4);
581586
}

ext/json/php_json_encoder.h

+14-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,19 @@
2222
#include "php.h"
2323
#include "zend_smart_str.h"
2424

25-
int php_json_encode_zval(smart_str *buf, zval *val, int options);
25+
typedef struct _php_json_encoder php_json_encoder;
26+
27+
struct _php_json_encoder {
28+
int depth;
29+
int max_depth;
30+
php_json_error_code error_code;
31+
};
32+
33+
static inline void php_json_encode_init(php_json_encoder *encoder)
34+
{
35+
memset(encoder, 0, sizeof(php_json_encoder));
36+
}
37+
38+
int php_json_encode_zval(smart_str *buf, zval *val, int options, php_json_encoder *encoder);
2639

2740
#endif /* PHP_JSON_ENCODER_H */

ext/json/tests/bug66025.phpt

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
--TEST--
2+
Bug #66025 (Indent wrong when json_encode() called from jsonSerialize function)
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded('json')) die('skip');
6+
?>
7+
--FILE--
8+
<?php
9+
10+
class Foo implements JsonSerializable {
11+
public function jsonSerialize() {
12+
return json_encode([1], JSON_PRETTY_PRINT);
13+
}
14+
}
15+
16+
echo json_encode([new Foo]), "\n";
17+
?>
18+
--EXPECT--
19+
["[\n 1\n]"]

ext/json/tests/bug73254.phpt

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
Bug #73254 (Incorrect indentation generated by json_encode() with JSON_PRETTY_PRINT)
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded('json')) die('skip');
6+
?>
7+
--FILE--
8+
<?php
9+
10+
echo json_encode([json_encode([1], JSON_PRETTY_PRINT)]), "\n";
11+
12+
$fp = fopen('php://temp', 'r');
13+
$data = ['a' => $fp];
14+
echo json_encode($data), "\n";
15+
echo json_encode([json_encode([1], JSON_PRETTY_PRINT)]), "\n";
16+
17+
?>
18+
--EXPECT--
19+
["[\n 1\n]"]
20+
21+
["[\n 1\n]"]

0 commit comments

Comments
 (0)