From 0fefcf84347a2bcc4961baec0295b9d04047d86f Mon Sep 17 00:00:00 2001 From: Nathan Brown Date: Thu, 6 Jun 2019 10:39:58 -0400 Subject: SERVER-7143 replace standard library number parsing with custom NumberParser --- src/mongo/bson/json.cpp | 175 +++++++++++++++++++++--------------------------- 1 file changed, 77 insertions(+), 98 deletions(-) (limited to 'src/mongo/bson/json.cpp') diff --git a/src/mongo/bson/json.cpp b/src/mongo/bson/json.cpp index 15661b0a456..91a6fcf2cd8 100644 --- a/src/mongo/bson/json.cpp +++ b/src/mongo/bson/json.cpp @@ -32,6 +32,7 @@ #include "mongo/bson/json.h" #include +#include #include "mongo/base/parse_number.h" #include "mongo/db/jsobj.h" @@ -48,6 +49,7 @@ namespace mongo { using std::unique_ptr; using std::ostringstream; using std::string; +using namespace fmt::literals; #if 0 #define MONGO_JSON_DEBUG(message) \ @@ -414,8 +416,6 @@ Status JParse::dateObject(StringData fieldName, BSONObjBuilder& builder) { if (!readToken(COLON)) { return parseError("Expected ':'"); } - errno = 0; - char* endptr; Date_t date; if (peekToken(DOUBLEQUOTE)) { @@ -454,32 +454,17 @@ Status JParse::dateObject(StringData fieldName, BSONObjBuilder& builder) { } long long numberLong; - ret = parseNumberFromString(numberLongString, &numberLong); + ret = NumberParser{}(numberLongString, &numberLong); if (!ret.isOK()) { return ret; } date = Date_t::fromMillisSinceEpoch(numberLong); } else { - // SERVER-11920: We should use parseNumberFromString here, but that function requires - // that we know ahead of time where the number ends, which is not currently the case. - date = Date_t::fromMillisSinceEpoch(strtoll(_input, &endptr, 10)); - if (_input == endptr) { - return parseError("Date expecting integer milliseconds"); - } - if (errno == ERANGE) { - /* Need to handle this because jsonString outputs the value of Date_t as unsigned. - * See SERVER-8330 and SERVER-8573 */ - errno = 0; - // SERVER-11920: We should use parseNumberFromString here, but that function - // requires that we know ahead of time where the number ends, which is not currently - // the case. - date = - Date_t::fromMillisSinceEpoch(static_cast(strtoull(_input, &endptr, 10))); - if (errno == ERANGE) { - return parseError("Date milliseconds overflow"); - } + StatusWith parsedDate = parseDate(); + if (!parsedDate.isOK()) { + return parsedDate.getStatus(); } - _input = endptr; + date = std::move(parsedDate).getValue(); } builder.appendDate(fieldName, date); return Status::OK(); @@ -503,15 +488,14 @@ Status JParse::timestampObject(StringData fieldName, BSONObjBuilder& builder) { if (readToken("-")) { return parseError("Negative seconds in \"$timestamp\""); } - errno = 0; char* endptr; - // SERVER-11920: We should use parseNumberFromString here, but that function requires that - // we know ahead of time where the number ends, which is not currently the case. - uint32_t seconds = strtoul(_input, &endptr, 10); - if (errno == ERANGE) { + uint32_t seconds; + NumberParser parser = NumberParser::strToAny(10); + Status parsedStatus = parser(_input, &seconds, &endptr); + if (parsedStatus == ErrorCodes::Overflow) { return parseError("Timestamp seconds overflow"); } - if (_input == endptr) { + if (!parsedStatus.isOK()) { return parseError("Expecting unsigned integer seconds in \"$timestamp\""); } _input = endptr; @@ -528,14 +512,12 @@ Status JParse::timestampObject(StringData fieldName, BSONObjBuilder& builder) { if (readToken("-")) { return parseError("Negative increment in \"$timestamp\""); } - errno = 0; - // SERVER-11920: We should use parseNumberFromString here, but that function requires that - // we know ahead of time where the number ends, which is not currently the case. - uint32_t count = strtoul(_input, &endptr, 10); - if (errno == ERANGE) { + uint32_t count; + parsedStatus = parser(_input, &count, &endptr); + if (parsedStatus == ErrorCodes::Overflow) { return parseError("Timestamp increment overflow"); } - if (_input == endptr) { + if (!parsedStatus.isOK()) { return parseError("Expecting unsigned integer increment in \"$timestamp\""); } _input = endptr; @@ -656,7 +638,7 @@ Status JParse::numberLongObject(StringData fieldName, BSONObjBuilder& builder) { } long long numberLong; - ret = parseNumberFromString(numberLongString, &numberLong); + ret = NumberParser{}(numberLongString, &numberLong); if (!ret.isOK()) { return ret; } @@ -753,26 +735,11 @@ Status JParse::date(StringData fieldName, BSONObjBuilder& builder) { if (!readToken(LPAREN)) { return parseError("Expecting '('"); } - errno = 0; - char* endptr; - // SERVER-11920: We should use parseNumberFromString here, but that function requires that - // we know ahead of time where the number ends, which is not currently the case. - Date_t date = Date_t::fromMillisSinceEpoch(strtoll(_input, &endptr, 10)); - if (_input == endptr) { - return parseError("Date expecting integer milliseconds"); + StatusWith parsedDate = parseDate(); + if (!parsedDate.isOK()) { + return parsedDate.getStatus(); } - if (errno == ERANGE) { - /* Need to handle this because jsonString outputs the value of Date_t as unsigned. - * See SERVER-8330 and SERVER-8573 */ - errno = 0; - // SERVER-11920: We should use parseNumberFromString here, but that function requires - // that we know ahead of time where the number ends, which is not currently the case. - date = Date_t::fromMillisSinceEpoch(static_cast(strtoull(_input, &endptr, 10))); - if (errno == ERANGE) { - return parseError("Date milliseconds overflow"); - } - } - _input = endptr; + Date_t date = parsedDate.getValue(); if (!readToken(RPAREN)) { return parseError("Expecting ')'"); } @@ -787,15 +754,14 @@ Status JParse::timestamp(StringData fieldName, BSONObjBuilder& builder) { if (readToken("-")) { return parseError("Negative seconds in \"$timestamp\""); } - errno = 0; char* endptr; - // SERVER-11920: We should use parseNumberFromString here, but that function requires that - // we know ahead of time where the number ends, which is not currently the case. - uint32_t seconds = strtoul(_input, &endptr, 10); - if (errno == ERANGE) { + NumberParser parser = NumberParser::strToAny(10); + uint32_t seconds; + Status parsedStatus = parser(_input, &seconds, &endptr); + if (parsedStatus == ErrorCodes::Overflow) { return parseError("Timestamp seconds overflow"); } - if (_input == endptr) { + if (!parsedStatus.isOK()) { return parseError("Expecting unsigned integer seconds in \"$timestamp\""); } _input = endptr; @@ -805,14 +771,12 @@ Status JParse::timestamp(StringData fieldName, BSONObjBuilder& builder) { if (readToken("-")) { return parseError("Negative seconds in \"$timestamp\""); } - errno = 0; - // SERVER-11920: We should use parseNumberFromString here, but that function requires that - // we know ahead of time where the number ends, which is not currently the case. - uint32_t count = strtoul(_input, &endptr, 10); - if (errno == ERANGE) { + uint32_t count; + parsedStatus = parser(_input, &count, &endptr); + if (parsedStatus == ErrorCodes::Overflow) { return parseError("Timestamp increment overflow"); } - if (_input == endptr) { + if (!parsedStatus.isOK()) { return parseError("Expecting unsigned integer increment in \"$timestamp\""); } _input = endptr; @@ -850,15 +814,13 @@ Status JParse::numberLong(StringData fieldName, BSONObjBuilder& builder) { if (!readToken(LPAREN)) { return parseError("Expecting '('"); } - errno = 0; char* endptr; - // SERVER-11920: We should use parseNumberFromString here, but that function requires that - // we know ahead of time where the number ends, which is not currently the case. - int64_t val = strtoll(_input, &endptr, 10); - if (errno == ERANGE) { + int64_t val; + Status parsedStatus = NumberParser::strToAny(10)(_input, &val, &endptr); + if (parsedStatus == ErrorCodes::Overflow) { return parseError("NumberLong out of range"); } - if (_input == endptr) { + if (!parsedStatus.isOK()) { return parseError("Expecting number in NumberLong"); } _input = endptr; @@ -880,7 +842,15 @@ Status JParse::numberDecimal(StringData fieldName, BSONObjBuilder& builder) { if (ret != Status::OK()) { return ret; } - Decimal128 val(decString); + Decimal128 val; + Status parsedStatus = NumberParser().setDecimal128RoundingMode( + Decimal128::RoundingMode::kRoundTiesToEven)(decString, &val); + if (parsedStatus == ErrorCodes::Overflow) { + return parseError("numberDecimal out of range"); + } + if (!parsedStatus.isOK()) { + return parseError("Expecting decimal in numberDecimal"); + } if (!readToken(RPAREN)) { return parseError("Expecting ')'"); @@ -893,15 +863,13 @@ Status JParse::numberInt(StringData fieldName, BSONObjBuilder& builder) { if (!readToken(LPAREN)) { return parseError("Expecting '('"); } - errno = 0; char* endptr; - // SERVER-11920: We should use parseNumberFromString here, but that function requires that - // we know ahead of time where the number ends, which is not currently the case. - int32_t val = strtol(_input, &endptr, 10); - if (errno == ERANGE) { + int32_t val; + Status parsedStatus = NumberParser::strToAny(10)(_input, &val, &endptr); + if (parsedStatus == ErrorCodes::Overflow) { return parseError("NumberInt out of range"); } - if (_input == endptr) { + if (!parsedStatus.isOK()) { return parseError("Expecting unsigned number in NumberInt"); } _input = endptr; @@ -1010,24 +978,15 @@ Status JParse::number(StringData fieldName, BSONObjBuilder& builder) { long long retll; double retd; - // reset errno to make sure that we are getting it from strtod - errno = 0; - // SERVER-11920: We should use parseNumberFromString here, but that function requires that - // we know ahead of time where the number ends, which is not currently the case. - retd = strtod(_input, &endptrd); - // if pointer does not move, we found no digits - if (_input == endptrd) { - return parseError("Bad characters in value"); - } - if (errno == ERANGE) { + Status parsedStatus = NumberParser::strToAny()(_input, &retd, &endptrd); + if (parsedStatus == ErrorCodes::Overflow) { return parseError("Value cannot fit in double"); } - // reset errno to make sure that we are getting it from strtoll - errno = 0; - // SERVER-11920: We should use parseNumberFromString here, but that function requires that - // we know ahead of time where the number ends, which is not currently the case. - retll = strtoll(_input, &endptrll, 10); - if (endptrll < endptrd || errno == ERANGE) { + if (!parsedStatus.isOK()) { + return parseError("Bad characters in value"); + } + parsedStatus = NumberParser::strToAny(10)(_input, &retll, &endptrll); + if (endptrll < endptrd || parsedStatus == ErrorCodes::Overflow) { // The number either had characters only meaningful for a double or // could not fit in a 64 bit int MONGO_JSON_DEBUG("Type: double"); @@ -1293,6 +1252,28 @@ bool JParse::isArray() { return peekToken(LBRACKET); } +StatusWith JParse::parseDate() { + long long msSinceEpoch; + char* endptr; + Status parsedStatus = NumberParser::strToAny(10)(_input, &msSinceEpoch, &endptr); + if (parsedStatus == ErrorCodes::Overflow) { + /* Need to handle this because jsonString outputs the value of Date_t as unsigned. + * See SERVER-8330 and SERVER-8573 */ + unsigned long long oldDate; // Date_t used to be stored as unsigned long longs + parsedStatus = NumberParser::strToAny(10)(_input, &oldDate, &endptr); + if (parsedStatus == ErrorCodes::Overflow) { + return parseError("Date milliseconds overflow"); + } + msSinceEpoch = static_cast(oldDate); + } else if (!parsedStatus.isOK()) { + return parseError("Date expecting integer milliseconds"); + } + invariant(endptr != _input); + Date_t date = Date_t::fromMillisSinceEpoch(msSinceEpoch); + _input = endptr; + return date; +} + BSONObj fromjson(const char* jsonString, int* len) { MONGO_JSON_DEBUG("jsonString: " << jsonString); if (jsonString[0] == '\0') { @@ -1312,9 +1293,7 @@ BSONObj fromjson(const char* jsonString, int* len) { } if (ret != Status::OK()) { - ostringstream message; - message << "code " << ret.code() << ": " << ret.codeString() << ": " << ret.reason(); - uasserted(16619, message.str()); + uasserted(16619, "code {}: {}: {}"_format(ret.code(), ret.codeString(), ret.reason())); } if (len) *len = jparse.offset(); -- cgit v1.2.1