]> granicus.if.org Git - php/commitdiff
Introduce json encoder to fix globals related issues
authorJakub Zelenka <bukka@php.net>
Sun, 30 Oct 2016 13:20:10 +0000 (13:20 +0000)
committerJakub Zelenka <bukka@php.net>
Sun, 30 Oct 2016 13:20:10 +0000 (13:20 +0000)
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.

NEWS
ext/json/json.c
ext/json/json_encoder.c
ext/json/php_json_encoder.h
ext/json/tests/bug66025.phpt [new file with mode: 0644]
ext/json/tests/bug73254.phpt [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index 3d1be74caf0b9ac2d85149fb85cd67d2160eab08..cc381e60a342585b2fc256982804b3fb8b0e5e6b 100644 (file)
--- a/NEWS
+++ b/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:
index 395f11db53fa8c8f065e46f732779683b26d2591..202296c65cc73b8220c23d1161b3fcd23cf2ab0d 100644 (file)
@@ -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();
 }
 /* }}} */
 
index 473726c569d77c1bad2a03e0df227abbc12a0a9e..ca41f383be25d1425b1e6108012eb73490ac86ad 100644 (file)
 #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);
                        }
index 0fec9174b534dff274fdd8f86406a846cac45b3e..68ce8b2de2d577297154ee9a70d5c2d6ef741443 100644 (file)
 #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 (file)
index 0000000..9322d39
--- /dev/null
@@ -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 (file)
index 0000000..b043330
--- /dev/null
@@ -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]"]