diff options
author | Andrea Faulds <ajf@ajf.me> | 2017-10-22 19:41:23 +0100 |
---|---|---|
committer | Andrea Faulds <ajf@ajf.me> | 2017-10-22 19:41:23 +0100 |
commit | e823770515bd0530bd3c09ea273c720b4df33734 (patch) | |
tree | 0d33cb3518feb5efa23114198ad9377d030e0900 | |
parent | d5127cec6ae020a23624a1d4e9da342a33d90cc0 (diff) | |
download | php-git-e823770515bd0530bd3c09ea273c720b4df33734.tar.gz |
Merge JSON_THROW_ON_ERROR
-rw-r--r-- | NEWS | 3 | ||||
-rw-r--r-- | UPGRADING | 15 | ||||
-rw-r--r-- | ext/json/json.c | 97 | ||||
-rw-r--r-- | ext/json/php_json.h | 1 | ||||
-rw-r--r-- | ext/json/tests/json_decode_exceptions.phpt | 50 | ||||
-rw-r--r-- | ext/json/tests/json_encode_exceptions.phpt | 56 | ||||
-rw-r--r-- | ext/json/tests/json_exceptions_error_clearing.phpt | 48 |
7 files changed, 236 insertions, 34 deletions
@@ -39,6 +39,9 @@ PHP NEWS . Fixed bug #75317 (UConverter::setDestinationEncoding changes source instead of destination). (andrewnester) +- JSON: + . Added JSON_THROW_ON_ERROR flag. (Andrea) + - LDAP: . Added ldap_exop_refresh helper for EXOP REFRESH operation with dds overlay. (Come) @@ -55,6 +55,15 @@ BCMath: 5. Changed Functions ======================================== +JSON: + . A new flag has been added, JSON_THROW_ON_ERROR, which can be used with + json_decode() or json_encode() and causes these functions to throw a + JsonException upon an error, instead of setting the global error state that + is retrieved with json_last_error(). JSON_PARTIAL_OUTPUT_ON_ERROR takes + precedence over JSON_THROW_ON_ERROR. + (RFC: https://wiki.php.net/rfc/json_throw_on_error) + +Standard: . debug_zval_dump() was changed to display recursive arrays and objects in the same way as var_dump(). Now, it doesn't display them twice. @@ -70,6 +79,9 @@ Date: 7. New Classes and Interfaces ======================================== +JSON: + . JsonException + ======================================== 8. Removed Extensions and SAPIs ======================================== @@ -93,6 +105,9 @@ Date: 10. New Global Constants ======================================== +JSON: + . JSON_THROW_ON_ERROR + PGSQL: . Requires Postgres 9.3 - PGSQL_DIAG_SCHEMA_NAME diff --git a/ext/json/json.c b/ext/json/json.c index d142f7ee3e..5c16e87c59 100644 --- a/ext/json/json.c +++ b/ext/json/json.c @@ -38,6 +38,7 @@ static PHP_FUNCTION(json_last_error); static PHP_FUNCTION(json_last_error_msg); PHP_JSON_API zend_class_entry *php_json_serializable_ce; +PHP_JSON_API zend_class_entry *php_json_exception_ce; PHP_JSON_API ZEND_DECLARE_MODULE_GLOBALS(json) @@ -95,6 +96,9 @@ static PHP_MINIT_FUNCTION(json) INIT_CLASS_ENTRY(ce, "JsonSerializable", json_serializable_interface); php_json_serializable_ce = zend_register_internal_interface(&ce); + INIT_CLASS_ENTRY(ce, "JsonException", NULL); + php_json_exception_ce = zend_register_internal_class_ex(&ce, zend_ce_exception); + /* options for json_encode */ PHP_JSON_REGISTER_CONSTANT("JSON_HEX_TAG", PHP_JSON_HEX_TAG); PHP_JSON_REGISTER_CONSTANT("JSON_HEX_AMP", PHP_JSON_HEX_AMP); @@ -116,6 +120,7 @@ static PHP_MINIT_FUNCTION(json) /* common options for json_decode and json_encode */ PHP_JSON_REGISTER_CONSTANT("JSON_INVALID_UTF8_IGNORE", PHP_JSON_INVALID_UTF8_IGNORE); PHP_JSON_REGISTER_CONSTANT("JSON_INVALID_UTF8_SUBSTITUTE", PHP_JSON_INVALID_UTF8_SUBSTITUTE); + PHP_JSON_REGISTER_CONSTANT("JSON_THROW_ON_ERROR", PHP_JSON_THROW_ON_ERROR); /* json error constants */ PHP_JSON_REGISTER_CONSTANT("JSON_ERROR_NONE", PHP_JSON_ERROR_NONE); @@ -207,6 +212,37 @@ PHP_JSON_API int php_json_encode(smart_str *buf, zval *val, int options) /* {{{ } /* }}} */ +static const char *php_json_get_error_msg(php_json_error_code error_code) /* {{{ */ +{ + switch(error_code) { + case PHP_JSON_ERROR_NONE: + return "No error"; + case PHP_JSON_ERROR_DEPTH: + return "Maximum stack depth exceeded"; + case PHP_JSON_ERROR_STATE_MISMATCH: + return "State mismatch (invalid or malformed JSON)"; + case PHP_JSON_ERROR_CTRL_CHAR: + return "Control character error, possibly incorrectly encoded"; + case PHP_JSON_ERROR_SYNTAX: + return "Syntax error"; + case PHP_JSON_ERROR_UTF8: + return "Malformed UTF-8 characters, possibly incorrectly encoded"; + case PHP_JSON_ERROR_RECURSION: + return "Recursion detected"; + case PHP_JSON_ERROR_INF_OR_NAN: + return "Inf and NaN cannot be JSON encoded"; + case PHP_JSON_ERROR_UNSUPPORTED_TYPE: + return "Type is not supported"; + case PHP_JSON_ERROR_INVALID_PROPERTY_NAME: + return "The decoded property name is invalid"; + case PHP_JSON_ERROR_UTF16: + return "Single unpaired UTF-16 surrogate in unicode escape"; + default: + return "Unknown error"; + } +} +/* }}} */ + PHP_JSON_API int php_json_decode_ex(zval *return_value, char *str, size_t str_len, zend_long options, zend_long depth) /* {{{ */ { php_json_parser parser; @@ -214,7 +250,12 @@ PHP_JSON_API int php_json_decode_ex(zval *return_value, char *str, size_t str_le php_json_parser_init(&parser, return_value, str, str_len, (int)options, (int)depth); if (php_json_yyparse(&parser)) { - JSON_G(error_code) = php_json_parser_error_code(&parser); + php_json_error_code error_code = php_json_parser_error_code(&parser); + if (!(options & PHP_JSON_THROW_ON_ERROR)) { + JSON_G(error_code) = error_code; + } else { + zend_throw_exception(php_json_exception_ce, php_json_get_error_msg(error_code), error_code); + } RETVAL_NULL(); return FAILURE; } @@ -243,11 +284,19 @@ static PHP_FUNCTION(json_encode) php_json_encode_init(&encoder); encoder.max_depth = (int)depth; php_json_encode_zval(&buf, parameter, (int)options, &encoder); - JSON_G(error_code) = encoder.error_code; - if (encoder.error_code != PHP_JSON_ERROR_NONE && !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) { - smart_str_free(&buf); - RETURN_FALSE; + if (!(options & PHP_JSON_THROW_ON_ERROR) || (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) { + JSON_G(error_code) = encoder.error_code; + if (encoder.error_code != PHP_JSON_ERROR_NONE && !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) { + smart_str_free(&buf); + RETURN_FALSE; + } + } else { + if (encoder.error_code != PHP_JSON_ERROR_NONE) { + smart_str_free(&buf); + zend_throw_exception(php_json_exception_ce, php_json_get_error_msg(encoder.error_code), encoder.error_code); + RETURN_FALSE; + } } smart_str_0(&buf); /* copy? */ @@ -277,10 +326,16 @@ static PHP_FUNCTION(json_decode) Z_PARAM_LONG(options) ZEND_PARSE_PARAMETERS_END(); - JSON_G(error_code) = PHP_JSON_ERROR_NONE; + if (!(options & PHP_JSON_THROW_ON_ERROR)) { + JSON_G(error_code) = PHP_JSON_ERROR_NONE; + } if (!str_len) { - JSON_G(error_code) = PHP_JSON_ERROR_SYNTAX; + if (!(options & PHP_JSON_THROW_ON_ERROR)) { + JSON_G(error_code) = PHP_JSON_ERROR_SYNTAX; + } else { + zend_throw_exception(php_json_exception_ce, php_json_get_error_msg(PHP_JSON_ERROR_SYNTAX), PHP_JSON_ERROR_SYNTAX); + } RETURN_NULL(); } @@ -327,33 +382,7 @@ static PHP_FUNCTION(json_last_error_msg) return; } - switch(JSON_G(error_code)) { - case PHP_JSON_ERROR_NONE: - RETURN_STRING("No error"); - case PHP_JSON_ERROR_DEPTH: - RETURN_STRING("Maximum stack depth exceeded"); - case PHP_JSON_ERROR_STATE_MISMATCH: - RETURN_STRING("State mismatch (invalid or malformed JSON)"); - case PHP_JSON_ERROR_CTRL_CHAR: - RETURN_STRING("Control character error, possibly incorrectly encoded"); - case PHP_JSON_ERROR_SYNTAX: - RETURN_STRING("Syntax error"); - case PHP_JSON_ERROR_UTF8: - RETURN_STRING("Malformed UTF-8 characters, possibly incorrectly encoded"); - case PHP_JSON_ERROR_RECURSION: - RETURN_STRING("Recursion detected"); - case PHP_JSON_ERROR_INF_OR_NAN: - RETURN_STRING("Inf and NaN cannot be JSON encoded"); - case PHP_JSON_ERROR_UNSUPPORTED_TYPE: - RETURN_STRING("Type is not supported"); - case PHP_JSON_ERROR_INVALID_PROPERTY_NAME: - RETURN_STRING("The decoded property name is invalid"); - case PHP_JSON_ERROR_UTF16: - RETURN_STRING("Single unpaired UTF-16 surrogate in unicode escape"); - default: - RETURN_STRING("Unknown error"); - } - + RETURN_STRING(php_json_get_error_msg(JSON_G(error_code))); } /* }}} */ diff --git a/ext/json/php_json.h b/ext/json/php_json.h index cfd6ca4816..900329fa74 100644 --- a/ext/json/php_json.h +++ b/ext/json/php_json.h @@ -74,6 +74,7 @@ typedef enum { /* json_decode() and json_encode() common options */ #define PHP_JSON_INVALID_UTF8_IGNORE (1<<20) #define PHP_JSON_INVALID_UTF8_SUBSTITUTE (1<<21) +#define PHP_JSON_THROW_ON_ERROR (1<<22) /* Internal flags */ #define PHP_JSON_OUTPUT_ARRAY 0 diff --git a/ext/json/tests/json_decode_exceptions.phpt b/ext/json/tests/json_decode_exceptions.phpt new file mode 100644 index 0000000000..e71a3a54c0 --- /dev/null +++ b/ext/json/tests/json_decode_exceptions.phpt @@ -0,0 +1,50 @@ +--TEST-- +Test json_decode() function : JSON_THROW_ON_ERROR flag +--FILE-- +<?php + +try { + var_dump(json_decode("{", false, 512, JSON_THROW_ON_ERROR)); +} catch (JsonException $e) { + var_dump($e); +} + +?> +--EXPECTF-- +object(JsonException)#1 (7) { + ["message":protected]=> + string(12) "Syntax error" + ["string":"Exception":private]=> + string(0) "" + ["code":protected]=> + int(4) + ["file":protected]=> + string(%d) "%s" + ["line":protected]=> + int(%d) + ["trace":"Exception":private]=> + array(1) { + [0]=> + array(4) { + ["file"]=> + string(%d) "%s" + ["line"]=> + int(%d) + ["function"]=> + string(11) "json_decode" + ["args"]=> + array(4) { + [0]=> + string(1) "{" + [1]=> + bool(false) + [2]=> + int(512) + [3]=> + int(4194304) + } + } + } + ["previous":"Exception":private]=> + NULL +} diff --git a/ext/json/tests/json_encode_exceptions.phpt b/ext/json/tests/json_encode_exceptions.phpt new file mode 100644 index 0000000000..7da2f9cdd0 --- /dev/null +++ b/ext/json/tests/json_encode_exceptions.phpt @@ -0,0 +1,56 @@ +--TEST-- +Test json_encode() function : JSON_THROW_ON_ERROR flag +--FILE-- +<?php + +try { + var_dump(json_encode("\x80", JSON_THROW_ON_ERROR)); +} catch (JsonException $e) { + var_dump($e); +} + +// JSON_PARTIAL_OUTPUT_ON_ERROR is incompatible with exceptions +// So it overrides it for the sake of working with wrappers that add the +// JSON_THROW_ON_ERROR flag +var_dump(json_encode("\x80", JSON_THROW_ON_ERROR | JSON_PARTIAL_OUTPUT_ON_ERROR)); +var_dump(json_last_error()); +var_dump(json_last_error_msg()); + +?> +--EXPECTF-- +object(JsonException)#1 (7) { + ["message":protected]=> + string(56) "Malformed UTF-8 characters, possibly incorrectly encoded" + ["string":"Exception":private]=> + string(0) "" + ["code":protected]=> + int(5) + ["file":protected]=> + string(%d) "%s" + ["line":protected]=> + int(%d) + ["trace":"Exception":private]=> + array(1) { + [0]=> + array(4) { + ["file"]=> + string(%d) "%s" + ["line"]=> + int(%d) + ["function"]=> + string(11) "json_encode" + ["args"]=> + array(2) { + [0]=> + string(1) "%s" + [1]=> + int(4194304) + } + } + } + ["previous":"Exception":private]=> + NULL +} +string(4) "null" +int(5) +string(56) "Malformed UTF-8 characters, possibly incorrectly encoded" diff --git a/ext/json/tests/json_exceptions_error_clearing.phpt b/ext/json/tests/json_exceptions_error_clearing.phpt new file mode 100644 index 0000000000..e55be5e02b --- /dev/null +++ b/ext/json/tests/json_exceptions_error_clearing.phpt @@ -0,0 +1,48 @@ +--TEST-- +JSON_THROW_ON_ERROR: global error flag untouched +--FILE-- +<?php + +var_dump(json_last_error()); + +// here we cause a different kind of error to the following errors, so that +// we can be sure the global error state looking unchanged isn't coincidence +json_decode("\xFF"); + +var_dump(json_last_error()); + +try { + json_decode("", false, 512, JSON_THROW_ON_ERROR); +} catch (JsonException $e) { + echo "Caught JSON exception: ", $e->getCode(), PHP_EOL; +} + +var_dump(json_last_error()); + +try { + json_decode("{", false, 512, JSON_THROW_ON_ERROR); +} catch (JsonException $e) { + echo "Caught JSON exception: ", $e->getCode(), PHP_EOL; +} + +var_dump(json_last_error()); + + +try { + json_encode(NAN, JSON_THROW_ON_ERROR); +} catch (JsonException $e) { + echo "Caught JSON exception: ", $e->getCode(), PHP_EOL; +} + +var_dump(json_last_error()); + +?> +--EXPECT-- +int(0) +int(5) +Caught JSON exception: 4 +int(5) +Caught JSON exception: 4 +int(5) +Caught JSON exception: 7 +int(5) |