Skip to content

Commit be32470

Browse files
rsteinkeXMongoDB Bot
authored andcommitted
SERVER-99691: Throw when fromjson() doesn't parse the entire string (#32320)
GitOrigin-RevId: a7c707bc95ed2f80669aeeb93c8c11fe9cb985e2
1 parent 0047309 commit be32470

File tree

3 files changed

+58
-23
lines changed

3 files changed

+58
-23
lines changed

src/mongo/bson/json.cpp

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@ std::string escapeNewlines(const char* input, int len) {
110110
}
111111
return out.str();
112112
}
113+
114+
bool isAllSpace(StringData str) {
115+
return std::all_of(str.begin(), str.end(), [](char c) { return ctype::isSpace(c); });
116+
}
117+
113118
} // namespace
114119

115120
JParse::JParse(StringData str)
@@ -1554,14 +1559,14 @@ StatusWith<Date_t> JParse::parseDate() {
15541559
return date;
15551560
}
15561561

1557-
BSONObj fromjson(const char* jsonString, int* len) {
1562+
namespace fromjson_detail {
1563+
1564+
BSONObj fromJsonImpl(const char* jsonString, size_t len) {
15581565
MONGO_JSON_DEBUG("jsonString: " << jsonString);
1559-
if (jsonString[0] == '\0') {
1560-
if (len)
1561-
*len = 0;
1566+
if (len == 0) {
15621567
return BSONObj();
15631568
}
1564-
JParse jparse(jsonString);
1569+
JParse jparse(StringData{jsonString, len});
15651570
BSONObjBuilder builder;
15661571
Status ret = Status::OK();
15671572
try {
@@ -1575,14 +1580,13 @@ BSONObj fromjson(const char* jsonString, int* len) {
15751580
if (ret != Status::OK()) {
15761581
uasserted(16619, "code {}: {}: {}"_format(ret.code(), ret.codeString(), ret.reason()));
15771582
}
1578-
if (len)
1579-
*len = jparse.offset();
1583+
uassert(ErrorCodes::FailedToParse,
1584+
"Garbage at end of json string",
1585+
isAllSpace(jsonString + jparse.offset()));
15801586
return builder.obj();
15811587
}
15821588

1583-
BSONObj fromjson(StringData str) {
1584-
return fromjson(str.toString().c_str());
1585-
}
1589+
} // namespace fromjson_detail
15861590

15871591
std::string tojson(const BSONObj& obj, JsonStringFormat format, bool pretty) {
15881592
return obj.jsonString(format, pretty);

src/mongo/bson/json.h

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@
4040

4141
namespace mongo {
4242

43+
namespace fromjson_detail {
44+
/** Private implementation detail of fromjson. */
45+
BSONObj fromJsonImpl(const char* jsonString, size_t len);
46+
} // namespace fromjson_detail
47+
4348
/**
4449
* Create a BSONObj from a JSON <http://www.json.org>,
4550
* <http://www.ietf.org/rfc/rfc4627.txt> string. In addition to the JSON
@@ -49,14 +54,46 @@ namespace mongo {
4954
* used when specifying field names and std::string values instead of double
5055
* quotes. JSON unicode escape sequences (of the form \uXXXX) are
5156
* converted to utf8.
57+
* `str` must be a null terminated string.
58+
*
59+
* `str` must be completely consumed by the parse.
60+
* Trailing whitespace is tolerated.
5261
*
53-
* @throws AssertionException if parsing fails. The message included with
54-
* this assertion includes the character offset where parsing failed.
62+
* Throws with `ErrorCodes::FailedToParse` on failure.
63+
* - If the parse failed because the `str` was incompletely consumed,
64+
* the exception message will indicate "Garbage at end".
65+
* - Otherwise, the message includes the character offset where parsing failed.
5566
*/
56-
BSONObj fromjson(StringData str);
5767

58-
/** @param len will be size of JSON object in text chars. */
59-
BSONObj fromjson(const char* str, int* len = nullptr);
68+
inline BSONObj fromjson(const char* str) {
69+
return fromjson_detail::fromJsonImpl(str, strlen(str));
70+
}
71+
72+
inline BSONObj fromjson(char* str) {
73+
return fromjson_detail::fromJsonImpl(str, strlen(str));
74+
}
75+
76+
inline BSONObj fromjson(const std::string& str) {
77+
return fromjson_detail::fromJsonImpl(str.c_str(), str.size());
78+
}
79+
80+
inline BSONObj fromjson(const str::stream& ss) {
81+
return fromjson(std::string{ss});
82+
}
83+
84+
/** Inefficiently promote to std::string, temporarily. */
85+
inline BSONObj fromjson(StringData str) {
86+
return fromjson(std::string{str});
87+
}
88+
89+
/** Decay arrays (literals) to `const char*`. */
90+
template <size_t N>
91+
BSONObj fromjson(const char (&str)[N]) {
92+
return fromjson_detail::fromJsonImpl(str, N - 1);
93+
}
94+
95+
/** Prevent conversion. Caller must choose a directly supported overload. */
96+
BSONObj fromjson(const auto& str) = delete;
6097

6198
/**
6299
* Tests whether the JSON string is an Array.

src/mongo/unittest/death_test.cpp

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -325,17 +325,11 @@ void DeathTestBase::Subprocess::monitorChild(FILE* pf) {
325325
line = line.substr(0, line.size() - 1);
326326
if (line.empty())
327327
continue;
328-
int parsedLen = 0;
329-
BSONObj parsedChildLog;
330328
try {
331-
parsedChildLog = fromjson(lineBuf, &parsedLen);
329+
auto parsedChildLog = fromjson(lineBuf);
330+
LOGV2(20165, "child", "json"_attr = parsedChildLog);
332331
} catch (DBException&) {
333332
// ignore json parsing errors and dump the whole log line as text
334-
parsedLen = 0;
335-
}
336-
if (static_cast<size_t>(parsedLen) == line.size()) {
337-
LOGV2(20165, "child", "json"_attr = parsedChildLog);
338-
} else {
339333
LOGV2(20169, "child", "text"_attr = line);
340334
}
341335
os.write(lineBuf, bytesRead);

0 commit comments

Comments
 (0)