Skip to content

Commit 3b9f7c4

Browse files
committed
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TUEZ4aW8xZqzutZBAxX9UJ
1 parent 70bddf2 commit 3b9f7c4

7 files changed

Lines changed: 86 additions & 3 deletions

ext/json/json_parser.y

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,9 @@ members:
122122
| member ','
123123
{
124124
if (!(parser->scanner.options & PHP_JSON_ALLOW_TRAILING_COMMAS)) {
125-
parser->scanner.errcode = PHP_JSON_ERROR_SYNTAX;
125+
if (!parser->scanner.errcode) {
126+
parser->scanner.errcode = PHP_JSON_ERROR_SYNTAX;
127+
}
126128
zval_ptr_dtor_nogc(&$1);
127129
YYERROR;
128130
}
@@ -187,7 +189,9 @@ elements:
187189
| element ','
188190
{
189191
if (!(parser->scanner.options & PHP_JSON_ALLOW_TRAILING_COMMAS)) {
190-
parser->scanner.errcode = PHP_JSON_ERROR_SYNTAX;
192+
if (!parser->scanner.errcode) {
193+
parser->scanner.errcode = PHP_JSON_ERROR_SYNTAX;
194+
}
191195
zval_ptr_dtor_nogc(&$1);
192196
YYERROR;
193197
}

ext/json/json_scanner.re

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ std:
123123
EXP = ( INT | FLOAT ) [eE] [+-]? DIGIT+ ;
124124
NL = "\r"? "\n" ;
125125
WS = [ \t\r]+ ;
126-
CMT_SL = "//" [^\n\x00]* ;
126+
CMT_SL = "//" [^\r\n\x00]* ;
127127
CMT_ML = "/*" ( [^*\x00] | ( "*"+ [^*/\x00] ) )* "*"+ "/" ;
128128
EOI = "\000";
129129
CTRL = [\x00-\x1F] ;

ext/json/tests/json_decode_comments.phpt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,19 @@ decode_dump("{\"a\" /*x*/ : /*y*/ 1 /*z*/, // t\n \"b\":2}");
2323
decode_dump('"// not a comment"');
2424
decode_dump('"/* not a comment */"');
2525
decode_dump("//only\n// comments\n[]");
26+
// empty containers whose only content is a comment
27+
decode_dump("[/*x*/]");
28+
decode_dump("{/*x*/}");
29+
decode_dump("[ //x\n]");
30+
decode_dump("{ //x\n}");
31+
// CRLF and bare CR both terminate a line comment
32+
decode_dump("// crlf\r\n[1, 2]");
33+
decode_dump("// bare cr\r1");
34+
// comment bodies are opaque: bytes that are not valid UTF-8 are skipped
35+
decode_dump("/* \xFF */1");
2636
var_dump(json_validate("// c\n{\"a\": [1, 2]}", 512, JSON_ALLOW_COMMENTS));
37+
var_dump(json_validate("{/*x*/}", 512, JSON_ALLOW_COMMENTS));
38+
var_dump(json_validate("/* \xFF */{}", 512, JSON_ALLOW_COMMENTS));
2739

2840
?>
2941
--EXPECT--
@@ -51,4 +63,22 @@ string(16) "// not a comment"
5163
string(19) "/* not a comment */"
5264
array(0) {
5365
}
66+
array(0) {
67+
}
68+
array(0) {
69+
}
70+
array(0) {
71+
}
72+
array(0) {
73+
}
74+
array(2) {
75+
[0]=>
76+
int(1)
77+
[1]=>
78+
int(2)
79+
}
80+
int(1)
81+
int(1)
82+
bool(true)
83+
bool(true)
5484
bool(true)

ext/json/tests/json_decode_comments_errors.phpt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ decode_dump("// x\n");
2727
// comments separate tokens but do not join values
2828
decode_dump("1 2", 0);
2929
decode_dump("1/*c*/2");
30+
// a comment does not replace a required colon
31+
decode_dump("{\"a\" /*c*/ 1}");
3032

3133
try {
3234
json_decode("/*", true, 512, JSON_ALLOW_COMMENTS | JSON_THROW_ON_ERROR);
@@ -62,4 +64,6 @@ NULL
6264
4: Syntax error near location 1:3
6365
NULL
6466
4: Syntax error near location 1:7
67+
NULL
68+
4: Syntax error near location 1:12
6569
JsonException: 4 Syntax error near location 1:1

ext/json/tests/json_decode_comments_trailing_flags_off.phpt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ decode_dump("[1, 2,]");
2020
// one flag does not enable the other relaxation
2121
decode_dump("[1,]", JSON_ALLOW_COMMENTS);
2222
decode_dump("[1, /*c*/ 2]", JSON_ALLOW_TRAILING_COMMAS);
23+
// scanner errors right after a comma keep their specific error codes
24+
decode_dump("[1,\x01]");
25+
decode_dump("[1,\x80]");
26+
decode_dump("[1,\"\\ud834\"]");
27+
decode_dump("{\"a\":1,\x01}");
2328
// validate behaves identically
2429
var_dump(json_validate("// x\n1", 512, JSON_ALLOW_TRAILING_COMMAS));
2530
var_dump(json_validate("[1,]", 512, JSON_ALLOW_COMMENTS));
@@ -44,5 +49,13 @@ NULL
4449
4: Syntax error near location 1:4
4550
NULL
4651
4: Syntax error near location 1:5
52+
NULL
53+
3: Control character error, possibly incorrectly encoded near location 1:4
54+
NULL
55+
5: Malformed UTF-8 characters, possibly incorrectly encoded near location 1:4
56+
NULL
57+
10: Single unpaired UTF-16 surrogate in unicode escape near location 1:4
58+
NULL
59+
3: Control character error, possibly incorrectly encoded near location 1:8
4760
bool(false)
4861
bool(false)

ext/json/tests/json_decode_trailing_commas.phpt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,20 @@ decode_dump("[1,,2]");
2626
decode_dump("{\"a\":1,,}");
2727
// mismatched closer after a trailing comma
2828
decode_dump("{\"a\":1,]");
29+
decode_dump("[1,}");
30+
// a comment between the opener and a lone comma does not make it valid
31+
decode_dump("[/*c*/,]", JSON_ALLOW_COMMENTS | JSON_ALLOW_TRAILING_COMMAS);
32+
// trailing commas and JSON_THROW_ON_ERROR
33+
try {
34+
var_dump(json_decode("[1,]", true, 512, JSON_ALLOW_TRAILING_COMMAS | JSON_THROW_ON_ERROR));
35+
} catch (JsonException $e) {
36+
echo "JsonException: ", $e->getCode(), " ", $e->getMessage(), "\n";
37+
}
38+
try {
39+
json_decode("[1,,]", true, 512, JSON_ALLOW_TRAILING_COMMAS | JSON_THROW_ON_ERROR);
40+
} catch (JsonException $e) {
41+
echo "JsonException: ", $e->getCode(), " ", $e->getMessage(), "\n";
42+
}
2943
// validate mirrors decode
3044
var_dump(json_validate("[1,]", 512, JSON_ALLOW_TRAILING_COMMAS));
3145
var_dump(json_validate("{\"a\":1,}", 512, JSON_ALLOW_TRAILING_COMMAS));
@@ -95,6 +109,15 @@ NULL
95109
4: Syntax error near location 1:8
96110
NULL
97111
2: State mismatch (invalid or malformed JSON) near location 1:8
112+
NULL
113+
2: State mismatch (invalid or malformed JSON) near location 1:4
114+
NULL
115+
4: Syntax error near location 1:7
116+
array(1) {
117+
[0]=>
118+
int(1)
119+
}
120+
JsonException: 4 Syntax error near location 1:4
98121
bool(true)
99122
bool(true)
100123
bool(false)

ext/json/tests/json_last_error_msg_error_location_011.phpt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ json_validate_trycatchdump($json, 512, JSON_ALLOW_COMMENTS);
4141
echo "\nError on line 1, column 13:\n";
4242
json_validate_trycatchdump("[1, /* x */ ,]", 512, JSON_ALLOW_COMMENTS | JSON_ALLOW_TRAILING_COMMAS);
4343

44+
// CRLF line endings inside a block comment count as one line each.
45+
echo "\nError on line 3, column 8:\n";
46+
json_validate_trycatchdump("[\r\n/* x\r\ny */ 1 2]", 512, JSON_ALLOW_COMMENTS);
47+
4448
?>
4549
--EXPECT--
4650
Error on line 4, column 20:
@@ -72,3 +76,8 @@ Error on line 1, column 13:
7276
bool(false)
7377
int(4)
7478
string(31) "Syntax error near location 1:13"
79+
80+
Error on line 3, column 8:
81+
bool(false)
82+
int(4)
83+
string(30) "Syntax error near location 3:8"

0 commit comments

Comments
 (0)