]> granicus.if.org Git - php/commitdiff
Merge JSON_THROW_ON_ERROR
authorAndrea Faulds <ajf@ajf.me>
Sun, 22 Oct 2017 18:41:23 +0000 (19:41 +0100)
committerAndrea Faulds <ajf@ajf.me>
Sun, 22 Oct 2017 18:41:23 +0000 (19:41 +0100)
NEWS
UPGRADING
ext/json/json.c
ext/json/php_json.h
ext/json/tests/json_decode_exceptions.phpt [new file with mode: 0644]
ext/json/tests/json_encode_exceptions.phpt [new file with mode: 0644]
ext/json/tests/json_exceptions_error_clearing.phpt [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index be7a72751abf5e6e09119cd7d4fe66026a57278a..e654b29e62f5b9f520b3581b8a8518ae91740a5b 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -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)
index f9d59167050184ebbb902140ac2cd6e7312b3ecf..3cca4334851e904cd695149f220b24ef621a8d1e 100644 (file)
--- a/UPGRADING
+++ b/UPGRADING
@@ -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
index d142f7ee3e0c40b4a3c46b584d161482a889de59..5c16e87c599f7908ac6007b7d33f96be54468a8b 100644 (file)
@@ -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)));
 }
 /* }}} */
 
index cfd6ca48165f9fe25356de9aec5e5395c90d4a9b..900329fa74735fb61d70fe6d710624c533e441d5 100644 (file)
@@ -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 (file)
index 0000000..e71a3a5
--- /dev/null
@@ -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 (file)
index 0000000..7da2f9c
--- /dev/null
@@ -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 (file)
index 0000000..e55be5e
--- /dev/null
@@ -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)