Skip to content

Commit 22c7ec3

Browse files
authored
fix: allow a comment between a trailing comma and ']' (#1500) (#1696)
Trailing commas and comments are both allowed by default, but they did not compose inside arrays: readArray detected a trailing-comma ']' with a raw `*current_ == ']'` peek that skipped only whitespace, not comments. So `[1, 2, /* c */]` left current_ at the comment, the peek failed, and the parser tried to read another value -- which hit ']' and reported "value, object or array expected". readObject already handled this via readTokenSkippingComments. Add skipCommentTokens() (skip whitespace and comments, leaving current_ at the next significant character) and use it in readArray before the ']' check. Consumed comments stay in commentsBefore_, so a comment before a real element is still attached to it; if the array ends, they are simply not attached -- matching object behavior. Adds CharReaderTest/parseTrailingCommaWithComment covering line/block comments after a trailing comma, an empty array containing only a comment, the object form, and that a comment before a real element is still attached.
1 parent 1127961 commit 22c7ec3

2 files changed

Lines changed: 56 additions & 1 deletion

File tree

src/lib_json/json_reader.cpp

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,7 @@ class OurReader {
926926
bool readToken(Token& token);
927927
bool readTokenSkippingComments(Token& token);
928928
void skipSpaces();
929+
void skipCommentTokens();
929930
void skipBom(bool skipBom);
930931
bool match(const Char* pattern, int patternLength);
931932
bool readComment();
@@ -1269,6 +1270,24 @@ void OurReader::skipSpaces() {
12691270
}
12701271
}
12711272

1273+
// Skip whitespace and any comments, leaving current_ at the next significant
1274+
// character. Consumed comments are recorded (commentsBefore_) so the next value
1275+
// still receives them; if none follows they are simply not attached. This lets
1276+
// callers peek for a delimiter that is preceded by comments (e.g. a ']' after a
1277+
// trailing comma -- see readArray and issue #1500).
1278+
void OurReader::skipCommentTokens() {
1279+
skipSpaces();
1280+
if (!features_.allowComments_)
1281+
return;
1282+
while (current_ != end_ && *current_ == '/' && (current_ + 1) != end_ &&
1283+
(current_[1] == '/' || current_[1] == '*')) {
1284+
Token comment;
1285+
if (!readToken(comment))
1286+
return;
1287+
skipSpaces();
1288+
}
1289+
}
1290+
12721291
void OurReader::skipBom(bool skipBom) {
12731292
// The default behavior is to skip BOM.
12741293
if (skipBom) {
@@ -1501,7 +1520,10 @@ bool OurReader::readArray(Token& token) {
15011520
currentValue().setOffsetStart(token.start_ - begin_);
15021521
int index = 0;
15031522
for (;;) {
1504-
skipSpaces();
1523+
// Skip comments too, so a ']' that follows a trailing comma (or comments in
1524+
// an otherwise empty array) is recognized rather than mistaken for the
1525+
// start of another value. See issue #1500.
1526+
skipCommentTokens();
15051527
if (current_ != end_ && *current_ == ']' &&
15061528
(index == 0 ||
15071529
(features_.allowTrailingCommas_ &&

src/test_lib_json/main.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3385,6 +3385,39 @@ JSONTEST_FIXTURE_LOCAL(CharReaderTest, parseComment) {
33853385
}
33863386
}
33873387

3388+
JSONTEST_FIXTURE_LOCAL(CharReaderTest, parseTrailingCommaWithComment) {
3389+
// Regression test for #1500: trailing commas and comments are both allowed by
3390+
// default, so they must compose -- a comment between a trailing comma and the
3391+
// closing ']' must not turn a valid document into a parse error. (Objects
3392+
// already handled this; arrays did not.)
3393+
Json::CharReaderBuilder b;
3394+
CharReaderPtr reader(b.newCharReader());
3395+
Json::Value root;
3396+
Json::String errs;
3397+
3398+
for (const char* doc : {
3399+
"[1,2,\n// trailing\n]", // line comment after trailing comma
3400+
"[1,2,/* trailing */]", // block comment after trailing comma
3401+
"[{},\n// trailing\n]", // trailing comma after a nested value
3402+
"[\n// only a comment\n]", // empty array containing a comment
3403+
"{\"a\":1,\n// trailing\n}", // object form (guard the existing case)
3404+
}) {
3405+
bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs);
3406+
JSONTEST_ASSERT(ok);
3407+
JSONTEST_ASSERT(errs.empty());
3408+
}
3409+
3410+
// A comment before a real (non-closing) element is still attached to it.
3411+
{
3412+
char const doc[] = "[1,\n// before two\n2]";
3413+
bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs);
3414+
JSONTEST_ASSERT(ok);
3415+
JSONTEST_ASSERT_EQUAL(2u, root.size());
3416+
JSONTEST_ASSERT_EQUAL(2, root[1]);
3417+
JSONTEST_ASSERT(root[1].hasComment(Json::commentBefore));
3418+
}
3419+
}
3420+
33883421
JSONTEST_FIXTURE_LOCAL(CharReaderTest, parseCommentsAfterValueScansLinearly) {
33893422
// A value, then a comment whose only newline is at its end, then many
33903423
// trailing comments. Comment handling should scan the value->comment gap a

0 commit comments

Comments
 (0)