Skip to content

Commit e823770

Browse files
Merge JSON_THROW_ON_ERROR
1 parent d5127ce commit e823770

7 files changed

+236
-34
lines changed

NEWS

+3
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ PHP NEWS
3939
. Fixed bug #75317 (UConverter::setDestinationEncoding changes source instead
4040
of destination). (andrewnester)
4141

42+
- JSON:
43+
. Added JSON_THROW_ON_ERROR flag. (Andrea)
44+
4245
- LDAP:
4346
. Added ldap_exop_refresh helper for EXOP REFRESH operation with dds overlay.
4447
(Come)

UPGRADING

+15
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,15 @@ BCMath:
5555
5. Changed Functions
5656
========================================
5757

58+
JSON:
59+
. A new flag has been added, JSON_THROW_ON_ERROR, which can be used with
60+
json_decode() or json_encode() and causes these functions to throw a
61+
JsonException upon an error, instead of setting the global error state that
62+
is retrieved with json_last_error(). JSON_PARTIAL_OUTPUT_ON_ERROR takes
63+
precedence over JSON_THROW_ON_ERROR.
64+
(RFC: https://wiki.php.net/rfc/json_throw_on_error)
65+
66+
Standard:
5867
. debug_zval_dump() was changed to display recursive arrays and objects
5968
in the same way as var_dump(). Now, it doesn't display them twice.
6069

@@ -70,6 +79,9 @@ Date:
7079
7. New Classes and Interfaces
7180
========================================
7281

82+
JSON:
83+
. JsonException
84+
7385
========================================
7486
8. Removed Extensions and SAPIs
7587
========================================
@@ -93,6 +105,9 @@ Date:
93105
10. New Global Constants
94106
========================================
95107

108+
JSON:
109+
. JSON_THROW_ON_ERROR
110+
96111
PGSQL:
97112
. Requires Postgres 9.3
98113
- PGSQL_DIAG_SCHEMA_NAME

ext/json/json.c

+63-34
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ static PHP_FUNCTION(json_last_error);
3838
static PHP_FUNCTION(json_last_error_msg);
3939

4040
PHP_JSON_API zend_class_entry *php_json_serializable_ce;
41+
PHP_JSON_API zend_class_entry *php_json_exception_ce;
4142

4243
PHP_JSON_API ZEND_DECLARE_MODULE_GLOBALS(json)
4344

@@ -95,6 +96,9 @@ static PHP_MINIT_FUNCTION(json)
9596
INIT_CLASS_ENTRY(ce, "JsonSerializable", json_serializable_interface);
9697
php_json_serializable_ce = zend_register_internal_interface(&ce);
9798

99+
INIT_CLASS_ENTRY(ce, "JsonException", NULL);
100+
php_json_exception_ce = zend_register_internal_class_ex(&ce, zend_ce_exception);
101+
98102
/* options for json_encode */
99103
PHP_JSON_REGISTER_CONSTANT("JSON_HEX_TAG", PHP_JSON_HEX_TAG);
100104
PHP_JSON_REGISTER_CONSTANT("JSON_HEX_AMP", PHP_JSON_HEX_AMP);
@@ -116,6 +120,7 @@ static PHP_MINIT_FUNCTION(json)
116120
/* common options for json_decode and json_encode */
117121
PHP_JSON_REGISTER_CONSTANT("JSON_INVALID_UTF8_IGNORE", PHP_JSON_INVALID_UTF8_IGNORE);
118122
PHP_JSON_REGISTER_CONSTANT("JSON_INVALID_UTF8_SUBSTITUTE", PHP_JSON_INVALID_UTF8_SUBSTITUTE);
123+
PHP_JSON_REGISTER_CONSTANT("JSON_THROW_ON_ERROR", PHP_JSON_THROW_ON_ERROR);
119124

120125
/* json error constants */
121126
PHP_JSON_REGISTER_CONSTANT("JSON_ERROR_NONE", PHP_JSON_ERROR_NONE);
@@ -207,14 +212,50 @@ PHP_JSON_API int php_json_encode(smart_str *buf, zval *val, int options) /* {{{
207212
}
208213
/* }}} */
209214

215+
static const char *php_json_get_error_msg(php_json_error_code error_code) /* {{{ */
216+
{
217+
switch(error_code) {
218+
case PHP_JSON_ERROR_NONE:
219+
return "No error";
220+
case PHP_JSON_ERROR_DEPTH:
221+
return "Maximum stack depth exceeded";
222+
case PHP_JSON_ERROR_STATE_MISMATCH:
223+
return "State mismatch (invalid or malformed JSON)";
224+
case PHP_JSON_ERROR_CTRL_CHAR:
225+
return "Control character error, possibly incorrectly encoded";
226+
case PHP_JSON_ERROR_SYNTAX:
227+
return "Syntax error";
228+
case PHP_JSON_ERROR_UTF8:
229+
return "Malformed UTF-8 characters, possibly incorrectly encoded";
230+
case PHP_JSON_ERROR_RECURSION:
231+
return "Recursion detected";
232+
case PHP_JSON_ERROR_INF_OR_NAN:
233+
return "Inf and NaN cannot be JSON encoded";
234+
case PHP_JSON_ERROR_UNSUPPORTED_TYPE:
235+
return "Type is not supported";
236+
case PHP_JSON_ERROR_INVALID_PROPERTY_NAME:
237+
return "The decoded property name is invalid";
238+
case PHP_JSON_ERROR_UTF16:
239+
return "Single unpaired UTF-16 surrogate in unicode escape";
240+
default:
241+
return "Unknown error";
242+
}
243+
}
244+
/* }}} */
245+
210246
PHP_JSON_API int php_json_decode_ex(zval *return_value, char *str, size_t str_len, zend_long options, zend_long depth) /* {{{ */
211247
{
212248
php_json_parser parser;
213249

214250
php_json_parser_init(&parser, return_value, str, str_len, (int)options, (int)depth);
215251

216252
if (php_json_yyparse(&parser)) {
217-
JSON_G(error_code) = php_json_parser_error_code(&parser);
253+
php_json_error_code error_code = php_json_parser_error_code(&parser);
254+
if (!(options & PHP_JSON_THROW_ON_ERROR)) {
255+
JSON_G(error_code) = error_code;
256+
} else {
257+
zend_throw_exception(php_json_exception_ce, php_json_get_error_msg(error_code), error_code);
258+
}
218259
RETVAL_NULL();
219260
return FAILURE;
220261
}
@@ -243,11 +284,19 @@ static PHP_FUNCTION(json_encode)
243284
php_json_encode_init(&encoder);
244285
encoder.max_depth = (int)depth;
245286
php_json_encode_zval(&buf, parameter, (int)options, &encoder);
246-
JSON_G(error_code) = encoder.error_code;
247287

248-
if (encoder.error_code != PHP_JSON_ERROR_NONE && !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
249-
smart_str_free(&buf);
250-
RETURN_FALSE;
288+
if (!(options & PHP_JSON_THROW_ON_ERROR) || (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
289+
JSON_G(error_code) = encoder.error_code;
290+
if (encoder.error_code != PHP_JSON_ERROR_NONE && !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
291+
smart_str_free(&buf);
292+
RETURN_FALSE;
293+
}
294+
} else {
295+
if (encoder.error_code != PHP_JSON_ERROR_NONE) {
296+
smart_str_free(&buf);
297+
zend_throw_exception(php_json_exception_ce, php_json_get_error_msg(encoder.error_code), encoder.error_code);
298+
RETURN_FALSE;
299+
}
251300
}
252301

253302
smart_str_0(&buf); /* copy? */
@@ -277,10 +326,16 @@ static PHP_FUNCTION(json_decode)
277326
Z_PARAM_LONG(options)
278327
ZEND_PARSE_PARAMETERS_END();
279328

280-
JSON_G(error_code) = PHP_JSON_ERROR_NONE;
329+
if (!(options & PHP_JSON_THROW_ON_ERROR)) {
330+
JSON_G(error_code) = PHP_JSON_ERROR_NONE;
331+
}
281332

282333
if (!str_len) {
283-
JSON_G(error_code) = PHP_JSON_ERROR_SYNTAX;
334+
if (!(options & PHP_JSON_THROW_ON_ERROR)) {
335+
JSON_G(error_code) = PHP_JSON_ERROR_SYNTAX;
336+
} else {
337+
zend_throw_exception(php_json_exception_ce, php_json_get_error_msg(PHP_JSON_ERROR_SYNTAX), PHP_JSON_ERROR_SYNTAX);
338+
}
284339
RETURN_NULL();
285340
}
286341

@@ -327,33 +382,7 @@ static PHP_FUNCTION(json_last_error_msg)
327382
return;
328383
}
329384

330-
switch(JSON_G(error_code)) {
331-
case PHP_JSON_ERROR_NONE:
332-
RETURN_STRING("No error");
333-
case PHP_JSON_ERROR_DEPTH:
334-
RETURN_STRING("Maximum stack depth exceeded");
335-
case PHP_JSON_ERROR_STATE_MISMATCH:
336-
RETURN_STRING("State mismatch (invalid or malformed JSON)");
337-
case PHP_JSON_ERROR_CTRL_CHAR:
338-
RETURN_STRING("Control character error, possibly incorrectly encoded");
339-
case PHP_JSON_ERROR_SYNTAX:
340-
RETURN_STRING("Syntax error");
341-
case PHP_JSON_ERROR_UTF8:
342-
RETURN_STRING("Malformed UTF-8 characters, possibly incorrectly encoded");
343-
case PHP_JSON_ERROR_RECURSION:
344-
RETURN_STRING("Recursion detected");
345-
case PHP_JSON_ERROR_INF_OR_NAN:
346-
RETURN_STRING("Inf and NaN cannot be JSON encoded");
347-
case PHP_JSON_ERROR_UNSUPPORTED_TYPE:
348-
RETURN_STRING("Type is not supported");
349-
case PHP_JSON_ERROR_INVALID_PROPERTY_NAME:
350-
RETURN_STRING("The decoded property name is invalid");
351-
case PHP_JSON_ERROR_UTF16:
352-
RETURN_STRING("Single unpaired UTF-16 surrogate in unicode escape");
353-
default:
354-
RETURN_STRING("Unknown error");
355-
}
356-
385+
RETURN_STRING(php_json_get_error_msg(JSON_G(error_code)));
357386
}
358387
/* }}} */
359388

ext/json/php_json.h

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ typedef enum {
7474
/* json_decode() and json_encode() common options */
7575
#define PHP_JSON_INVALID_UTF8_IGNORE (1<<20)
7676
#define PHP_JSON_INVALID_UTF8_SUBSTITUTE (1<<21)
77+
#define PHP_JSON_THROW_ON_ERROR (1<<22)
7778

7879
/* Internal flags */
7980
#define PHP_JSON_OUTPUT_ARRAY 0
+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
--TEST--
2+
Test json_decode() function : JSON_THROW_ON_ERROR flag
3+
--FILE--
4+
<?php
5+
6+
try {
7+
var_dump(json_decode("{", false, 512, JSON_THROW_ON_ERROR));
8+
} catch (JsonException $e) {
9+
var_dump($e);
10+
}
11+
12+
?>
13+
--EXPECTF--
14+
object(JsonException)#1 (7) {
15+
["message":protected]=>
16+
string(12) "Syntax error"
17+
["string":"Exception":private]=>
18+
string(0) ""
19+
["code":protected]=>
20+
int(4)
21+
["file":protected]=>
22+
string(%d) "%s"
23+
["line":protected]=>
24+
int(%d)
25+
["trace":"Exception":private]=>
26+
array(1) {
27+
[0]=>
28+
array(4) {
29+
["file"]=>
30+
string(%d) "%s"
31+
["line"]=>
32+
int(%d)
33+
["function"]=>
34+
string(11) "json_decode"
35+
["args"]=>
36+
array(4) {
37+
[0]=>
38+
string(1) "{"
39+
[1]=>
40+
bool(false)
41+
[2]=>
42+
int(512)
43+
[3]=>
44+
int(4194304)
45+
}
46+
}
47+
}
48+
["previous":"Exception":private]=>
49+
NULL
50+
}
+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
--TEST--
2+
Test json_encode() function : JSON_THROW_ON_ERROR flag
3+
--FILE--
4+
<?php
5+
6+
try {
7+
var_dump(json_encode("\x80", JSON_THROW_ON_ERROR));
8+
} catch (JsonException $e) {
9+
var_dump($e);
10+
}
11+
12+
// JSON_PARTIAL_OUTPUT_ON_ERROR is incompatible with exceptions
13+
// So it overrides it for the sake of working with wrappers that add the
14+
// JSON_THROW_ON_ERROR flag
15+
var_dump(json_encode("\x80", JSON_THROW_ON_ERROR | JSON_PARTIAL_OUTPUT_ON_ERROR));
16+
var_dump(json_last_error());
17+
var_dump(json_last_error_msg());
18+
19+
?>
20+
--EXPECTF--
21+
object(JsonException)#1 (7) {
22+
["message":protected]=>
23+
string(56) "Malformed UTF-8 characters, possibly incorrectly encoded"
24+
["string":"Exception":private]=>
25+
string(0) ""
26+
["code":protected]=>
27+
int(5)
28+
["file":protected]=>
29+
string(%d) "%s"
30+
["line":protected]=>
31+
int(%d)
32+
["trace":"Exception":private]=>
33+
array(1) {
34+
[0]=>
35+
array(4) {
36+
["file"]=>
37+
string(%d) "%s"
38+
["line"]=>
39+
int(%d)
40+
["function"]=>
41+
string(11) "json_encode"
42+
["args"]=>
43+
array(2) {
44+
[0]=>
45+
string(1) "%s"
46+
[1]=>
47+
int(4194304)
48+
}
49+
}
50+
}
51+
["previous":"Exception":private]=>
52+
NULL
53+
}
54+
string(4) "null"
55+
int(5)
56+
string(56) "Malformed UTF-8 characters, possibly incorrectly encoded"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
--TEST--
2+
JSON_THROW_ON_ERROR: global error flag untouched
3+
--FILE--
4+
<?php
5+
6+
var_dump(json_last_error());
7+
8+
// here we cause a different kind of error to the following errors, so that
9+
// we can be sure the global error state looking unchanged isn't coincidence
10+
json_decode("\xFF");
11+
12+
var_dump(json_last_error());
13+
14+
try {
15+
json_decode("", false, 512, JSON_THROW_ON_ERROR);
16+
} catch (JsonException $e) {
17+
echo "Caught JSON exception: ", $e->getCode(), PHP_EOL;
18+
}
19+
20+
var_dump(json_last_error());
21+
22+
try {
23+
json_decode("{", false, 512, JSON_THROW_ON_ERROR);
24+
} catch (JsonException $e) {
25+
echo "Caught JSON exception: ", $e->getCode(), PHP_EOL;
26+
}
27+
28+
var_dump(json_last_error());
29+
30+
31+
try {
32+
json_encode(NAN, JSON_THROW_ON_ERROR);
33+
} catch (JsonException $e) {
34+
echo "Caught JSON exception: ", $e->getCode(), PHP_EOL;
35+
}
36+
37+
var_dump(json_last_error());
38+
39+
?>
40+
--EXPECT--
41+
int(0)
42+
int(5)
43+
Caught JSON exception: 4
44+
int(5)
45+
Caught JSON exception: 4
46+
int(5)
47+
Caught JSON exception: 7
48+
int(5)

0 commit comments

Comments
 (0)