Skip to content

Commit 84fe2cc

Browse files
committed
Improve json_encode error handling
json_encode() now returns bool(false) for all possible errors, throws the respective warning and also sets the respective json_last_error() error code. Three new error codes have been added: * JSON_ERROR_RECURSION * JSON_ERROR_INF_OR_NAN * JSON_ERROR_UNSUPPORTED_TYPE To get a partial JSON output instead of bool(false) the option JSON_PARTIAL_OUTPUT_ON_ERROR can be specified. In this case the invalid segments will be replaced either by null (for recursion, unsupported type and invalid JSON) or 0 (for Inf and NaN). The warning for invalid UTF-8 stays intact and is thrown also with display_errors = On. If this behavior is undesired this can be remedied later.
1 parent cc90ac5 commit 84fe2cc

File tree

9 files changed

+110
-13
lines changed

9 files changed

+110
-13
lines changed

ext/json/JSON_parser.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ enum error_codes {
2424
PHP_JSON_ERROR_STATE_MISMATCH,
2525
PHP_JSON_ERROR_CTRL_CHAR,
2626
PHP_JSON_ERROR_SYNTAX,
27-
PHP_JSON_ERROR_UTF8
27+
PHP_JSON_ERROR_UTF8,
28+
PHP_JSON_ERROR_RECURSION,
29+
PHP_JSON_ERROR_INF_OR_NAN,
30+
PHP_JSON_ERROR_UNSUPPORTED_TYPE
2831
};
2932

3033
extern JSON_parser new_JSON_parser(int depth);

ext/json/json.c

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ static PHP_MINIT_FUNCTION(json)
8181
REGISTER_LONG_CONSTANT("JSON_ERROR_CTRL_CHAR", PHP_JSON_ERROR_CTRL_CHAR, CONST_CS | CONST_PERSISTENT);
8282
REGISTER_LONG_CONSTANT("JSON_ERROR_SYNTAX", PHP_JSON_ERROR_SYNTAX, CONST_CS | CONST_PERSISTENT);
8383
REGISTER_LONG_CONSTANT("JSON_ERROR_UTF8", PHP_JSON_ERROR_UTF8, CONST_CS | CONST_PERSISTENT);
84+
REGISTER_LONG_CONSTANT("JSON_ERROR_RECURSION", PHP_JSON_ERROR_RECURSION, CONST_CS | CONST_PERSISTENT);
85+
REGISTER_LONG_CONSTANT("JSON_ERROR_INF_OR_NAN", PHP_JSON_ERROR_INF_OR_NAN, CONST_CS | CONST_PERSISTENT);
86+
REGISTER_LONG_CONSTANT("JSON_ERROR_UNSUPPORTED_TYPE", PHP_JSON_ERROR_UNSUPPORTED_TYPE, CONST_CS | CONST_PERSISTENT);
8487

8588
return SUCCESS;
8689
}
@@ -181,6 +184,7 @@ static void json_encode_array(smart_str *buf, zval **val, int options TSRMLS_DC)
181184
}
182185

183186
if (myht && myht->nApplyCount > 1) {
187+
JSON_G(error_code) = PHP_JSON_ERROR_RECURSION;
184188
php_error_docref(NULL TSRMLS_CC, E_WARNING, "recursion detected");
185189
smart_str_appendl(buf, "null", 4);
186190
return;
@@ -303,7 +307,8 @@ static void json_escape_string(smart_str *buf, char *s, int len, int options TSR
303307
smart_str_appendl(buf, tmp, l);
304308
efree(tmp);
305309
} else {
306-
php_error_docref(NULL TSRMLS_CC, E_WARNING, "double %.9g does not conform to the JSON spec, encoded as 0", d);
310+
JSON_G(error_code) = PHP_JSON_ERROR_INF_OR_NAN;
311+
php_error_docref(NULL TSRMLS_CC, E_WARNING, "double %.9g does not conform to the JSON spec", d);
307312
smart_str_appendc(buf, '0');
308313
}
309314
}
@@ -460,7 +465,8 @@ PHP_JSON_API void php_json_encode(smart_str *buf, zval *val, int options TSRMLS_
460465
smart_str_appendl(buf, d, len);
461466
efree(d);
462467
} else {
463-
php_error_docref(NULL TSRMLS_CC, E_WARNING, "double %.9g does not conform to the JSON spec, encoded as 0", dbl);
468+
JSON_G(error_code) = PHP_JSON_ERROR_INF_OR_NAN;
469+
php_error_docref(NULL TSRMLS_CC, E_WARNING, "double %.9g does not conform to the JSON spec", dbl);
464470
smart_str_appendc(buf, '0');
465471
}
466472
}
@@ -476,7 +482,8 @@ PHP_JSON_API void php_json_encode(smart_str *buf, zval *val, int options TSRMLS_
476482
break;
477483

478484
default:
479-
php_error_docref(NULL TSRMLS_CC, E_WARNING, "type is unsupported, encoded as null");
485+
JSON_G(error_code) = PHP_JSON_ERROR_UNSUPPORTED_TYPE;
486+
php_error_docref(NULL TSRMLS_CC, E_WARNING, "type is unsupported");
480487
smart_str_appendl(buf, "null", 4);
481488
break;
482489
}
@@ -570,7 +577,7 @@ static PHP_FUNCTION(json_encode)
570577

571578
php_json_encode(&buf, parameter, options TSRMLS_CC);
572579

573-
if (JSON_G(error_code) != PHP_JSON_ERROR_NONE && options ^ PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
580+
if (JSON_G(error_code) != PHP_JSON_ERROR_NONE && !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
574581
ZVAL_FALSE(return_value);
575582
} else {
576583
ZVAL_STRINGL(return_value, buf.c, buf.len, 1);

ext/json/tests/003.phpt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ $a = array();
99
$a[] = &$a;
1010

1111
var_dump($a);
12+
1213
var_dump(json_encode($a));
14+
var_dump(json_last_error());
1315

14-
/* Break circular data structure to prevent memory leaks */
15-
unset($a[0]);
16+
var_dump(json_encode($a, JSON_PARTIAL_OUTPUT_ON_ERROR));
17+
var_dump(json_last_error());
1618

1719
echo "Done\n";
1820
?>
@@ -25,6 +27,11 @@ array(1) {
2527
}
2628
}
2729

30+
Warning: json_encode(): recursion detected in %s on line %d
31+
bool(false)
32+
int(6)
33+
2834
Warning: json_encode(): recursion detected in %s on line %d
2935
string(8) "[[null]]"
36+
int(6)
3037
Done

ext/json/tests/004.phpt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@ $a = new stdclass;
99
$a->prop = $a;
1010

1111
var_dump($a);
12+
1213
var_dump(json_encode($a));
14+
var_dump(json_last_error());
15+
16+
var_dump(json_encode($a, JSON_PARTIAL_OUTPUT_ON_ERROR));
17+
var_dump(json_last_error());
1318

1419
echo "Done\n";
1520
?>
@@ -19,6 +24,11 @@ object(stdClass)#%d (1) {
1924
*RECURSION*
2025
}
2126

27+
Warning: json_encode(): recursion detected in %s on line %d
28+
bool(false)
29+
int(6)
30+
2231
Warning: json_encode(): recursion detected in %s on line %d
2332
string(22) "{"prop":{"prop":null}}"
33+
int(6)
2434
Done

ext/json/tests/inf_nan_error.phpt

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
--TEST--
2+
An error is thrown when INF or NaN are encoded
3+
--FILE--
4+
<?php
5+
6+
$inf = INF;
7+
8+
var_dump($inf);
9+
10+
var_dump(json_encode($inf));
11+
var_dump(json_last_error());
12+
13+
var_dump(json_encode($inf, JSON_PARTIAL_OUTPUT_ON_ERROR));
14+
var_dump(json_last_error());
15+
16+
$nan = NAN;
17+
18+
var_dump($nan);
19+
20+
var_dump(json_encode($nan));
21+
var_dump(json_last_error());
22+
23+
var_dump(json_encode($nan, JSON_PARTIAL_OUTPUT_ON_ERROR));
24+
var_dump(json_last_error());
25+
?>
26+
--EXPECTF--
27+
float(INF)
28+
29+
Warning: json_encode(): double INF does not conform to the JSON spec in %s on line %d
30+
bool(false)
31+
int(7)
32+
33+
Warning: json_encode(): double INF does not conform to the JSON spec in %s on line %d
34+
string(1) "0"
35+
int(7)
36+
float(NAN)
37+
38+
Warning: json_encode(): double NAN does not conform to the JSON spec in %s on line %d
39+
bool(false)
40+
int(7)
41+
42+
Warning: json_encode(): double NAN does not conform to the JSON spec in %s on line %d
43+
string(1) "0"
44+
int(7)

ext/json/tests/json_encode_basic.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,8 @@ string(4) "null"
151151
string(4) "null"
152152
-- Iteration 26 --
153153

154-
Warning: json_encode(): type is unsupported, encoded as null in %s on line %d
155-
string(4) "null"
154+
Warning: json_encode(): type is unsupported in %s on line %d
155+
bool(false)
156156
-- Iteration 27 --
157157
string(82) "{"MyInt":99,"MyFloat":123.45,"MyBool":true,"MyNull":null,"MyString":"Hello World"}"
158158
===Done===

ext/json/tests/pass001.1.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,10 @@ $arr = json_decode($test, true);
9090
var_dump($arr);
9191

9292
echo "ENCODE: FROM OBJECT\n";
93-
$obj_enc = json_encode($obj);
93+
$obj_enc = json_encode($obj, JSON_PARTIAL_OUTPUT_ON_ERROR);
9494
echo $obj_enc . "\n";
9595
echo "ENCODE: FROM ARRAY\n";
96-
$arr_enc = json_encode($arr);
96+
$arr_enc = json_encode($arr, JSON_PARTIAL_OUTPUT_ON_ERROR);
9797
echo $arr_enc . "\n";
9898

9999
echo "DECODE AGAIN: AS OBJECT\n";

ext/json/tests/pass001.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,10 @@ $arr = json_decode($test, true);
7979
var_dump($arr);
8080

8181
echo "ENCODE: FROM OBJECT\n";
82-
$obj_enc = json_encode($obj);
82+
$obj_enc = json_encode($obj, JSON_PARTIAL_OUTPUT_ON_ERROR);
8383
echo $obj_enc . "\n";
8484
echo "ENCODE: FROM ARRAY\n";
85-
$arr_enc = json_encode($arr);
85+
$arr_enc = json_encode($arr, JSON_PARTIAL_OUTPUT_ON_ERROR);
8686
echo $arr_enc . "\n";
8787

8888
echo "DECODE AGAIN: AS OBJECT\n";
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--TEST--
2+
An error is thrown when an unsupported type is encoded
3+
--FILE--
4+
<?php
5+
6+
$resource = fopen(__FILE__, "r");
7+
8+
var_dump($resource);
9+
10+
var_dump(json_encode($resource));
11+
var_dump(json_last_error());
12+
13+
var_dump(json_encode($resource, JSON_PARTIAL_OUTPUT_ON_ERROR));
14+
var_dump(json_last_error());
15+
16+
?>
17+
--EXPECTF--
18+
resource(5) of type (stream)
19+
20+
Warning: json_encode(): type is unsupported in %s on line %d
21+
bool(false)
22+
int(8)
23+
24+
Warning: json_encode(): type is unsupported in %s on line %d
25+
string(4) "null"
26+
int(8)

0 commit comments

Comments
 (0)