summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS6
-rw-r--r--ext/json/json.c4
-rw-r--r--ext/json/json_encoder.c119
-rw-r--r--ext/json/php_json_encoder.h2
-rw-r--r--ext/json/tests/bug68992.phpt30
5 files changed, 110 insertions, 51 deletions
diff --git a/NEWS b/NEWS
index 6dc4f1aeef..97169e7879 100644
--- a/NEWS
+++ b/NEWS
@@ -25,6 +25,12 @@ PHP NEWS
- Intl:
. Fixed bug #65732 (grapheme_*() is not Unicode compliant on CR LF
sequence). (cmb)
+
+- JSON:
+ . Implemented earlier return when json_encode fails, fixes bugs #68992
+ (Stacking exceptions thrown by JsonSerializable) and #70275 (On recursion
+ error, json_encode can eat up all system memory). (Jakub Zelenka)
+
- Opcache:
. Fixed bug #72949 (Typo in opcache error message). (cmb)
diff --git a/ext/json/json.c b/ext/json/json.c
index 2acfb5e79f..3bbe532d97 100644
--- a/ext/json/json.c
+++ b/ext/json/json.c
@@ -186,9 +186,7 @@ static PHP_MINFO_FUNCTION(json)
PHP_JSON_API int php_json_encode(smart_str *buf, zval *val, int options) /* {{{ */
{
- php_json_encode_zval(buf, val, options);
-
- return JSON_G(error_code) > 0 ? FAILURE : SUCCESS;
+ return php_json_encode_zval(buf, val, options);
}
/* }}} */
diff --git a/ext/json/json_encoder.c b/ext/json/json_encoder.c
index 7cf7673235..8fd2f23dff 100644
--- a/ext/json/json_encoder.c
+++ b/ext/json/json_encoder.c
@@ -33,7 +33,7 @@
static const char digits[] = "0123456789abcdef";
-static void 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);
static int php_json_determine_array_type(zval *val) /* {{{ */
{
@@ -108,7 +108,21 @@ static inline void php_json_encode_double(smart_str *buf, double d, int options)
}
/* }}} */
-static void php_json_encode_array(smart_str *buf, zval *val, int options) /* {{{ */
+#define PHP_JSON_HASH_APPLY_PROTECTION_INC(_tmp_ht) \
+ do { \
+ if (tmp_ht && ZEND_HASH_APPLY_PROTECTION(_tmp_ht)) { \
+ ZEND_HASH_INC_APPLY_COUNT(_tmp_ht); \
+ } \
+ } while (0)
+
+#define PHP_JSON_HASH_APPLY_PROTECTION_DEC(_tmp_ht) \
+ do { \
+ if (tmp_ht && ZEND_HASH_APPLY_PROTECTION(_tmp_ht)) { \
+ ZEND_HASH_DEC_APPLY_COUNT(_tmp_ht); \
+ } \
+ } while (0)
+
+static int php_json_encode_array(smart_str *buf, zval *val, int options) /* {{{ */
{
int i, r, need_comma = 0;
HashTable *myht;
@@ -124,7 +138,7 @@ static void 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;
smart_str_appendl(buf, "null", 4);
- return;
+ return FAILURE;
}
if (r == PHP_JSON_OUTPUT_ARRAY) {
@@ -146,9 +160,7 @@ static void php_json_encode_array(smart_str *buf, zval *val, int options) /* {{{
ZEND_HASH_FOREACH_KEY_VAL_IND(myht, index, key, data) {
ZVAL_DEREF(data);
tmp_ht = HASH_OF(data);
- if (tmp_ht && ZEND_HASH_APPLY_PROTECTION(tmp_ht)) {
- ZEND_HASH_INC_APPLY_COUNT(tmp_ht);
- }
+ PHP_JSON_HASH_APPLY_PROTECTION_INC(tmp_ht);
if (r == PHP_JSON_OUTPUT_ARRAY) {
if (need_comma) {
@@ -159,14 +171,11 @@ static void 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_encode(buf, data, options);
} 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) {
/* Skip protected and private members. */
- if (tmp_ht && ZEND_HASH_APPLY_PROTECTION(tmp_ht)) {
- ZEND_HASH_DEC_APPLY_COUNT(tmp_ht);
- }
+ PHP_JSON_HASH_APPLY_PROTECTION_DEC(tmp_ht);
continue;
}
@@ -180,11 +189,6 @@ static void php_json_encode_array(smart_str *buf, zval *val, int options) /* {{{
php_json_pretty_print_indent(buf, options);
php_json_escape_string(buf, ZSTR_VAL(key), ZSTR_LEN(key), options & ~PHP_JSON_NUMERIC_CHECK);
- smart_str_appendc(buf, ':');
-
- php_json_pretty_print_char(buf, options, ' ');
-
- php_json_encode(buf, data, options);
} else {
if (need_comma) {
smart_str_appendc(buf, ',');
@@ -198,22 +202,26 @@ static void php_json_encode_array(smart_str *buf, zval *val, int options) /* {{{
smart_str_appendc(buf, '"');
smart_str_append_long(buf, (zend_long) index);
smart_str_appendc(buf, '"');
- smart_str_appendc(buf, ':');
-
- php_json_pretty_print_char(buf, options, ' ');
-
- php_json_encode(buf, data, options);
}
+
+ smart_str_appendc(buf, ':');
+ php_json_pretty_print_char(buf, options, ' ');
}
- if (tmp_ht && ZEND_HASH_APPLY_PROTECTION(tmp_ht)) {
- ZEND_HASH_DEC_APPLY_COUNT(tmp_ht);
+ if (php_json_encode(buf, data, options) == FAILURE && !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
+ PHP_JSON_HASH_APPLY_PROTECTION_DEC(tmp_ht);
+ return FAILURE;
}
+
+ PHP_JSON_HASH_APPLY_PROTECTION_DEC(tmp_ht);
} ZEND_HASH_FOREACH_END();
}
if (JSON_G(encoder_depth) > JSON_G(encode_max_depth)) {
JSON_G(error_code) = PHP_JSON_ERROR_DEPTH;
+ if (!(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
+ return FAILURE;
+ }
}
--JSON_G(encoder_depth);
@@ -228,6 +236,8 @@ static void php_json_encode_array(smart_str *buf, zval *val, int options) /* {{{
} else {
smart_str_appendc(buf, '}');
}
+
+ return SUCCESS;
}
/* }}} */
@@ -268,7 +278,7 @@ static int php_json_utf8_to_utf16(unsigned short *utf16, char utf8[], size_t len
}
/* }}} */
-static void 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) /* {{{ */
{
int status;
unsigned int us;
@@ -276,7 +286,7 @@ static void php_json_escape_string(smart_str *buf, char *s, size_t len, int opti
if (len == 0) {
smart_str_appendl(buf, "\"\"", 2);
- return;
+ return SUCCESS;
}
if (options & PHP_JSON_NUMERIC_CHECK) {
@@ -287,10 +297,10 @@ static void php_json_escape_string(smart_str *buf, char *s, size_t len, int opti
if ((type = is_numeric_string(s, len, &p, &d, 0)) != 0) {
if (type == IS_LONG) {
smart_str_append_long(buf, p);
- return;
+ return SUCCESS;
} else if (type == IS_DOUBLE && php_json_is_valid_double(d)) {
php_json_encode_double(buf, d, options);
- return;
+ return SUCCESS;
}
}
@@ -300,8 +310,10 @@ static void php_json_escape_string(smart_str *buf, char *s, size_t len, int opti
/* validate UTF-8 string first */
if (php_json_utf8_to_utf16(NULL, s, len) < 0) {
JSON_G(error_code) = PHP_JSON_ERROR_UTF8;
- smart_str_appendl(buf, "null", 4);
- return;
+ if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
+ smart_str_appendl(buf, "null", 4);
+ }
+ return FAILURE;
}
}
@@ -322,8 +334,10 @@ static void php_json_escape_string(smart_str *buf, char *s, size_t len, int opti
ZSTR_LEN(buf->s) = checkpoint;
}
JSON_G(error_code) = PHP_JSON_ERROR_UTF8;
- smart_str_appendl(buf, "null", 4);
- return;
+ if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
+ smart_str_appendl(buf, "null", 4);
+ }
+ return FAILURE;
}
/* Escape U+2028/U+2029 line terminators, UNLESS both
JSON_UNESCAPED_UNICODE and
@@ -442,10 +456,12 @@ static void php_json_escape_string(smart_str *buf, char *s, size_t len, int opti
} while (pos < len);
smart_str_appendc(buf, '"');
+
+ return SUCCESS;
}
/* }}} */
-static void 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) /* {{{ */
{
zend_class_entry *ce = Z_OBJCE_P(val);
zval retval, fname;
@@ -460,8 +476,10 @@ static void php_json_encode_serializable_object(smart_str *buf, zval *val, int o
if (myht && ZEND_HASH_GET_APPLY_COUNT(myht) > 1) {
JSON_G(error_code) = PHP_JSON_ERROR_RECURSION;
- smart_str_appendl(buf, "null", 4);
- return;
+ if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
+ smart_str_appendl(buf, "null", 4);
+ }
+ return FAILURE;
}
@@ -470,9 +488,12 @@ static void php_json_encode_serializable_object(smart_str *buf, zval *val, int o
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) {
zend_throw_exception_ex(NULL, 0, "Failed calling %s::jsonSerialize()", ZSTR_VAL(ce->name));
- smart_str_appendl(buf, "null", sizeof("null") - 1);
zval_ptr_dtor(&fname);
- return;
+
+ if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
+ smart_str_appendl(buf, "null", 4);
+ }
+ return FAILURE;
}
JSON_G(error_code) = origin_error_code;
@@ -480,8 +501,11 @@ static void php_json_encode_serializable_object(smart_str *buf, zval *val, int o
/* Error already raised */
zval_ptr_dtor(&retval);
zval_ptr_dtor(&fname);
- smart_str_appendl(buf, "null", sizeof("null") - 1);
- return;
+
+ if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
+ smart_str_appendl(buf, "null", 4);
+ }
+ return FAILURE;
}
if ((Z_TYPE(retval) == IS_OBJECT) &&
@@ -495,10 +519,12 @@ static void php_json_encode_serializable_object(smart_str *buf, zval *val, int o
zval_ptr_dtor(&retval);
zval_ptr_dtor(&fname);
+
+ return SUCCESS;
}
/* }}} */
-void php_json_encode_zval(smart_str *buf, zval *val, int options) /* {{{ */
+int php_json_encode_zval(smart_str *buf, zval *val, int options) /* {{{ */
{
again:
switch (Z_TYPE_P(val))
@@ -528,18 +554,15 @@ again:
break;
case IS_STRING:
- php_json_escape_string(buf, Z_STRVAL_P(val), Z_STRLEN_P(val), options);
- break;
+ return php_json_escape_string(buf, Z_STRVAL_P(val), Z_STRLEN_P(val), options);
case IS_OBJECT:
if (instanceof_function(Z_OBJCE_P(val), php_json_serializable_ce)) {
- php_json_encode_serializable_object(buf, val, options);
- break;
+ return php_json_encode_serializable_object(buf, val, options);
}
/* fallthrough -- Non-serializable object */
case IS_ARRAY:
- php_json_encode_array(buf, val, options);
- break;
+ return php_json_encode_array(buf, val, options);
case IS_REFERENCE:
val = Z_REFVAL_P(val);
@@ -547,11 +570,13 @@ again:
default:
JSON_G(error_code) = PHP_JSON_ERROR_UNSUPPORTED_TYPE;
- smart_str_appendl(buf, "null", 4);
- break;
+ if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
+ smart_str_appendl(buf, "null", 4);
+ }
+ return FAILURE;
}
- return;
+ return SUCCESS;
}
/* }}} */
diff --git a/ext/json/php_json_encoder.h b/ext/json/php_json_encoder.h
index b10f7a614a..0fec9174b5 100644
--- a/ext/json/php_json_encoder.h
+++ b/ext/json/php_json_encoder.h
@@ -22,6 +22,6 @@
#include "php.h"
#include "zend_smart_str.h"
-void php_json_encode_zval(smart_str *buf, zval *val, int options);
+int php_json_encode_zval(smart_str *buf, zval *val, int options);
#endif /* PHP_JSON_ENCODER_H */
diff --git a/ext/json/tests/bug68992.phpt b/ext/json/tests/bug68992.phpt
new file mode 100644
index 0000000000..0fc41eca49
--- /dev/null
+++ b/ext/json/tests/bug68992.phpt
@@ -0,0 +1,30 @@
+--TEST--
+Bug #68992 (json_encode stacks exceptions thrown by JsonSerializable classes)
+--SKIPIF--
+<?php
+if (!extension_loaded('json')) die('skip');
+?>
+--FILE--
+<?php
+
+class MyClass implements JsonSerializable {
+ public function jsonSerialize() {
+ throw new Exception('Not implemented!');
+ }
+}
+$classes = [];
+for($i = 0; $i < 5; $i++) {
+ $classes[] = new MyClass();
+}
+
+try {
+ json_encode($classes);
+} catch(Exception $e) {
+ do {
+ printf("%s (%d) [%s]\n", $e->getMessage(), $e->getCode(), get_class($e));
+ } while ($e = $e->getPrevious());
+}
+?>
+--EXPECT--
+Failed calling MyClass::jsonSerialize() (0) [Exception]
+Not implemented! (0) [Exception]