From 70bddf2aad4e39779f833a2a9867ab7e9da97b85 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 24 Jul 2026 20:27:08 +0000 Subject: [PATCH 1/2] Add native JSONC support to ext/json Introduce two new flags accepted by json_decode() and json_validate(): - JSON_ALLOW_COMMENTS: treats // single-line comments (terminated by newline or end of input) and /* ... */ block comments as whitespace anywhere whitespace is allowed. Block comments do not nest; an unterminated /* is reported as JSON_ERROR_SYNTAX at the comment opener. Newlines inside block comments advance the line counter so error locations stay accurate. Comment-like sequences inside strings are untouched. - JSON_ALLOW_TRAILING_COMMAS: permits exactly one trailing comma after the last object member or array element ([1,] and {"a":1,}), while [,], {,}, [1,,2] and [1,,] remain syntax errors. Comments are handled in the re2c scanner (rules match unconditionally, the flag is checked in the action so the flag-off error is byte identical to the previous behavior), trailing commas via new "member ','" / "element ','" productions in the bison grammar with the flag checked at reduce time, keeping the grammar conflict-free and the flag-off error code and line:column identical to before. The partially built container is freed explicitly on the YYERROR path since bison does not run destructors for the RHS of the failing rule. Using both flags together makes json_decode() a superset of JSONC as used by VS Code configuration files, tsconfig.json and similar formats. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01TUEZ4aW8xZqzutZBAxX9UJ --- ext/json/json.c | 4 +- ext/json/json.stub.php | 11 ++ ext/json/json_arginfo.h | 4 +- ext/json/json_parser.y | 18 ++++ ext/json/json_scanner.re | 23 ++++ ext/json/php_json.h | 4 + ext/json/tests/json_decode_comments.phpt | 54 ++++++++++ .../tests/json_decode_comments_errors.phpt | 65 ++++++++++++ ...on_decode_comments_trailing_flags_off.phpt | 48 +++++++++ .../tests/json_decode_jsonc_combined.phpt | 59 +++++++++++ .../tests/json_decode_trailing_commas.phpt | 100 ++++++++++++++++++ ...son_last_error_msg_error_location_011.phpt | 74 +++++++++++++ ext/json/tests/json_validate_002.phpt | 28 ++++- 13 files changed, 487 insertions(+), 5 deletions(-) create mode 100644 ext/json/tests/json_decode_comments.phpt create mode 100644 ext/json/tests/json_decode_comments_errors.phpt create mode 100644 ext/json/tests/json_decode_comments_trailing_flags_off.phpt create mode 100644 ext/json/tests/json_decode_jsonc_combined.phpt create mode 100644 ext/json/tests/json_decode_trailing_commas.phpt create mode 100644 ext/json/tests/json_last_error_msg_error_location_011.phpt diff --git a/ext/json/json.c b/ext/json/json.c index 04a62f52152f..0ac944a54708 100644 --- a/ext/json/json.c +++ b/ext/json/json.c @@ -351,8 +351,8 @@ PHP_FUNCTION(json_validate) ZEND_PARSE_PARAMETERS_END(); - if ((options != 0) && (options != PHP_JSON_INVALID_UTF8_IGNORE)) { - zend_argument_value_error(3, "must be a valid flag (allowed flags: JSON_INVALID_UTF8_IGNORE)"); + if ((options & ~(PHP_JSON_INVALID_UTF8_IGNORE | PHP_JSON_ALLOW_COMMENTS | PHP_JSON_ALLOW_TRAILING_COMMAS)) != 0) { + zend_argument_value_error(3, "must be a valid flag (allowed flags: JSON_INVALID_UTF8_IGNORE, JSON_ALLOW_COMMENTS, JSON_ALLOW_TRAILING_COMMAS)"); RETURN_THROWS(); } diff --git a/ext/json/json.stub.php b/ext/json/json.stub.php index a805c3893dd1..737d341e9ca2 100644 --- a/ext/json/json.stub.php +++ b/ext/json/json.stub.php @@ -90,6 +90,17 @@ */ const JSON_THROW_ON_ERROR = UNKNOWN; +/** + * @var int + * @cvalue PHP_JSON_ALLOW_COMMENTS + */ +const JSON_ALLOW_COMMENTS = UNKNOWN; +/** + * @var int + * @cvalue PHP_JSON_ALLOW_TRAILING_COMMAS + */ +const JSON_ALLOW_TRAILING_COMMAS = UNKNOWN; + /** * @var int * @cvalue PHP_JSON_ERROR_NONE diff --git a/ext/json/json_arginfo.h b/ext/json/json_arginfo.h index 87ba9cce3afd..906491686cbd 100644 --- a/ext/json/json_arginfo.h +++ b/ext/json/json_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit json.stub.php instead. - * Stub hash: 0ceb50047401c4b9e878c09cc518eacc274f7fff */ + * Stub hash: 405dc5ea03cb0ed560c3717cad38203b2f55d870 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_json_encode, 0, 1, MAY_BE_STRING|MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) @@ -68,6 +68,8 @@ static void register_json_symbols(int module_number) REGISTER_LONG_CONSTANT("JSON_INVALID_UTF8_IGNORE", PHP_JSON_INVALID_UTF8_IGNORE, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("JSON_INVALID_UTF8_SUBSTITUTE", PHP_JSON_INVALID_UTF8_SUBSTITUTE, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("JSON_THROW_ON_ERROR", PHP_JSON_THROW_ON_ERROR, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("JSON_ALLOW_COMMENTS", PHP_JSON_ALLOW_COMMENTS, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("JSON_ALLOW_TRAILING_COMMAS", PHP_JSON_ALLOW_TRAILING_COMMAS, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("JSON_ERROR_NONE", PHP_JSON_ERROR_NONE, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("JSON_ERROR_DEPTH", PHP_JSON_ERROR_DEPTH, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("JSON_ERROR_STATE_MISMATCH", PHP_JSON_ERROR_STATE_MISMATCH, CONST_PERSISTENT); diff --git a/ext/json/json_parser.y b/ext/json/json_parser.y index 0fd3e2c4e364..b1e151c05cdc 100644 --- a/ext/json/json_parser.y +++ b/ext/json/json_parser.y @@ -119,6 +119,15 @@ members: } } | member + | member ',' + { + if (!(parser->scanner.options & PHP_JSON_ALLOW_TRAILING_COMMAS)) { + parser->scanner.errcode = PHP_JSON_ERROR_SYNTAX; + zval_ptr_dtor_nogc(&$1); + YYERROR; + } + ZVAL_COPY_VALUE(&$$, &$1); + } ; member: @@ -175,6 +184,15 @@ elements: } } | element + | element ',' + { + if (!(parser->scanner.options & PHP_JSON_ALLOW_TRAILING_COMMAS)) { + parser->scanner.errcode = PHP_JSON_ERROR_SYNTAX; + zval_ptr_dtor_nogc(&$1); + YYERROR; + } + ZVAL_COPY_VALUE(&$$, &$1); + } ; element: diff --git a/ext/json/json_scanner.re b/ext/json/json_scanner.re index be62875a00e0..8a499857314c 100644 --- a/ext/json/json_scanner.re +++ b/ext/json/json_scanner.re @@ -123,6 +123,8 @@ std: EXP = ( INT | FLOAT ) [eE] [+-]? DIGIT+ ; NL = "\r"? "\n" ; WS = [ \t\r]+ ; + CMT_SL = "//" [^\n\x00]* ; + CMT_ML = "/*" ( [^*\x00] | ( "*"+ [^*/\x00] ) )* "*"+ "/" ; EOI = "\000"; CTRL = [\x00-\x1F] ; UTF8T = [\x80-\xBF] ; @@ -203,6 +205,27 @@ std: goto std; } WS { goto std; } + CMT_SL { + if (!(s->options & PHP_JSON_ALLOW_COMMENTS)) { + s->errcode = PHP_JSON_ERROR_SYNTAX; + return PHP_JSON_T_ERROR; + } + goto std; + } + CMT_ML { + php_json_ctype *p; + if (!(s->options & PHP_JSON_ALLOW_COMMENTS)) { + s->errcode = PHP_JSON_ERROR_SYNTAX; + return PHP_JSON_T_ERROR; + } + for (p = s->token; p < s->cursor; p++) { + if (*p == '\n') { + s->line++; + s->line_start = p + 1; + } + } + goto std; + } EOI { if (s->limit < s->cursor) { return PHP_JSON_T_EOI; diff --git a/ext/json/php_json.h b/ext/json/php_json.h index f20b20964a71..0145a0dfaa6c 100644 --- a/ext/json/php_json.h +++ b/ext/json/php_json.h @@ -89,6 +89,10 @@ static inline void php_json_error_details_clear(php_json_error_details *out) { #define PHP_JSON_INVALID_UTF8_SUBSTITUTE (1<<21) #define PHP_JSON_THROW_ON_ERROR (1<<22) +/* json_validate() and json_decode() common options */ +#define PHP_JSON_ALLOW_COMMENTS (1<<23) +#define PHP_JSON_ALLOW_TRAILING_COMMAS (1<<24) + /* default depth */ #define PHP_JSON_PARSER_DEFAULT_DEPTH 512 diff --git a/ext/json/tests/json_decode_comments.phpt b/ext/json/tests/json_decode_comments.phpt new file mode 100644 index 000000000000..f167cad139e1 --- /dev/null +++ b/ext/json/tests/json_decode_comments.phpt @@ -0,0 +1,54 @@ +--TEST-- +json_decode() with JSON_ALLOW_COMMENTS - valid comment usage +--FILE-- + +--EXPECT-- +int(1) +int(1) +int(1) +int(1) +int(1) +int(1) +int(1) +int(1) +array(2) { + [0]=> + int(1) + [1]=> + int(2) +} +array(2) { + ["a"]=> + int(1) + ["b"]=> + int(2) +} +string(16) "// not a comment" +string(19) "/* not a comment */" +array(0) { +} +bool(true) diff --git a/ext/json/tests/json_decode_comments_errors.phpt b/ext/json/tests/json_decode_comments_errors.phpt new file mode 100644 index 000000000000..1b2f4c80289d --- /dev/null +++ b/ext/json/tests/json_decode_comments_errors.phpt @@ -0,0 +1,65 @@ +--TEST-- +json_decode() with JSON_ALLOW_COMMENTS - malformed comments and other errors +--FILE-- +getCode(), " ", $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +NULL +4: Syntax error near location 1:1 +NULL +4: Syntax error near location 1:1 +NULL +4: Syntax error near location 2:2 +NULL +4: Syntax error near location 1:1 +NULL +4: Syntax error near location 1:1 +NULL +4: Syntax error near location 1:10 +NULL +4: Syntax error near location 1:1 +NULL +4: Syntax error near location 1:2 +NULL +4: Syntax error near location 1:4 +NULL +4: Syntax error near location 1:6 +NULL +4: Syntax error near location 2:1 +NULL +4: Syntax error near location 1:3 +NULL +4: Syntax error near location 1:7 +JsonException: 4 Syntax error near location 1:1 diff --git a/ext/json/tests/json_decode_comments_trailing_flags_off.phpt b/ext/json/tests/json_decode_comments_trailing_flags_off.phpt new file mode 100644 index 000000000000..6c948df4f455 --- /dev/null +++ b/ext/json/tests/json_decode_comments_trailing_flags_off.phpt @@ -0,0 +1,48 @@ +--TEST-- +JSONC syntax is rejected when JSON_ALLOW_COMMENTS / JSON_ALLOW_TRAILING_COMMAS are not set +--FILE-- + +--EXPECT-- +NULL +4: Syntax error near location 1:1 +NULL +4: Syntax error near location 1:1 +NULL +4: Syntax error near location 1:1 +NULL +4: Syntax error near location 1:8 +NULL +4: Syntax error near location 1:4 +NULL +4: Syntax error near location 1:8 +NULL +4: Syntax error near location 1:7 +NULL +4: Syntax error near location 1:4 +NULL +4: Syntax error near location 1:5 +bool(false) +bool(false) diff --git a/ext/json/tests/json_decode_jsonc_combined.phpt b/ext/json/tests/json_decode_jsonc_combined.phpt new file mode 100644 index 000000000000..5a704e21f6cb --- /dev/null +++ b/ext/json/tests/json_decode_jsonc_combined.phpt @@ -0,0 +1,59 @@ +--TEST-- +json_decode() with JSON_ALLOW_COMMENTS | JSON_ALLOW_TRAILING_COMMAS - full JSONC documents +--FILE-- + +--EXPECT-- +array(4) { + ["host"]=> + string(9) "localhost" + ["port"]=> + int(8080) + ["features"]=> + array(2) { + [0]=> + string(5) "alpha" + [1]=> + string(4) "beta" + } + ["debug"]=> + bool(true) +} +bool(true) +array(1) { + [0]=> + int(1) +} +array(1) { + ["a"]=> + int(1) +} +NULL +4: Syntax error near location 2:1 diff --git a/ext/json/tests/json_decode_trailing_commas.phpt b/ext/json/tests/json_decode_trailing_commas.phpt new file mode 100644 index 000000000000..4f836822509a --- /dev/null +++ b/ext/json/tests/json_decode_trailing_commas.phpt @@ -0,0 +1,100 @@ +--TEST-- +json_decode() with JSON_ALLOW_TRAILING_COMMAS +--FILE-- + +--EXPECTF-- +array(1) { + [0]=> + int(1) +} +0: No error +array(1) { + [0]=> + int(1) +} +0: No error +array(1) { + [0]=> + int(1) +} +0: No error +array(1) { + [0]=> + string(1) "a" +} +0: No error +array(1) { + ["a"]=> + int(1) +} +0: No error +object(stdClass)#%d (1) { + ["a"]=> + int(1) +} +0: No error +array(1) { + [0]=> + array(1) { + [0]=> + int(1) + } +} +0: No error +array(1) { + ["a"]=> + array(1) { + [0]=> + int(1) + } +} +0: No error +NULL +4: Syntax error near location 1:2 +NULL +4: Syntax error near location 1:2 +NULL +4: Syntax error near location 1:2 +NULL +4: Syntax error near location 1:2 +NULL +4: Syntax error near location 1:4 +NULL +4: Syntax error near location 1:4 +NULL +4: Syntax error near location 1:8 +NULL +2: State mismatch (invalid or malformed JSON) near location 1:8 +bool(true) +bool(true) +bool(false) diff --git a/ext/json/tests/json_last_error_msg_error_location_011.phpt b/ext/json/tests/json_last_error_msg_error_location_011.phpt new file mode 100644 index 000000000000..a517aae8608f --- /dev/null +++ b/ext/json/tests/json_last_error_msg_error_location_011.phpt @@ -0,0 +1,74 @@ +--TEST-- +json_last_error_msg() - Error location reporting with JSONC comments +--FILE-- + +--EXPECT-- +Error on line 4, column 20: +bool(false) +int(4) +string(31) "Syntax error near location 4:20" + +Error on line 3, column 4: +bool(false) +int(4) +string(30) "Syntax error near location 3:4" + +Error on line 1, column 12: +bool(false) +int(4) +string(31) "Syntax error near location 1:12" + +Error on line 1, column 22: +bool(false) +int(4) +string(31) "Syntax error near location 1:22" + +Error on line 3, column 3: +bool(false) +int(4) +string(30) "Syntax error near location 3:3" + +Error on line 1, column 13: +bool(false) +int(4) +string(31) "Syntax error near location 1:13" diff --git a/ext/json/tests/json_validate_002.phpt b/ext/json/tests/json_validate_002.phpt index 423564c4ad7c..5ab1adca7c2b 100644 --- a/ext/json/tests/json_validate_002.phpt +++ b/ext/json/tests/json_validate_002.phpt @@ -15,6 +15,12 @@ json_validate_trycatchdump("-", 512, JSON_BIGINT_AS_STRING); json_validate_trycatchdump("-", 512, JSON_BIGINT_AS_STRING | JSON_INVALID_UTF8_IGNORE); json_validate_trycatchdump("-", 512, JSON_INVALID_UTF8_IGNORE); json_validate_trycatchdump("{}", 512, JSON_INVALID_UTF8_IGNORE); +json_validate_trycatchdump("[1,] // ok", 512, JSON_ALLOW_COMMENTS | JSON_ALLOW_TRAILING_COMMAS); +json_validate_trycatchdump("// c\n{}", 512, JSON_ALLOW_COMMENTS); +json_validate_trycatchdump("[1,]", 512, JSON_ALLOW_TRAILING_COMMAS); +json_validate_trycatchdump("[1,]", 512, JSON_ALLOW_COMMENTS); +json_validate_trycatchdump("// c\n{}", 512, JSON_ALLOW_TRAILING_COMMAS); +json_validate_trycatchdump("{}", 512, JSON_ALLOW_COMMENTS | JSON_BIGINT_AS_STRING); ?> --EXPECTF-- @@ -36,10 +42,10 @@ string(8) "No error" Error: 0 json_validate(): Argument #2 ($depth) must be greater than 0 int(0) string(8) "No error" -Error: 0 json_validate(): Argument #3 ($flags) must be a valid flag (allowed flags: JSON_INVALID_UTF8_IGNORE) +Error: 0 json_validate(): Argument #3 ($flags) must be a valid flag (allowed flags: JSON_INVALID_UTF8_IGNORE, JSON_ALLOW_COMMENTS, JSON_ALLOW_TRAILING_COMMAS) int(0) string(8) "No error" -Error: 0 json_validate(): Argument #3 ($flags) must be a valid flag (allowed flags: JSON_INVALID_UTF8_IGNORE) +Error: 0 json_validate(): Argument #3 ($flags) must be a valid flag (allowed flags: JSON_INVALID_UTF8_IGNORE, JSON_ALLOW_COMMENTS, JSON_ALLOW_TRAILING_COMMAS) int(0) string(8) "No error" bool(false) @@ -48,3 +54,21 @@ string(30) "Syntax error near location 1:1" bool(true) int(0) string(8) "No error" +bool(true) +int(0) +string(8) "No error" +bool(true) +int(0) +string(8) "No error" +bool(true) +int(0) +string(8) "No error" +bool(false) +int(4) +string(30) "Syntax error near location 1:4" +bool(false) +int(4) +string(30) "Syntax error near location 1:1" +Error: 0 json_validate(): Argument #3 ($flags) must be a valid flag (allowed flags: JSON_INVALID_UTF8_IGNORE, JSON_ALLOW_COMMENTS, JSON_ALLOW_TRAILING_COMMAS) +int(4) +string(30) "Syntax error near location 1:1" From 3b9f7c45d88ddc355c033f8740508a159479cb5a Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 24 Jul 2026 20:49:37 +0000 Subject: [PATCH 2/2] Fix error-code regression in trailing-comma rules and harden tests The new "member ','" / "element ','" reduces are installed as bison default actions in their states, so a scanner error token arriving right after a comma (control character, invalid UTF-8, unpaired UTF-16 surrogate) executed the semantic action before the parse error was detected, overwriting the scanner's specific error code with JSON_ERROR_SYNTAX when the flag was unset. Preserve an already-set errcode, mirroring php_json_yyerror(), so json_decode("[1,\x01]") keeps reporting JSON_ERROR_CTRL_CHAR exactly as before this feature. Also terminate // comments at a bare CR in addition to LF, matching how the scanner already treats CR ("\r"? "\n" line terminators, CR as whitespace) and how JSONC editors treat line comments. Extend the tests to pin: the preserved error codes above, empty containers whose only content is a comment, CRLF and bare-CR comment termination, comment bodies being opaque to UTF-8 validation, a comment in colon position, [1,} state mismatch, a comment between an opener and a lone comma, JSON_THROW_ON_ERROR with trailing commas, and CRLF line counting inside block comments. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01TUEZ4aW8xZqzutZBAxX9UJ --- ext/json/json_parser.y | 8 +++-- ext/json/json_scanner.re | 2 +- ext/json/tests/json_decode_comments.phpt | 30 +++++++++++++++++++ .../tests/json_decode_comments_errors.phpt | 4 +++ ...on_decode_comments_trailing_flags_off.phpt | 13 ++++++++ .../tests/json_decode_trailing_commas.phpt | 23 ++++++++++++++ ...son_last_error_msg_error_location_011.phpt | 9 ++++++ 7 files changed, 86 insertions(+), 3 deletions(-) diff --git a/ext/json/json_parser.y b/ext/json/json_parser.y index b1e151c05cdc..742f7141042f 100644 --- a/ext/json/json_parser.y +++ b/ext/json/json_parser.y @@ -122,7 +122,9 @@ members: | member ',' { if (!(parser->scanner.options & PHP_JSON_ALLOW_TRAILING_COMMAS)) { - parser->scanner.errcode = PHP_JSON_ERROR_SYNTAX; + if (!parser->scanner.errcode) { + parser->scanner.errcode = PHP_JSON_ERROR_SYNTAX; + } zval_ptr_dtor_nogc(&$1); YYERROR; } @@ -187,7 +189,9 @@ elements: | element ',' { if (!(parser->scanner.options & PHP_JSON_ALLOW_TRAILING_COMMAS)) { - parser->scanner.errcode = PHP_JSON_ERROR_SYNTAX; + if (!parser->scanner.errcode) { + parser->scanner.errcode = PHP_JSON_ERROR_SYNTAX; + } zval_ptr_dtor_nogc(&$1); YYERROR; } diff --git a/ext/json/json_scanner.re b/ext/json/json_scanner.re index 8a499857314c..6821b4140d58 100644 --- a/ext/json/json_scanner.re +++ b/ext/json/json_scanner.re @@ -123,7 +123,7 @@ std: EXP = ( INT | FLOAT ) [eE] [+-]? DIGIT+ ; NL = "\r"? "\n" ; WS = [ \t\r]+ ; - CMT_SL = "//" [^\n\x00]* ; + CMT_SL = "//" [^\r\n\x00]* ; CMT_ML = "/*" ( [^*\x00] | ( "*"+ [^*/\x00] ) )* "*"+ "/" ; EOI = "\000"; CTRL = [\x00-\x1F] ; diff --git a/ext/json/tests/json_decode_comments.phpt b/ext/json/tests/json_decode_comments.phpt index f167cad139e1..7c47ffd8687f 100644 --- a/ext/json/tests/json_decode_comments.phpt +++ b/ext/json/tests/json_decode_comments.phpt @@ -23,7 +23,19 @@ decode_dump("{\"a\" /*x*/ : /*y*/ 1 /*z*/, // t\n \"b\":2}"); decode_dump('"// not a comment"'); decode_dump('"/* not a comment */"'); decode_dump("//only\n// comments\n[]"); +// empty containers whose only content is a comment +decode_dump("[/*x*/]"); +decode_dump("{/*x*/}"); +decode_dump("[ //x\n]"); +decode_dump("{ //x\n}"); +// CRLF and bare CR both terminate a line comment +decode_dump("// crlf\r\n[1, 2]"); +decode_dump("// bare cr\r1"); +// comment bodies are opaque: bytes that are not valid UTF-8 are skipped +decode_dump("/* \xFF */1"); var_dump(json_validate("// c\n{\"a\": [1, 2]}", 512, JSON_ALLOW_COMMENTS)); +var_dump(json_validate("{/*x*/}", 512, JSON_ALLOW_COMMENTS)); +var_dump(json_validate("/* \xFF */{}", 512, JSON_ALLOW_COMMENTS)); ?> --EXPECT-- @@ -51,4 +63,22 @@ string(16) "// not a comment" string(19) "/* not a comment */" array(0) { } +array(0) { +} +array(0) { +} +array(0) { +} +array(0) { +} +array(2) { + [0]=> + int(1) + [1]=> + int(2) +} +int(1) +int(1) +bool(true) +bool(true) bool(true) diff --git a/ext/json/tests/json_decode_comments_errors.phpt b/ext/json/tests/json_decode_comments_errors.phpt index 1b2f4c80289d..7cb8cd224107 100644 --- a/ext/json/tests/json_decode_comments_errors.phpt +++ b/ext/json/tests/json_decode_comments_errors.phpt @@ -27,6 +27,8 @@ decode_dump("// x\n"); // comments separate tokens but do not join values decode_dump("1 2", 0); decode_dump("1/*c*/2"); +// a comment does not replace a required colon +decode_dump("{\"a\" /*c*/ 1}"); try { json_decode("/*", true, 512, JSON_ALLOW_COMMENTS | JSON_THROW_ON_ERROR); @@ -62,4 +64,6 @@ NULL 4: Syntax error near location 1:3 NULL 4: Syntax error near location 1:7 +NULL +4: Syntax error near location 1:12 JsonException: 4 Syntax error near location 1:1 diff --git a/ext/json/tests/json_decode_comments_trailing_flags_off.phpt b/ext/json/tests/json_decode_comments_trailing_flags_off.phpt index 6c948df4f455..32b86762a1b5 100644 --- a/ext/json/tests/json_decode_comments_trailing_flags_off.phpt +++ b/ext/json/tests/json_decode_comments_trailing_flags_off.phpt @@ -20,6 +20,11 @@ decode_dump("[1, 2,]"); // one flag does not enable the other relaxation decode_dump("[1,]", JSON_ALLOW_COMMENTS); decode_dump("[1, /*c*/ 2]", JSON_ALLOW_TRAILING_COMMAS); +// scanner errors right after a comma keep their specific error codes +decode_dump("[1,\x01]"); +decode_dump("[1,\x80]"); +decode_dump("[1,\"\\ud834\"]"); +decode_dump("{\"a\":1,\x01}"); // validate behaves identically var_dump(json_validate("// x\n1", 512, JSON_ALLOW_TRAILING_COMMAS)); var_dump(json_validate("[1,]", 512, JSON_ALLOW_COMMENTS)); @@ -44,5 +49,13 @@ NULL 4: Syntax error near location 1:4 NULL 4: Syntax error near location 1:5 +NULL +3: Control character error, possibly incorrectly encoded near location 1:4 +NULL +5: Malformed UTF-8 characters, possibly incorrectly encoded near location 1:4 +NULL +10: Single unpaired UTF-16 surrogate in unicode escape near location 1:4 +NULL +3: Control character error, possibly incorrectly encoded near location 1:8 bool(false) bool(false) diff --git a/ext/json/tests/json_decode_trailing_commas.phpt b/ext/json/tests/json_decode_trailing_commas.phpt index 4f836822509a..6268c5b6930d 100644 --- a/ext/json/tests/json_decode_trailing_commas.phpt +++ b/ext/json/tests/json_decode_trailing_commas.phpt @@ -26,6 +26,20 @@ decode_dump("[1,,2]"); decode_dump("{\"a\":1,,}"); // mismatched closer after a trailing comma decode_dump("{\"a\":1,]"); +decode_dump("[1,}"); +// a comment between the opener and a lone comma does not make it valid +decode_dump("[/*c*/,]", JSON_ALLOW_COMMENTS | JSON_ALLOW_TRAILING_COMMAS); +// trailing commas and JSON_THROW_ON_ERROR +try { + var_dump(json_decode("[1,]", true, 512, JSON_ALLOW_TRAILING_COMMAS | JSON_THROW_ON_ERROR)); +} catch (JsonException $e) { + echo "JsonException: ", $e->getCode(), " ", $e->getMessage(), "\n"; +} +try { + json_decode("[1,,]", true, 512, JSON_ALLOW_TRAILING_COMMAS | JSON_THROW_ON_ERROR); +} catch (JsonException $e) { + echo "JsonException: ", $e->getCode(), " ", $e->getMessage(), "\n"; +} // validate mirrors decode var_dump(json_validate("[1,]", 512, JSON_ALLOW_TRAILING_COMMAS)); var_dump(json_validate("{\"a\":1,}", 512, JSON_ALLOW_TRAILING_COMMAS)); @@ -95,6 +109,15 @@ NULL 4: Syntax error near location 1:8 NULL 2: State mismatch (invalid or malformed JSON) near location 1:8 +NULL +2: State mismatch (invalid or malformed JSON) near location 1:4 +NULL +4: Syntax error near location 1:7 +array(1) { + [0]=> + int(1) +} +JsonException: 4 Syntax error near location 1:4 bool(true) bool(true) bool(false) diff --git a/ext/json/tests/json_last_error_msg_error_location_011.phpt b/ext/json/tests/json_last_error_msg_error_location_011.phpt index a517aae8608f..91af2aa5038a 100644 --- a/ext/json/tests/json_last_error_msg_error_location_011.phpt +++ b/ext/json/tests/json_last_error_msg_error_location_011.phpt @@ -41,6 +41,10 @@ json_validate_trycatchdump($json, 512, JSON_ALLOW_COMMENTS); echo "\nError on line 1, column 13:\n"; json_validate_trycatchdump("[1, /* x */ ,]", 512, JSON_ALLOW_COMMENTS | JSON_ALLOW_TRAILING_COMMAS); +// CRLF line endings inside a block comment count as one line each. +echo "\nError on line 3, column 8:\n"; +json_validate_trycatchdump("[\r\n/* x\r\ny */ 1 2]", 512, JSON_ALLOW_COMMENTS); + ?> --EXPECT-- Error on line 4, column 20: @@ -72,3 +76,8 @@ Error on line 1, column 13: bool(false) int(4) string(31) "Syntax error near location 1:13" + +Error on line 3, column 8: +bool(false) +int(4) +string(30) "Syntax error near location 3:8"