diff options
author | Nathan Brown <nathan.brown@10gen.com> | 2019-06-06 10:39:58 -0400 |
---|---|---|
committer | Nathan Brown <nathan.brown@10gen.com> | 2019-06-27 10:04:18 -0400 |
commit | 0fefcf84347a2bcc4961baec0295b9d04047d86f (patch) | |
tree | ce86921e5b14ee83a00400f4f4e8712093505cb8 /src/mongo | |
parent | a700238800aa2bf1e10c255337ec35373ffd2667 (diff) | |
download | mongo-0fefcf84347a2bcc4961baec0295b9d04047d86f.tar.gz |
SERVER-7143 replace standard library number parsing with custom NumberParser
Diffstat (limited to 'src/mongo')
33 files changed, 783 insertions, 456 deletions
diff --git a/src/mongo/base/parse_number.cpp b/src/mongo/base/parse_number.cpp index e3c20c93f79..07f0ea2cbbe 100644 --- a/src/mongo/base/parse_number.cpp +++ b/src/mongo/base/parse_number.cpp @@ -32,6 +32,7 @@ #include "mongo/base/parse_number.h" #include <algorithm> +#include <cctype> #include <cerrno> #include <cstdint> #include <cstdlib> @@ -128,45 +129,69 @@ inline StringData _extractBase(StringData stringValue, int inputBase, int* outpu inline StatusWith<uint64_t> parseMagnitudeFromStringWithBase(uint64_t base, StringData wholeString, - StringData magnitudeStr) { + StringData magnitudeStr, + const char** end, + bool allowTrailingText) { uint64_t n = 0; + size_t charsConsumed = 0; for (char digitChar : magnitudeStr) { const uint64_t digitValue = _digitValue(digitChar); if (digitValue >= base) { - return Status(ErrorCodes::FailedToParse, - std::string("Bad digit \"") + digitChar + "\" while parsing " + - wholeString); + break; } // This block is (n = (n * base) + digitValue) with overflow checking at each step. uint64_t multiplied; if (mongoUnsignedMultiplyOverflow64(n, base, &multiplied)) - return Status(ErrorCodes::FailedToParse, "Overflow"); + return Status(ErrorCodes::Overflow, "Overflow"); if (mongoUnsignedAddOverflow64(multiplied, digitValue, &n)) - return Status(ErrorCodes::FailedToParse, "Overflow"); + return Status(ErrorCodes::Overflow, "Overflow"); + ++charsConsumed; } + if (end) + *end = magnitudeStr.begin() + charsConsumed; + if (!allowTrailingText && charsConsumed != magnitudeStr.size()) + return Status(ErrorCodes::FailedToParse, "Did not consume whole string."); + if (charsConsumed == 0) + return Status(ErrorCodes::FailedToParse, "Did not consume any digits"); return n; } -} // namespace +StringData removeLeadingWhitespace(StringData s) { + return s.substr(std::distance( + s.begin(), + std::find_if_not(s.begin(), s.end(), [](unsigned char c) { return isspace(c); }))); +} template <typename NumberType> -Status parseNumberFromStringWithBase(StringData wholeString, int base, NumberType* result) { +Status parseNumberFromStringHelper(StringData s, + NumberType* result, + const char** endptr, + const NumberParser& parser) { MONGO_STATIC_ASSERT(sizeof(NumberType) <= sizeof(uint64_t)); typedef ::std::numeric_limits<NumberType> limits; - if (base == 1 || base < 0 || base > 36) - return Status(ErrorCodes::BadValue, "Invalid base"); + if (endptr) + *endptr = s.begin(); + + if (parser._base == 1 || parser._base < 0 || parser._base > 36) + return Status(ErrorCodes::BadValue, "Invalid parser._base"); + + if (parser._skipLeadingWhitespace) { + s = removeLeadingWhitespace(s); + } - // Separate the magnitude from modifiers such as sign and base prefixes such as "0x" + // Separate the magnitude from modifiers such as sign and parser._base prefixes such as "0x" bool isNegative = false; - StringData magnitudeStr = _extractBase(_extractSign(wholeString, &isNegative), base, &base); + int base = 0; + StringData magnitudeStr = _extractBase(_extractSign(s, &isNegative), parser._base, &base); if (isNegative && !limits::is_signed) return Status(ErrorCodes::FailedToParse, "Negative value"); if (magnitudeStr.empty()) return Status(ErrorCodes::FailedToParse, "No digits"); - auto status = parseMagnitudeFromStringWithBase(base, wholeString, magnitudeStr); + auto status = + parseMagnitudeFromStringWithBase(base, s, magnitudeStr, endptr, parser._allowTrailingText); if (!status.isOK()) return status.getStatus(); uint64_t magnitude = status.getValue(); @@ -174,7 +199,7 @@ Status parseNumberFromStringWithBase(StringData wholeString, int base, NumberTyp // The range of 2's complement integers is from -(max + 1) to +max. const uint64_t maxMagnitude = uint64_t(limits::max()) + (isNegative ? 1u : 0u); if (magnitude > maxMagnitude) - return Status(ErrorCodes::FailedToParse, "Overflow"); + return Status(ErrorCodes::Overflow, "Overflow"); #pragma warning(push) // C4146: unary minus operator applied to unsigned type, result still unsigned @@ -185,23 +210,6 @@ Status parseNumberFromStringWithBase(StringData wholeString, int base, NumberTyp return Status::OK(); } -// Definition of the various supported implementations of parseNumberFromStringWithBase. - -#define DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE(NUMBER_TYPE) \ - template Status parseNumberFromStringWithBase<NUMBER_TYPE>(StringData, int, NUMBER_TYPE*); - -DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE(long) -DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE(long long) -DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE(unsigned long) -DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE(unsigned long long) -DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE(short) -DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE(unsigned short) -DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE(int) -DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE(unsigned int) -DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE(int8_t); -DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE(uint8_t); -#undef DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE - #ifdef _WIN32 namespace { @@ -221,15 +229,19 @@ char toLowerAscii(char c) { #endif // defined(_WIN32) template <> -Status parseNumberFromStringWithBase<double>(StringData stringValue, int base, double* result) { - if (base != 0) { - return Status(ErrorCodes::BadValue, - "Must pass 0 as base to parseNumberFromStringWithBase<double>."); +Status parseNumberFromStringHelper<double>(StringData stringValue, + double* result, + const char** endptr, + const NumberParser& parser) { + if (endptr) + *endptr = stringValue.begin(); + if (parser._base != 0) { + return Status(ErrorCodes::BadValue, "NumberParser::base must be 0 for a double."); } if (stringValue.empty()) return Status(ErrorCodes::FailedToParse, "Empty string"); - if (isspace(stringValue[0])) + if (!parser._skipLeadingWhitespace && isspace(stringValue[0])) return Status(ErrorCodes::FailedToParse, "Leading whitespace"); std::string str = stringValue.toString(); @@ -238,38 +250,59 @@ Status parseNumberFromStringWithBase<double>(StringData stringValue, int base, d errno = 0; double d = strtod(cStr, &endp); int actualErrno = errno; - if (endp != stringValue.size() + cStr) { + if (endp == cStr) { #ifdef _WIN32 // The Windows libc implementation of strtod cannot parse +/-infinity or nan, // so handle that here. std::transform(str.begin(), str.end(), str.begin(), toLowerAscii); if (str == "nan"_sd) { *result = std::numeric_limits<double>::quiet_NaN(); + if (endptr) + *endptr = stringValue.end(); return Status::OK(); } else if (str == "+infinity"_sd || str == "infinity"_sd) { *result = std::numeric_limits<double>::infinity(); + if (endptr) + *endptr = stringValue.end(); return Status::OK(); } else if (str == "-infinity"_sd) { *result = -std::numeric_limits<double>::infinity(); + if (endptr) + *endptr = stringValue.end(); return Status::OK(); } #endif // defined(_WIN32) - - return Status(ErrorCodes::FailedToParse, "Did not consume whole number."); + return Status(ErrorCodes::FailedToParse, "Did not consume any digits"); + } + if (actualErrno == ERANGE) { + return Status(ErrorCodes::Overflow, "Out of range"); + } + if (endptr) { + size_t charsConsumed = endp - cStr; + *endptr = stringValue.begin() + charsConsumed; } - if (actualErrno == ERANGE) - return Status(ErrorCodes::FailedToParse, "Out of range"); + if (!parser._allowTrailingText && endp != (cStr + str.size())) + return Status(ErrorCodes::FailedToParse, "Did not consume whole string."); *result = d; return Status::OK(); } template <> -Status parseNumberFromStringWithBase<Decimal128>(StringData stringValue, - int base, - Decimal128* result) { - if (base != 0) { +Status parseNumberFromStringHelper<Decimal128>(StringData stringValue, + Decimal128* result, + const char** endptr, + const NumberParser& parser) { + if (endptr) + *endptr = stringValue.begin(); // same behavior as strtod: if unable to parse, set end to + // be the beginning of input str + + if (parser._base != 0) { return Status(ErrorCodes::BadValue, - "Must pass 0 as base to parseNumberFromStringWithBase<Decimal128>."); + "NumberParser::parser._base must be 0 for a Decimal128."); + } + + if (parser._skipLeadingWhitespace) { + stringValue = removeLeadingWhitespace(stringValue); } if (stringValue.empty()) { @@ -277,21 +310,44 @@ Status parseNumberFromStringWithBase<Decimal128>(StringData stringValue, } std::uint32_t signalingFlags = 0; - auto parsedDecimal = Decimal128( - stringValue.toString(), &signalingFlags, Decimal128::RoundingMode::kRoundTowardZero); + size_t charsConsumed; + auto parsedDecimal = + Decimal128(stringValue.toString(), &signalingFlags, parser._roundingMode, &charsConsumed); if (Decimal128::hasFlag(signalingFlags, Decimal128::SignalingFlag::kOverflow)) { - return Status(ErrorCodes::FailedToParse, - "Conversion from string to decimal would overflow"); + return Status(ErrorCodes::Overflow, "Conversion from string to decimal would overflow"); } else if (Decimal128::hasFlag(signalingFlags, Decimal128::SignalingFlag::kUnderflow)) { - return Status(ErrorCodes::FailedToParse, - "Conversion from string to decimal would underflow"); + return Status(ErrorCodes::Overflow, "Conversion from string to decimal would underflow"); } else if (signalingFlags != Decimal128::SignalingFlag::kNoFlag && signalingFlags != Decimal128::SignalingFlag::kInexact) { // Ignore precision loss. return Status(ErrorCodes::FailedToParse, "Failed to parse string to decimal"); } + if (endptr) + *endptr += charsConsumed; + if (!parser._allowTrailingText && charsConsumed != stringValue.size()) + return Status(ErrorCodes::FailedToParse, "Did not consume whole string."); *result = parsedDecimal; return Status::OK(); } +} // namespace + +#define DEFINE_NUMBER_PARSER_OPERATOR(type) \ + Status NumberParser::operator()(StringData stringValue, type* result, char** endPtr) const { \ + return parseNumberFromStringHelper( \ + stringValue, result, const_cast<const char**>(endPtr), *this); \ + } + +DEFINE_NUMBER_PARSER_OPERATOR(long) +DEFINE_NUMBER_PARSER_OPERATOR(long long) +DEFINE_NUMBER_PARSER_OPERATOR(unsigned long) +DEFINE_NUMBER_PARSER_OPERATOR(unsigned long long) +DEFINE_NUMBER_PARSER_OPERATOR(short) +DEFINE_NUMBER_PARSER_OPERATOR(unsigned short) +DEFINE_NUMBER_PARSER_OPERATOR(int) +DEFINE_NUMBER_PARSER_OPERATOR(unsigned int) +DEFINE_NUMBER_PARSER_OPERATOR(int8_t) +DEFINE_NUMBER_PARSER_OPERATOR(uint8_t) +DEFINE_NUMBER_PARSER_OPERATOR(double) +DEFINE_NUMBER_PARSER_OPERATOR(Decimal128) } // namespace mongo diff --git a/src/mongo/base/parse_number.h b/src/mongo/base/parse_number.h index fb3095012b3..dcdbcc50424 100644 --- a/src/mongo/base/parse_number.h +++ b/src/mongo/base/parse_number.h @@ -35,32 +35,95 @@ #include "mongo/base/status.h" #include "mongo/base/string_data.h" +#include "mongo/platform/decimal128.h" namespace mongo { /** - * Parses a number out of a StringData. - * - * Parses "stringValue", interpreting it as a number of the given "base". On success, stores - * the parsed value into "*result" and returns Status::OK(). - * - * Valid values for "base" are 2-36, with 0 meaning "choose the base by inspecting the prefix - * on the number", as in strtol. Returns Status::BadValue if an illegal value is supplied for - * "base". - * - * The entirety of the std::string must consist of digits in the given base, except optionally the - * first character may be "+" or "-", and hexadecimal numbers may begin "0x". Same as strtol, - * without the property of stripping whitespace at the beginning, and fails to parse if there - * are non-digit characters at the end of the string. - * - * See parse_number.cpp for the available instantiations, and add any new instantiations there. + * Builder pattern for setting up a number parser. Intended usage: + * long result; + * char* end; + * NumberParser() + * .base(16) + * .allowTrailingText() + * .skipWhitespace() + * ("\t\n 0x16hello, world", &result, &end); + * //end points to 'h' and result holds 22 */ -template <typename NumberType> -Status parseNumberFromStringWithBase(StringData stringValue, int base, NumberType* result); +struct NumberParser { +public: + /** + * Behave like strtol/atoi and skip whitespace at the beginning of the string + */ + NumberParser& skipWhitespace(bool skipws = true) { + _skipLeadingWhitespace = skipws; + return *this; + } + + /** + * Set a base for the conversion. 0 means infer the base akin to strtol. + * Legal bases are [2-35]. If a base outside of this is selected, then operator() + * will return BadValue. + */ + NumberParser& base(int b = 0) { + _base = b; + return *this; + } + + /* + * Acts like atoi/strtol and will still parse even if there are non-numeric characters in the + * string after the number. Without this option, the parser will return FailedToParse if there + * are leftover characters in the parsed string. + */ + NumberParser& allowTrailingText(bool allowTrailingText = true) { + _allowTrailingText = allowTrailingText; + return *this; + } + + NumberParser& setDecimal128RoundingMode( + Decimal128::RoundingMode mode = Decimal128::RoundingMode::kRoundTiesToEven) { + _roundingMode = mode; + return *this; + } + + /* + * returns a NumberParser configured like strtol/atoi + */ + static NumberParser strToAny(int base = 0) { + return NumberParser().skipWhitespace().base(base).allowTrailingText(); + } + + /* + * Parsing overloads for different supported numerical types. + * + * On success, the parsed value is stored into *result and returns Status::OK(). + * If endPtr is not nullptr, the end of the number portion of the string will be stored at + * *endPtr (like strtol). + * This will return with Status::FailedToParse if the string does not represent a number value. + * See skipWhitespace and allowTrailingText for ways to expand the parser's capabilities. + * Returns with Status::Overflow if the parsed number cannot be represented by the desired type. + * If the status is not OK, then there are no guarantees about what value will be stored in + * result. + */ + Status operator()(StringData strData, long* result, char** endPtr = nullptr) const; + Status operator()(StringData strData, long long* result, char** endPtr = nullptr) const; + Status operator()(StringData strData, unsigned long* result, char** endPtr = nullptr) const; + Status operator()(StringData strData, + unsigned long long* result, + char** endPtr = nullptr) const; + Status operator()(StringData strData, short* result, char** endPtr = nullptr) const; + Status operator()(StringData strData, unsigned short* result, char** endPtr = nullptr) const; + Status operator()(StringData strData, int* result, char** endPtr = nullptr) const; + Status operator()(StringData strData, unsigned int* result, char** endPtr = nullptr) const; + Status operator()(StringData strData, int8_t* result, char** endPtr = nullptr) const; + Status operator()(StringData strData, uint8_t* result, char** endPtr = nullptr) const; + Status operator()(StringData strData, double* result, char** endPtr = nullptr) const; + Status operator()(StringData strData, Decimal128* result, char** endPtr = nullptr) const; -template <typename NumberType> -static Status parseNumberFromString(StringData stringValue, NumberType* result) { - return parseNumberFromStringWithBase(stringValue, 0, result); -} + int _base = 0; + Decimal128::RoundingMode _roundingMode = Decimal128::RoundingMode::kRoundTowardZero; + bool _skipLeadingWhitespace = false; + bool _allowTrailingText = false; +}; } // namespace mongo diff --git a/src/mongo/base/parse_number_test.cpp b/src/mongo/base/parse_number_test.cpp index b17968f4081..d4f42dbbb72 100644 --- a/src/mongo/base/parse_number_test.cpp +++ b/src/mongo/base/parse_number_test.cpp @@ -32,184 +32,387 @@ #include <cmath> #include <cstdint> #include <limits> +#include <type_traits> +#include <typeinfo> +#include <vector> #include "mongo/base/parse_number.h" #include "mongo/base/status.h" #include "mongo/unittest/unittest.h" +#include "mongo/util/if_constexpr.h" #include "mongo/util/str.h" // for str::stream()! -#define ASSERT_PARSES(TYPE, INPUT_STRING, EXPECTED_VALUE) \ - do { \ - TYPE v; \ - ASSERT_OK(parseNumberFromString(INPUT_STRING, &v)); \ - ASSERT_EQUALS(static_cast<TYPE>(EXPECTED_VALUE), v); \ +#define ASSERT_PARSES_WITH_PARSER(type, input_string, parser, expected_value) \ + do { \ + type v; \ + ASSERT_OK(parser(input_string, &v)); \ + ASSERT_EQ(static_cast<type>(expected_value), v); \ } while (false) +#define ASSERT_PARSES(TYPE, INPUT_STRING, EXPECTED_VALUE) \ + ASSERT_PARSES_WITH_PARSER(TYPE, INPUT_STRING, NumberParser(), EXPECTED_VALUE) + #define ASSERT_PARSES_WITH_BASE(TYPE, INPUT_STRING, BASE, EXPECTED_VALUE) \ - do { \ - TYPE v; \ - ASSERT_OK(parseNumberFromStringWithBase(INPUT_STRING, BASE, &v)); \ - ASSERT_EQUALS(static_cast<TYPE>(EXPECTED_VALUE), v); \ - } while (false) + ASSERT_PARSES_WITH_PARSER(TYPE, INPUT_STRING, NumberParser().base(BASE), EXPECTED_VALUE) namespace mongo { namespace { -template <typename _NumberType> -class CommonNumberParsingTests { -public: - typedef _NumberType NumberType; - typedef std::numeric_limits<NumberType> Limits; +template <typename... Ts> +struct TypeListTag {}; +template <typename T> +using TypeTag = TypeListTag<T>; - static void TestRejectingBadBases() { - NumberType ignored; - ASSERT_EQUALS(ErrorCodes::BadValue, parseNumberFromStringWithBase("0", -1, &ignored)); - ASSERT_EQUALS(ErrorCodes::BadValue, parseNumberFromStringWithBase("10", 1, &ignored)); - ASSERT_EQUALS(ErrorCodes::BadValue, parseNumberFromStringWithBase("-10", 37, &ignored)); - ASSERT_EQUALS(ErrorCodes::BadValue, parseNumberFromStringWithBase(" ", -1, &ignored)); - ASSERT_EQUALS(ErrorCodes::BadValue, parseNumberFromStringWithBase("f", 37, &ignored)); - ASSERT_EQUALS(ErrorCodes::BadValue, parseNumberFromStringWithBase("^%", -1, &ignored)); - } +template <typename F, typename... Ts> +void apply(F&& f, TypeListTag<Ts...>) { + (f.run(TypeTag<Ts>{}), ...); +} - static void TestParsingNonNegatives() { - ASSERT_PARSES(NumberType, "10", 10); - ASSERT_PARSES(NumberType, "0", 0); - ASSERT_PARSES(NumberType, "1", 1); - ASSERT_PARSES(NumberType, "0xff", 0xff); - ASSERT_PARSES(NumberType, "077", 077); - } - static void TestParsingNegatives() { - if (Limits::is_signed) { - ASSERT_PARSES(NumberType, "-0", 0); - ASSERT_PARSES(NumberType, "-10", -10); - ASSERT_PARSES(NumberType, "-0xff", -0xff); - ASSERT_PARSES(NumberType, "-077", -077); +auto allTypes = TypeListTag<short, + int, + long, + long long, + unsigned short, + unsigned int, + unsigned long, + unsigned long long, + int16_t, + int32_t, + int64_t, + uint16_t, + uint32_t, + uint64_t>{}; + +#define PARSE_TEST(TEST_NAME) \ + struct PARSE_TEST_##TEST_NAME { \ + template <typename NumberType> \ + static void run(); \ + }; \ + struct RUN_PARSE_TEST_##TEST_NAME { \ + template <typename T> \ + void run(TypeTag<T>) const { \ + PARSE_TEST_##TEST_NAME::run<T>(); \ + } \ + } TEST_NAME; \ + template <typename NumberType> \ + void PARSE_TEST_##TEST_NAME::run() + +/* + * The PARSE_TEST macro will generate boilerplate code to enable applying the same function to + * multiple types. + * NumberType is a template parameter representing a type the test is supposed to pass for. + * After writing a PARSE_TEST, there is an object with the name passed in as the parameter. This + * should be passed to the apply function along with a list of types to apply to the function. + */ + +PARSE_TEST(TestParsingNegatives) { + struct Spec { + StringData spec; + int expectedValue; + }; + std::vector<Spec> specs = {{"-0", 0}, {"-10", -10}, {"-0xff", -0xff}}; + if (typeid(NumberType) != typeid(double)) { + specs.push_back({"-077", -077}); // no octals for double + } + for (const auto& s : specs) { + if (std::is_signed_v<NumberType>) { + ASSERT_PARSES(NumberType, s.spec, s.expectedValue); } else { NumberType ignored; - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("-10", &ignored)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("-0xff", &ignored)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("-077", &ignored)); + ASSERT_EQ(ErrorCodes::FailedToParse, NumberParser{}(s.spec, &ignored)); } } +} + +TEST(NumberParser, ParseNegatives) { + apply(TestParsingNegatives, allTypes); +} - static void TestParsingGarbage() { +PARSE_TEST(TestRejectingBadBases) { + struct Spec { + int base; + StringData spec; + }; + std::vector<Spec> specs = {{-1, "0"}, {1, "10"}, {37, "-10"}, {-1, " "}, {37, "f"}, {-1, "^%"}}; + if (typeid(NumberType) == typeid(double)) { + std::vector<Spec> doubleSpecs = { + {8, "0"}, {10, "0"}, {16, "0"}, {36, "0"}, + }; + std::copy(doubleSpecs.begin(), doubleSpecs.end(), std::back_inserter(specs)); + } + for (const auto& s : specs) { NumberType ignored; - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("", &ignored)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString(" ", &ignored)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString(" 10", &ignored)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("15b", &ignored)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("--10", &ignored)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("+-10", &ignored)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("++10", &ignored)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("--10", &ignored)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("0x+10", &ignored)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("0x-10", &ignored)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("0+10", &ignored)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("0-10", &ignored)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("1+10", &ignored)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("1-10", &ignored)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("48*3", &ignored)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("0x", &ignored)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("+", &ignored)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("-", &ignored)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("+0x", &ignored)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("-0x", &ignored)); + ASSERT_EQ(ErrorCodes::BadValue, NumberParser().base(s.base)(s.spec, &ignored)); } +} - static void TestParsingWithExplicitBase() { - NumberType x; - ASSERT_PARSES_WITH_BASE(NumberType, "15b", 16, 0x15b); - ASSERT_PARSES_WITH_BASE(NumberType, "77", 8, 077); - ASSERT_PARSES_WITH_BASE(NumberType, "z", 36, 35); - ASSERT_PARSES_WITH_BASE(NumberType, "09", 10, 9); - ASSERT_PARSES_WITH_BASE(NumberType, "00000000000z0", 36, 35 * 36); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromStringWithBase("1b", 10, &x)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromStringWithBase("80", 8, &x)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromStringWithBase("0X", 16, &x)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromStringWithBase("0x", 16, &x)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromStringWithBase("0x", 8, &x)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromStringWithBase("0X", 8, &x)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromStringWithBase("0x", 10, &x)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromStringWithBase("0X", 10, &x)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromStringWithBase("+0X", 16, &x)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromStringWithBase("+0x", 16, &x)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromStringWithBase("+0x", 8, &x)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromStringWithBase("+0X", 8, &x)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromStringWithBase("+0x", 10, &x)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromStringWithBase("+0X", 10, &x)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromStringWithBase("-0X", 16, &x)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromStringWithBase("-0x", 16, &x)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromStringWithBase("-0x", 8, &x)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromStringWithBase("-0X", 8, &x)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromStringWithBase("-0x", 10, &x)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromStringWithBase("-0X", 10, &x)); +TEST(NumberParser, RejectBadBases) { + apply(TestRejectingBadBases, allTypes); +} + +PARSE_TEST(TestParsingNonNegatives) { + struct { + StringData spec; + int expectedValue; + } specs[] = {{"10", 10}, {"0", 0}, {"1", 1}, {"0xff", 0xff}, {"077", 077}}; + for (const auto[str, expected] : specs) { + ASSERT_PARSES(NumberType, str, expected); } +} - static void TestParsingLimits() { +TEST(NumberParser, ParseNonNegatives) { + apply(TestParsingNonNegatives, allTypes); +} + +PARSE_TEST(TestParsingGarbage) { + NumberType ignored; + StringData garbage[] = {"", " ", " 10", "15b", "--10", "+-10", "++10", + "--10", "0x+10", "0x-10", "0+10", "0-10", "48*3", "0x", + "0X", "+", "-", "+0x", "+0X", "-0X", "-0x"}; + + StringData decimalGarbage[] = {"1.0.1", + "1.0-1", + " 1.0", + "1.0P4", + "1e6 ", + " 1e6", + "1e6 ", + " 1e6", + "0xabcab.defPa", + "1.0\0garbage"_sd}; + for (const auto str : garbage) { + ASSERT_EQ(ErrorCodes::FailedToParse, NumberParser{}(str, &ignored)); + } + if (typeid(NumberType) == typeid(double)) { + for (const auto str : decimalGarbage) { + ASSERT_EQ(ErrorCodes::FailedToParse, NumberParser{}(str, &ignored)); + } + } +} + +TEST(NumberParser, ParseGarbage) { + apply(TestParsingGarbage, allTypes); +} + +PARSE_TEST(TestParsingWithExplicitBase) { + struct { + StringData spec; + int base; + NumberType val; + } passes[] = {{"15b", 16, 0x15b}, + {"77", 8, 077}, + {"z", 36, 35}, + {"09", 10, 9}, + {"00000000000z0", 36, 36 * 35}, + {"1011", 2, 0b1011}, + {"11", 5, 6}}; + for (const auto& s : passes) { + ASSERT_PARSES_WITH_BASE(NumberType, s.spec, s.base, s.val); + } + + struct { + StringData spec; + int base; + } fails[] = {{"1b", 10}, {"80", 8}, {"0X", 16}, {"0x", 16}, {"0X", 8}, {"0x", 8}, + {"0X", 10}, {"0x", 10}, {"+0X", 16}, {"+0x", 16}, {"+0X", 8}, {"+0x", 8}, + {"+0X", 10}, {"+0x", 10}, {"-0X", 16}, {"-0x", 16}, {"-0X", 8}, {"-0x", 8}, + {"-0X", 10}, {"-0x", 10}, {"2", 2}, {"4", 3}}; + for (const auto& s : fails) { NumberType ignored; - ASSERT_PARSES(NumberType, std::string(str::stream() << Limits::max()), Limits::max()); - ASSERT_PARSES(NumberType, std::string(str::stream() << Limits::min()), Limits::min()); - ASSERT_EQUALS( - ErrorCodes::FailedToParse, - parseNumberFromString(std::string(str::stream() << Limits::max() << '0'), &ignored)); - - if (Limits::is_signed) { - // Max + 1 - ASSERT_EQUALS( - ErrorCodes::FailedToParse, - parseNumberFromString(std::to_string(uint64_t(Limits::max()) + 1), &ignored)); - - // Min - 1 (equivalent to -(Max + 2)) - ASSERT_EQUALS( - ErrorCodes::FailedToParse, - parseNumberFromString("-" + std::to_string(uint64_t(Limits::max()) + 2), &ignored)); - - ASSERT_EQUALS(ErrorCodes::FailedToParse, - parseNumberFromString(std::string(str::stream() << Limits::min() << '0'), - &ignored)); + ASSERT_EQ(ErrorCodes::FailedToParse, NumberParser().base(s.base)(s.spec, &ignored)); + } +} + +TEST(NumberParser, ParseWithExplicitBase) { + apply(TestParsingWithExplicitBase, allTypes); +} + +PARSE_TEST(TestParsingLimits) { + using Limits = std::numeric_limits<NumberType>; + NumberType ignored; + ASSERT_PARSES(NumberType, std::string(str::stream() << Limits::max()), Limits::max()); + ASSERT_PARSES(NumberType, std::string(str::stream() << Limits::min()), Limits::min()); + ASSERT_EQUALS(ErrorCodes::Overflow, + NumberParser{}(std::string(str::stream() << Limits::max() << '0'), &ignored)); + + if (std::is_signed_v<NumberType>) { + // Max + 1 + ASSERT_EQUALS(ErrorCodes::Overflow, + NumberParser{}(std::to_string(uint64_t(Limits::max()) + 1), &ignored)); + + // Min - 1 (equivalent to -(Max + 2)) + ASSERT_EQUALS(ErrorCodes::Overflow, + NumberParser{}("-" + std::to_string(uint64_t(Limits::max()) + 2), &ignored)); + + ASSERT_EQUALS(ErrorCodes::Overflow, + NumberParser{}(std::string(str::stream() << Limits::min() << '0'), &ignored)); + } +} + +TEST(NumberParser, ParseLimits) { + apply(TestParsingLimits, allTypes); +} + +PARSE_TEST(TestSkipLeadingWhitespace) { + StringData whitespaces[] = {" ", "", "\t \t", "\r\n\n\t", "\f\v "}; + struct { + StringData spec; + bool is_negative; + } specs[] = {{"10", false}, + {"0", false}, + {"1", false}, + {"0xff", false}, + {"077", false}, + {"-10", true}, + {"-0", true}, + {"-1", true}, + {"-0xff", true}, + {"-077", true}}; + NumberParser defaultParser; + NumberParser skipWs = NumberParser().skipWhitespace(); + for (const auto[numStr, is_negative] : specs) { + NumberType expected; + + bool shouldParse = !is_negative || (is_negative && std::is_signed_v<NumberType>); + Status parsed = defaultParser(numStr, &expected); + + if (shouldParse) { + ASSERT_OK(parsed); + } else { + ASSERT_EQ(ErrorCodes::FailedToParse, parsed); + } + + for (StringData ws : whitespaces) { + std::string withWhitespace = ws.toString() + numStr; + if (shouldParse) { + ASSERT_PARSES_WITH_PARSER(NumberType, withWhitespace, skipWs, expected); + } else { + NumberType actual; + ASSERT_EQ(ErrorCodes::FailedToParse, skipWs(withWhitespace, &actual)); + } } } -}; - -#define GENERAL_NUMBER_TESTS(SHORT_NAME, TYPE) \ - class ParseNumberTests##SHORT_NAME : public unittest::Test { \ - public: \ - typedef CommonNumberParsingTests<TYPE> TestFns; \ - }; \ - TEST_F(ParseNumberTests##SHORT_NAME, RejectBadBases) { \ - TestFns::TestRejectingBadBases(); \ - } \ - TEST_F(ParseNumberTests##SHORT_NAME, ParseNonNegatives) { \ - TestFns::TestParsingNonNegatives(); \ - } \ - TEST_F(ParseNumberTests##SHORT_NAME, ParseNegatives) { \ - TestFns::TestParsingNegatives(); \ - } \ - TEST_F(ParseNumberTests##SHORT_NAME, ParseGarbage) { \ - TestFns::TestParsingGarbage(); \ - } \ - TEST_F(ParseNumberTests##SHORT_NAME, ParseWithExplicitBase) { \ - TestFns::TestParsingWithExplicitBase(); \ - } \ - TEST_F(ParseNumberTests##SHORT_NAME, TestParsingLimits) { \ - TestFns::TestParsingLimits(); \ +} + +TEST(NumberParser, TestSkipLeadingWhitespace) { + apply(TestSkipLeadingWhitespace, allTypes); +} + +PARSE_TEST(TestEndOfNum) { + struct { + StringData spec; + bool is_negative; + } specs[] = {{"10", false}, + {"0", false}, + {"1", false}, + {"0xff", false}, + {"077", false}, + {"-10", true}, + {"-0", true}, + {"-1", true}, + {"-0xff", true}, + {"-077", true}}; + StringData suffixes[] = { + " ", + "\r\t", + "@!()", + " #$", + "Hello World", + "g", // since the largest inferred base is 16, next non-number character will be g + ""}; + NumberParser defaultParser; + for (const auto[numStr, is_negative] : specs) { + NumberType expected; + bool shouldParse = !is_negative || (is_negative && std::is_signed_v<NumberType>); + Status parsed = defaultParser(numStr, &expected); + if (shouldParse) { + ASSERT_OK(parsed); + } else { + ASSERT_EQ(ErrorCodes::FailedToParse, parsed); + } + for (StringData& suffix : suffixes) { + std::string spec = numStr.toString() + suffix; + char* numEnd = nullptr; + NumberType actual; + parsed = NumberParser().allowTrailingText()(spec, &actual, &numEnd); + if (shouldParse) { + ASSERT_OK(parsed); + ASSERT_EQ(actual, expected); + StringData remaining_str{numEnd, suffix.size()}; + ASSERT_TRUE(remaining_str == suffix); + } else { + ASSERT_EQ(ErrorCodes::FailedToParse, parsed); + ASSERT_TRUE(numEnd == spec.c_str()); + } + } } +} -GENERAL_NUMBER_TESTS(Short, short) -GENERAL_NUMBER_TESTS(Int, int) -GENERAL_NUMBER_TESTS(Long, long) -GENERAL_NUMBER_TESTS(LongLong, long long) -GENERAL_NUMBER_TESTS(UnsignedShort, unsigned short) -GENERAL_NUMBER_TESTS(UnsignedInt, unsigned int) -GENERAL_NUMBER_TESTS(UnsignedLong, unsigned long) -GENERAL_NUMBER_TESTS(UnsignedLongLong, unsigned long long) -GENERAL_NUMBER_TESTS(Int16, int16_t); -GENERAL_NUMBER_TESTS(Int32, int32_t); -GENERAL_NUMBER_TESTS(Int64, int64_t); -GENERAL_NUMBER_TESTS(UInt16, uint16_t); -GENERAL_NUMBER_TESTS(UInt32, uint32_t); -GENERAL_NUMBER_TESTS(UInt64, uint64_t); +TEST(NumberParser, TestEndOfNum) { + apply(TestEndOfNum, allTypes); +} + +PARSE_TEST(TestNotNullTerminated) { + StringData noNull{"1234", 3}; + NumberParser parsers[] = {NumberParser(), + NumberParser().skipWhitespace(), + NumberParser().base(10), + NumberParser().allowTrailingText()}; + for (auto& parser : parsers) { + ASSERT_PARSES_WITH_PARSER(NumberType, noNull, parser, 123); + } +} + +TEST(NumberParser, TestNotNullTerminated) { + apply(TestNotNullTerminated, allTypes); +} + +PARSE_TEST(TestSkipLeadingWsAndEndptr) { + struct { + StringData spec; + bool is_negative; + } specs[] = {{"10", false}, + {"0", false}, + {"1", false}, + {"0xff", false}, + {"077", false}, + {"-10", true}, + {"-0", true}, + {"-1", true}, + {"-0xff", true}, + {"-077", true}}; + StringData whitespaces[] = {" ", "", "\t \t", "\r\n\n\t", "\f\v "}; + NumberParser defaultParser; + for (const auto[numStr, is_negative] : specs) { + NumberType expected; + bool shouldParse = !is_negative || (is_negative && std::is_signed_v<NumberType>); + Status parsed = defaultParser(numStr, &expected); + if (shouldParse) { + ASSERT_OK(parsed); + } else { + ASSERT_EQ(ErrorCodes::FailedToParse, parsed); + } + for (StringData& prefix : whitespaces) { + std::string spec = prefix.toString() + numStr; + char* numEnd = nullptr; + NumberType actual; + parsed = NumberParser().skipWhitespace()(spec, &actual, &numEnd); + if (shouldParse) { + ASSERT_OK(parsed); + ASSERT_EQ(actual, expected); + ASSERT_TRUE(numEnd == (spec.c_str() + spec.size())); + } else { + ASSERT_EQ(ErrorCodes::FailedToParse, parsed); + ASSERT_TRUE(StringData(numEnd, spec.size()) == spec.c_str()); + } + } + } +} + +TEST(NumberParser, TestSkipLeadingWsAndEndptr) { + apply(TestSkipLeadingWsAndEndptr, allTypes); +} TEST(ParseNumber, NotNullTerminated) { ASSERT_PARSES(int, StringData("1234", 3), 123); @@ -217,12 +420,12 @@ TEST(ParseNumber, NotNullTerminated) { TEST(ParseNumber, Int8) { int8_t ignored; - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("-129", &ignored)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("-130", &ignored)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("-900", &ignored)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("128", &ignored)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("130", &ignored)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("900", &ignored)); + ASSERT_EQUALS(ErrorCodes::Overflow, NumberParser{}("-129", &ignored)); + ASSERT_EQUALS(ErrorCodes::Overflow, NumberParser{}("-130", &ignored)); + ASSERT_EQUALS(ErrorCodes::Overflow, NumberParser{}("-900", &ignored)); + ASSERT_EQUALS(ErrorCodes::Overflow, NumberParser{}("128", &ignored)); + ASSERT_EQUALS(ErrorCodes::Overflow, NumberParser{}("130", &ignored)); + ASSERT_EQUALS(ErrorCodes::Overflow, NumberParser{}("900", &ignored)); for (int32_t i = -128; i <= 127; ++i) ASSERT_PARSES(int8_t, std::string(str::stream() << i), i); @@ -230,11 +433,11 @@ TEST(ParseNumber, Int8) { TEST(ParseNumber, UInt8) { uint8_t ignored; - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("-129", &ignored)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("-130", &ignored)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("-900", &ignored)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("+256", &ignored)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("+900", &ignored)); + ASSERT_EQUALS(ErrorCodes::FailedToParse, NumberParser{}("-129", &ignored)); + ASSERT_EQUALS(ErrorCodes::FailedToParse, NumberParser{}("-130", &ignored)); + ASSERT_EQUALS(ErrorCodes::FailedToParse, NumberParser{}("-900", &ignored)); + ASSERT_EQUALS(ErrorCodes::Overflow, NumberParser{}("+256", &ignored)); + ASSERT_EQUALS(ErrorCodes::Overflow, NumberParser{}("+900", &ignored)); for (uint32_t i = 0; i <= 255; ++i) ASSERT_PARSES(uint8_t, std::string(str::stream() << i), i); @@ -245,80 +448,75 @@ TEST(ParseNumber, TestParsingOverflow) { // These both have one too many hex digits and will overflow the multiply. The second overflows // such that the truncated result is still greater than either input and can catch overly // simplistic overflow checks. - ASSERT_EQUALS(ErrorCodes::FailedToParse, - parseNumberFromStringWithBase("0xfffffffffffffffff", 16, &u64)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, - parseNumberFromStringWithBase("0x7ffffffffffffffff", 16, &u64)); + ASSERT_EQUALS(ErrorCodes::Overflow, NumberParser().base(16)("0xfffffffffffffffff", &u64)); + ASSERT_EQUALS(ErrorCodes::Overflow, NumberParser().base(16)("0x7ffffffffffffffff", &u64)); // 2**64 exactly. This will overflow the add. - ASSERT_EQUALS(ErrorCodes::FailedToParse, - parseNumberFromStringWithBase("18446744073709551616", 10, &u64)); + ASSERT_EQUALS(ErrorCodes::Overflow, NumberParser().base(10)("18446744073709551616", &u64)); uint32_t u32; // Too large when down-converting. - ASSERT_EQUALS(ErrorCodes::FailedToParse, - parseNumberFromStringWithBase("0xfffffffff", 16, &u32)); + ASSERT_EQUALS(ErrorCodes::Overflow, NumberParser().base(16)("0xfffffffff", &u32)); int32_t i32; // Too large when down-converting. - ASSERT_EQUALS( - ErrorCodes::FailedToParse, - parseNumberFromString(std::to_string(std::numeric_limits<uint32_t>::max()), &i32)); + ASSERT_EQUALS(ErrorCodes::Overflow, + NumberParser{}(std::to_string(std::numeric_limits<uint32_t>::max()), &i32)); } -TEST(Double, TestRejectingBadBases) { - double ignored; +PARSE_TEST(DoubleNormalParse) { + ASSERT_PARSES(NumberType, "10", 10); + ASSERT_PARSES(NumberType, "0", 0); + ASSERT_PARSES(NumberType, "1", 1); + ASSERT_PARSES(NumberType, "-10", -10); + ASSERT_PARSES(NumberType, "1e8", 1e8); + ASSERT_PARSES(NumberType, "1e-8", 1e-8); + ASSERT_PARSES(NumberType, "12e-8", 12e-8); + ASSERT_PARSES(NumberType, "-485.381e-8", -485.381e-8); + +#if !(defined(_WIN32) || defined(__sun)) + // Parse hexadecimal representations of a double. Hex literals not supported by MSVC, and + // not parseable by the Windows SDK libc or the Solaris libc in the mode we build. + // See SERVER-14131. - // Only supported base for parseNumberFromStringWithBase<double> is 0. - ASSERT_EQUALS(ErrorCodes::BadValue, parseNumberFromStringWithBase("0", -1, &ignored)); - ASSERT_EQUALS(ErrorCodes::BadValue, parseNumberFromStringWithBase("0", 1, &ignored)); - ASSERT_EQUALS(ErrorCodes::BadValue, parseNumberFromStringWithBase("0", 8, &ignored)); - ASSERT_EQUALS(ErrorCodes::BadValue, parseNumberFromStringWithBase("0", 10, &ignored)); - ASSERT_EQUALS(ErrorCodes::BadValue, parseNumberFromStringWithBase("0", 16, &ignored)); + ASSERT_PARSES(NumberType, "0xff", 255); + ASSERT_PARSES(NumberType, "-0xff", -255); + ASSERT_PARSES(NumberType, "0xabcab.defdefP-10", 687.16784283419838); +#endif } -TEST(Double, TestParsingGarbage) { - double d; - CommonNumberParsingTests<double>::TestParsingGarbage(); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString<double>("1.0.1", &d)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString<double>("1.0-1", &d)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString<double>(" 1.0", &d)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString<double>("1.0P4", &d)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString<double>("1e6 ", &d)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString<double>(" 1e6", &d)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString<double>("1e6 ", &d)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString<double>(" 1e6", &d)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString<double>("0xabcab.defPa", &d)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString<double>("1.0\0garbage"_sd, &d)); +TEST(NumberParser, TestDoubleNormalParse) { + apply(DoubleNormalParse, TypeListTag<double>{}); } TEST(Double, TestParsingOverflow) { double d; - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("1e309", &d)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("-1e309", &d)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("1e-400", &d)); - ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("-1e-400", &d)); + ASSERT_EQUALS(ErrorCodes::Overflow, NumberParser{}("1e309", &d)); + ASSERT_EQUALS(ErrorCodes::Overflow, NumberParser{}("-1e309", &d)); + ASSERT_EQUALS(ErrorCodes::Overflow, NumberParser{}("1e-400", &d)); + ASSERT_EQUALS(ErrorCodes::Overflow, NumberParser{}("-1e-400", &d)); } TEST(Double, TestParsingNan) { double d = 0; - ASSERT_OK(parseNumberFromString("NaN", &d)); + ASSERT_OK(NumberParser{}("NaN", &d)); + ASSERT_OK(NumberParser{}("nan", &d)); ASSERT_TRUE(std::isnan(d)); } TEST(Double, TestParsingNegativeZero) { double d = 0; - ASSERT_OK(parseNumberFromString("-0.0", &d)); + ASSERT_OK(NumberParser{}("-0.0", &d)); ASSERT_EQ(d, -0.0); ASSERT_TRUE(std::signbit(d)); } TEST(Double, TestParsingInfinity) { double d = 0; - ASSERT_OK(parseNumberFromString("infinity", &d)); + ASSERT_OK(NumberParser{}("infinity", &d)); ASSERT_TRUE(std::isinf(d)); d = 0; - ASSERT_OK(parseNumberFromString("-Infinity", &d)); + ASSERT_OK(NumberParser{}("-Infinity", &d)); ASSERT_TRUE(std::isinf(d)); } diff --git a/src/mongo/bson/bsonelement.cpp b/src/mongo/bson/bsonelement.cpp index d659f84ee13..4820437c359 100644 --- a/src/mongo/bson/bsonelement.cpp +++ b/src/mongo/bson/bsonelement.cpp @@ -36,6 +36,7 @@ #include "mongo/base/compare_numbers.h" #include "mongo/base/data_cursor.h" +#include "mongo/base/parse_number.h" #include "mongo/base/simple_string_data_comparator.h" #include "mongo/db/jsobj.h" #include "mongo/platform/strnlen.h" @@ -160,9 +161,12 @@ void BSONElement::jsonStringStream(JsonStringFormat format, s << " "; } - if (strtol(e.fieldName(), nullptr, 10) > count) { + long index; + if (NumberParser::strToAny(10)(e.fieldName(), &index).isOK() && index > count) { s << "undefined"; } else { + // print the element if its index is being printed or if the index it + // belongs to could not be parsed e.jsonStringStream(format, false, pretty ? pretty + 1 : 0, s); e = i.next(); } @@ -514,7 +518,7 @@ std::vector<BSONElement> BSONElement::Array() const { const char* f = e.fieldName(); unsigned u; - Status status = parseNumberFromString(f, &u); + Status status = NumberParser{}(f, &u); if (status.isOK()) { verify(u < 1000000); if (u >= v.size()) 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 <cstdint> +#include <fmt/format.h> #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<long long>(strtoull(_input, &endptr, 10))); - if (errno == ERANGE) { - return parseError("Date milliseconds overflow"); - } + StatusWith<Date_t> 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<Date_t> 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<long long>(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<Date_t> 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<long long>(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(); diff --git a/src/mongo/bson/json.h b/src/mongo/bson/json.h index 45b08acd095..9b74bce5fbb 100644 --- a/src/mongo/bson/json.h +++ b/src/mongo/bson/json.h @@ -32,7 +32,9 @@ #include <string> #include "mongo/base/status.h" +#include "mongo/base/status_with.h" #include "mongo/bson/bsonobj.h" +#include "mongo/util/time_support.h" namespace mongo { @@ -473,6 +475,12 @@ private: */ Status parseError(StringData msg); + /** + * @returns a valid Date_t or FailedToParse status. + * Updates _input to past the end of the parsed date. + */ + StatusWith<Date_t> parseDate(); + public: inline int offset() { return (_input - _buf); diff --git a/src/mongo/bson/oid_test.cpp b/src/mongo/bson/oid_test.cpp index 6faefa88dfa..54053860396 100644 --- a/src/mongo/bson/oid_test.cpp +++ b/src/mongo/bson/oid_test.cpp @@ -29,6 +29,7 @@ #include "mongo/bson/oid.h" +#include "mongo/base/parse_number.h" #include "mongo/platform/endian.h" #include "mongo/unittest/unittest.h" @@ -161,6 +162,8 @@ TEST(Basic, FromTerm) { auto oidTail = oidStr.substr(oidStr.length() - 1); ASSERT_EQUALS("7fffffff", oidHead); - ASSERT_EQUALS(term, std::stoi(oidTail)); + int oidTailInt; + ASSERT_OK(mongo::NumberParser::strToAny()(oidTail, &oidTailInt)); + ASSERT_EQUALS(term, oidTailInt); } } diff --git a/src/mongo/client/sasl_scram_client_conversation.cpp b/src/mongo/client/sasl_scram_client_conversation.cpp index c5a4b788f32..effd369a4ff 100644 --- a/src/mongo/client/sasl_scram_client_conversation.cpp +++ b/src/mongo/client/sasl_scram_client_conversation.cpp @@ -151,7 +151,7 @@ StatusWith<bool> SaslSCRAMClientConversation::_secondStep(StringData inputData, str::stream() << "Incorrect SCRAM iteration count: " << input[2]); } size_t iterationCount; - Status status = parseNumberFromStringWithBase(input[2].substr(2), 10, &iterationCount); + Status status = NumberParser().base(10)(input[2].substr(2), &iterationCount); if (!status.isOK()) { return Status(ErrorCodes::BadValue, str::stream() << "Failed to parse SCRAM iteration count: " << input[2]); diff --git a/src/mongo/db/commands/parameters.cpp b/src/mongo/db/commands/parameters.cpp index b68630938dc..09e7f52f688 100644 --- a/src/mongo/db/commands/parameters.cpp +++ b/src/mongo/db/commands/parameters.cpp @@ -400,7 +400,7 @@ Status LogLevelServerParameter::set(const BSONElement& newValueElement) { Status LogLevelServerParameter::setFromString(const std::string& strLevel) { int newValue; - Status status = parseNumberFromString(strLevel, &newValue); + Status status = NumberParser{}(strLevel, &newValue); if (!status.isOK()) return status; if (newValue < 0) diff --git a/src/mongo/db/namespace_string.cpp b/src/mongo/db/namespace_string.cpp index 566b23a2604..9a98e3f3dd5 100644 --- a/src/mongo/db/namespace_string.cpp +++ b/src/mongo/db/namespace_string.cpp @@ -175,24 +175,23 @@ StatusWith<repl::OpTime> NamespaceString::getDropPendingNamespaceOpTime() const } long long seconds; - auto status = parseNumberFromString(opTimeStr.substr(0, incrementSeparatorIndex), &seconds); + auto status = NumberParser{}(opTimeStr.substr(0, incrementSeparatorIndex), &seconds); if (!status.isOK()) { return status.withContext( str::stream() << "Invalid timestamp seconds in drop-pending namespace: " << _ns); } unsigned int increment; - status = - parseNumberFromString(opTimeStr.substr(incrementSeparatorIndex + 1, - termSeparatorIndex - (incrementSeparatorIndex + 1)), - &increment); + status = NumberParser{}(opTimeStr.substr(incrementSeparatorIndex + 1, + termSeparatorIndex - (incrementSeparatorIndex + 1)), + &increment); if (!status.isOK()) { return status.withContext( str::stream() << "Invalid timestamp increment in drop-pending namespace: " << _ns); } long long term; - status = mongo::parseNumberFromString(opTimeStr.substr(termSeparatorIndex + 1), &term); + status = mongo::NumberParser{}(opTimeStr.substr(termSeparatorIndex + 1), &term); if (!status.isOK()) { return status.withContext(str::stream() << "Invalid term in drop-pending namespace: " << _ns); diff --git a/src/mongo/db/pipeline/expression.cpp b/src/mongo/db/pipeline/expression.cpp index 8dfd63eb547..8b641f35fda 100644 --- a/src/mongo/db/pipeline/expression.cpp +++ b/src/mongo/db/pipeline/expression.cpp @@ -5527,14 +5527,14 @@ private: targetType result; // Reject any strings in hex format. This check is needed because the - // parseNumberFromStringWithBase call below allows an input hex string prefixed by '0x' when + // NumberParser::parse call below allows an input hex string prefixed by '0x' when // parsing to a double. uassert(ErrorCodes::ConversionFailure, str::stream() << "Illegal hexadecimal input in $convert with no onError value: " << stringValue, !stringValue.startsWith("0x")); - Status parseStatus = parseNumberFromStringWithBase(stringValue, base, &result); + Status parseStatus = NumberParser().base(base)(stringValue, &result); uassert(ErrorCodes::ConversionFailure, str::stream() << "Failed to parse number '" << stringValue << "' in $convert with no onError value: " diff --git a/src/mongo/db/pipeline/expression_convert_test.cpp b/src/mongo/db/pipeline/expression_convert_test.cpp index a0a5c2d4a64..acee5cf618b 100644 --- a/src/mongo/db/pipeline/expression_convert_test.cpp +++ b/src/mongo/db/pipeline/expression_convert_test.cpp @@ -2172,7 +2172,8 @@ TEST_F(ExpressionConvertTest, ConvertStringToLongFailsForFloats) { AssertionException, [](const AssertionException& exception) { ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure); - ASSERT_STRING_CONTAINS(exception.reason(), "Bad digit \".\""); + ASSERT_STRING_CONTAINS(exception.reason(), + "Did not consume whole string"); }); spec = fromjson("{$convert: {input: '5.0', to: 'long'}}"); @@ -2181,7 +2182,8 @@ TEST_F(ExpressionConvertTest, ConvertStringToLongFailsForFloats) { AssertionException, [](const AssertionException& exception) { ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure); - ASSERT_STRING_CONTAINS(exception.reason(), "Bad digit \".\""); + ASSERT_STRING_CONTAINS(exception.reason(), + "Did not consume whole string"); }); } @@ -2274,7 +2276,7 @@ TEST_F(ExpressionConvertTest, ConvertStringToDoubleFailsForInvalidFloats) { [](const AssertionException& exception) { ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure); ASSERT_STRING_CONTAINS(exception.reason(), - "Did not consume whole number"); + "Did not consume whole string"); }); spec = fromjson("{$convert: {input: '5.5f', to: 'double'}}"); @@ -2284,7 +2286,7 @@ TEST_F(ExpressionConvertTest, ConvertStringToDoubleFailsForInvalidFloats) { [](const AssertionException& exception) { ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure); ASSERT_STRING_CONTAINS(exception.reason(), - "Did not consume whole number"); + "Did not consume whole string"); }); } @@ -2762,7 +2764,7 @@ TEST_F(ExpressionConvertTest, ConvertStringToNumberFailsForHexStrings) { [](const AssertionException& exception) { ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure); ASSERT_STRING_CONTAINS(exception.reason(), - "Did not consume whole number"); + "Did not consume any digits"); }); spec = fromjson("{$convert: {input: 'FF', to: 'decimal'}}"); diff --git a/src/mongo/db/query/datetime/date_time_support.cpp b/src/mongo/db/query/datetime/date_time_support.cpp index 0d8b8f0c684..6ab2a93c976 100644 --- a/src/mongo/db/query/datetime/date_time_support.cpp +++ b/src/mongo/db/query/datetime/date_time_support.cpp @@ -320,7 +320,7 @@ boost::optional<Seconds> TimeZoneDatabase::parseUtcOffset(StringData offsetSpec) // ±HH if (offsetSpec.size() == 3 && isdigit(offsetSpec[1]) && isdigit(offsetSpec[2])) { int offset; - if (parseNumberFromStringWithBase(offsetSpec.substr(1, 2), 10, &offset).isOK()) { + if (NumberParser().base(10)(offsetSpec.substr(1, 2), &offset).isOK()) { return duration_cast<Seconds>(Hours(bias * offset)); } return boost::none; @@ -330,7 +330,7 @@ boost::optional<Seconds> TimeZoneDatabase::parseUtcOffset(StringData offsetSpec) if (offsetSpec.size() == 5 && isdigit(offsetSpec[1]) && isdigit(offsetSpec[2]) && isdigit(offsetSpec[3]) && isdigit(offsetSpec[4])) { int offset; - if (parseNumberFromStringWithBase(offsetSpec.substr(1, 4), 10, &offset).isOK()) { + if (NumberParser().base(10)(offsetSpec.substr(1, 4), &offset).isOK()) { return duration_cast<Seconds>(Hours(bias * (offset / 100L)) + Minutes(bias * (offset % 100))); } @@ -341,10 +341,10 @@ boost::optional<Seconds> TimeZoneDatabase::parseUtcOffset(StringData offsetSpec) if (offsetSpec.size() == 6 && isdigit(offsetSpec[1]) && isdigit(offsetSpec[2]) && offsetSpec[3] == ':' && isdigit(offsetSpec[4]) && isdigit(offsetSpec[5])) { int hourOffset, minuteOffset; - if (!parseNumberFromStringWithBase(offsetSpec.substr(1, 2), 10, &hourOffset).isOK()) { + if (!NumberParser().base(10)(offsetSpec.substr(1, 2), &hourOffset).isOK()) { return boost::none; } - if (!parseNumberFromStringWithBase(offsetSpec.substr(4, 2), 10, &minuteOffset).isOK()) { + if (!NumberParser().base(10)(offsetSpec.substr(4, 2), &minuteOffset).isOK()) { return boost::none; } return duration_cast<Seconds>(Hours(bias * hourOffset) + Minutes(bias * minuteOffset)); diff --git a/src/mongo/db/query/parsed_distinct.cpp b/src/mongo/db/query/parsed_distinct.cpp index 9ade1492a5d..ca72257d6d3 100644 --- a/src/mongo/db/query/parsed_distinct.cpp +++ b/src/mongo/db/query/parsed_distinct.cpp @@ -119,7 +119,7 @@ std::string getProjectedDottedField(const std::string& field, bool* isIDOut) { // with a number, the number cannot be an array index. int arrayIndex = 0; for (size_t i = 1; i < res.size(); ++i) { - if (mongo::parseNumberFromStringWithBase(res[i], 10, &arrayIndex).isOK()) { + if (mongo::NumberParser().base(10)(res[i], &arrayIndex).isOK()) { // Array indices cannot be negative numbers (this is not $slice). // Negative numbers are allowed as field names. if (arrayIndex >= 0) { diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.cpp index 71f8a9b29ab..4f78bcaf68a 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.cpp +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.cpp @@ -468,7 +468,7 @@ void OpenWriteTransactionParam::append(OperationContext* opCtx, Status OpenWriteTransactionParam::setFromString(const std::string& str) { int num = 0; - Status status = parseNumberFromString(str, &num); + Status status = NumberParser{}(str, &num); if (!status.isOK()) { return status; } @@ -489,7 +489,7 @@ void OpenReadTransactionParam::append(OperationContext* opCtx, Status OpenReadTransactionParam::setFromString(const std::string& str) { int num = 0; - Status status = parseNumberFromString(str, &num); + Status status = NumberParser{}(str, &num); if (!status.isOK()) { return status; } @@ -655,7 +655,7 @@ WiredTigerKVEngine::WiredTigerKVEngine(const std::string& canonicalName, invariantWTOK(_conn->query_timestamp(_conn, buf, "get=recovery")); std::uint64_t tmp; - fassert(50758, parseNumberFromStringWithBase(buf, 16, &tmp)); + fassert(50758, NumberParser().base(16)(buf, &tmp)); _recoveryTimestamp = Timestamp(tmp); LOG_FOR_RECOVERY(0) << "WiredTiger recoveryTimestamp. Ts: " << _recoveryTimestamp; } @@ -1792,7 +1792,7 @@ Timestamp WiredTigerKVEngine::getOldestOpenReadTimestamp() const { } uint64_t tmp; - fassert(38802, parseNumberFromStringWithBase(buf, 16, &tmp)); + fassert(38802, NumberParser().base(16)(buf, &tmp)); return Timestamp(tmp); } @@ -1963,7 +1963,7 @@ std::uint64_t WiredTigerKVEngine::_getCheckpointTimestamp() const { invariantWTOK(_conn->query_timestamp(_conn, buf, "get=last_checkpoint")); std::uint64_t tmp; - fassert(50963, parseNumberFromStringWithBase(buf, 16, &tmp)); + fassert(50963, NumberParser().base(16)(buf, &tmp)); return tmp; } diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_oplog_manager.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_oplog_manager.cpp index ff45337699d..28dd48f261f 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_oplog_manager.cpp +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_oplog_manager.cpp @@ -263,7 +263,7 @@ uint64_t WiredTigerOplogManager::fetchAllCommittedValue(WT_CONNECTION* conn) { } uint64_t tmp; - fassert(38002, parseNumberFromStringWithBase(buf, 16, &tmp)); + fassert(38002, NumberParser().base(16)(buf, &tmp)); return tmp; } diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_parameters.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_parameters.cpp index 5cda75b3c2f..4901b30d631 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_parameters.cpp +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_parameters.cpp @@ -124,7 +124,7 @@ Status WiredTigerMaxCacheOverflowSizeGBParameter::set(const BSONElement& element Status WiredTigerMaxCacheOverflowSizeGBParameter::setFromString(const std::string& str) { double value; - const auto status = parseNumberFromString(str, &value); + const auto status = NumberParser{}(str, &value); if (!status.isOK()) { return status; } diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit.cpp index 6998b790318..3f26c7b8614 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit.cpp +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit.cpp @@ -594,7 +594,7 @@ Timestamp WiredTigerRecoveryUnit::_getTransactionReadTimestamp(WT_SESSION* sessi auto wtstatus = session->query_timestamp(session, buf, "get=read"); invariantWTOK(wtstatus); uint64_t read_timestamp; - fassert(50949, parseNumberFromStringWithBase(buf, 16, &read_timestamp)); + fassert(50949, NumberParser().base(16)(buf, &read_timestamp)); return Timestamp(read_timestamp); } diff --git a/src/mongo/idl/server_parameter_specialized_test.cpp b/src/mongo/idl/server_parameter_specialized_test.cpp index 07ce7e4dc8c..7bf9cdb6bc6 100644 --- a/src/mongo/idl/server_parameter_specialized_test.cpp +++ b/src/mongo/idl/server_parameter_specialized_test.cpp @@ -144,7 +144,7 @@ void SpecializedWithValueServerParameter::append(OperationContext*, } Status SpecializedWithValueServerParameter::setFromString(const std::string& value) { - return parseNumberFromString(value, &_data); + return NumberParser{}(value, &_data); } TEST(SpecializedServerParameter, withValue) { @@ -195,7 +195,7 @@ void SpecializedWithAtomicValueServerParameter::append(OperationContext*, Status SpecializedWithAtomicValueServerParameter::setFromString(const std::string& value) { std::uint32_t val; - auto status = parseNumberFromString(value, &val); + auto status = NumberParser{}(value, &val); if (!status.isOK()) { return status; } @@ -275,7 +275,7 @@ void SpecializedWithCtorAndValueServerParameter::append(OperationContext*, } Status SpecializedWithCtorAndValueServerParameter::setFromString(const std::string& value) { - return parseNumberFromString(value, &_data); + return NumberParser{}(value, &_data); } TEST(SpecializedServerParameter, withCtorAndValue) { diff --git a/src/mongo/idl/server_parameter_with_storage.h b/src/mongo/idl/server_parameter_with_storage.h index 0da90e950f7..30d1d4abc7d 100644 --- a/src/mongo/idl/server_parameter_with_storage.h +++ b/src/mongo/idl/server_parameter_with_storage.h @@ -38,6 +38,7 @@ #include <functional> #include <string> +#include "mongo/base/parse_number.h" #include "mongo/base/status.h" #include "mongo/base/string_data.h" #include "mongo/bson/bsonelement.h" @@ -54,7 +55,7 @@ namespace idl_server_parameter_detail { template <typename T> inline StatusWith<T> coerceFromString(StringData str) { T value; - Status status = parseNumberFromString(str, &value); + Status status = NumberParser{}(str, &value); if (!status.isOK()) { return status; } diff --git a/src/mongo/platform/decimal128.cpp b/src/mongo/platform/decimal128.cpp index 18d59af7490..0137616fd04 100644 --- a/src/mongo/platform/decimal128.cpp +++ b/src/mongo/platform/decimal128.cpp @@ -62,7 +62,9 @@ std::string toAsciiLowerCase(mongo::StringData input) { return res; } -void validateInputString(mongo::StringData input, std::uint32_t* signalingFlags) { +// Returns the number of characters consumed from input string. If unable to parse, +// it returns 0. +size_t validateInputString(mongo::StringData input, std::uint32_t* signalingFlags) { // Input must be of these forms: // * Valid decimal (standard or scientific notation): // /[-+]?\d*(.\d+)?([e][+\-]?\d+)?/ @@ -73,35 +75,36 @@ void validateInputString(mongo::StringData input, std::uint32_t* signalingFlags) // Check for NaN and Infinity size_t start = (isSigned) ? 1 : 0; + size_t charsConsumed = start; mongo::StringData noSign = input.substr(start); bool isNanOrInf = noSign == "nan" || noSign == "inf" || noSign == "infinity"; if (isNanOrInf) - return; + return start + noSign.size(); // Input starting with non digit if (!std::isdigit(noSign[0])) { if (noSign[0] != '.') { *signalingFlags = mongo::Decimal128::SignalingFlag::kInvalid; - return; + return 0; } else if (noSign.size() == 1) { *signalingFlags = mongo::Decimal128::SignalingFlag::kInvalid; - return; + return 0; } } bool isZero = true; bool hasCoefficient = false; // Check coefficient, i.e. the part before the e - int dotCount = 0; + bool parsedDot = false; size_t i = 0; for (/*i = 0*/; i < noSign.size(); i++) { char c = noSign[i]; if (c == '.') { - dotCount++; - if (dotCount > 1) { + if (parsedDot) { *signalingFlags = mongo::Decimal128::SignalingFlag::kInvalid; - return; + return 0; } + parsedDot = true; } else if (!std::isdigit(c)) { break; } else { @@ -111,6 +114,7 @@ void validateInputString(mongo::StringData input, std::uint32_t* signalingFlags) } } } + charsConsumed += i; if (isZero) { // Override inexact/overflow flag set by the intel library @@ -119,13 +123,13 @@ void validateInputString(mongo::StringData input, std::uint32_t* signalingFlags) // Input is valid if we've parsed the entire string if (i == noSign.size()) { - return; + return charsConsumed; } // String with empty coefficient and non-empty exponent if (!hasCoefficient) { *signalingFlags = mongo::Decimal128::SignalingFlag::kInvalid; - return; + return 0; } // Check exponent @@ -133,25 +137,29 @@ void validateInputString(mongo::StringData input, std::uint32_t* signalingFlags) if (exponent[0] != 'e' || exponent.size() < 2) { *signalingFlags = mongo::Decimal128::SignalingFlag::kInvalid; - return; + return 0; } if (exponent[1] == '-' || exponent[1] == '+') { exponent = exponent.substr(2); if (exponent.size() == 0) { *signalingFlags = mongo::Decimal128::SignalingFlag::kInvalid; - return; + return 0; } + charsConsumed += 2; } else { exponent = exponent.substr(1); + ++charsConsumed; } for (size_t j = 0; j < exponent.size(); j++) { char c = exponent[j]; if (!std::isdigit(c)) { *signalingFlags = mongo::Decimal128::SignalingFlag::kInvalid; - return; + return 0; } + ++charsConsumed; } + return charsConsumed; } } // namespace @@ -307,20 +315,23 @@ Decimal128::Decimal128(double doubleValue, invariant(getCoefficientLow() <= kLargest15DigitInt); } -Decimal128::Decimal128(std::string stringValue, RoundingMode roundMode) { +Decimal128::Decimal128(std::string stringValue, RoundingMode roundMode, size_t* charsConsumed) { std::uint32_t throwAwayFlag = 0; - *this = Decimal128(stringValue, &throwAwayFlag, roundMode); + *this = Decimal128(stringValue, &throwAwayFlag, roundMode, charsConsumed); } Decimal128::Decimal128(std::string stringValue, std::uint32_t* signalingFlags, - RoundingMode roundMode) { + RoundingMode roundMode, + size_t* charsConsumed) { std::string lower = toAsciiLowerCase(stringValue); BID_UINT128 dec128; // The intel library function requires a char * while c_str() returns a const char*. // We're using const_cast here since the library function should not modify the input. dec128 = bid128_from_string(const_cast<char*>(lower.c_str()), roundMode, signalingFlags); - validateInputString(StringData(lower), signalingFlags); + size_t consumed = validateInputString(lower, signalingFlags); + if (charsConsumed) + *charsConsumed = consumed; _value = libraryTypeToValue(dec128); } diff --git a/src/mongo/platform/decimal128.h b/src/mongo/platform/decimal128.h index 7b96d8e2611..052c7a020d2 100644 --- a/src/mongo/platform/decimal128.h +++ b/src/mongo/platform/decimal128.h @@ -198,11 +198,14 @@ public: * "200E9999999999" --> +Inf * "-200E9999999999" --> -Inf */ - explicit Decimal128(std::string stringValue, RoundingMode roundMode = kRoundTiesToEven); + explicit Decimal128(std::string stringValue, + RoundingMode roundMode = kRoundTiesToEven, + size_t* charsConsumed = nullptr); Decimal128(std::string stringValue, std::uint32_t* signalingFlag, - RoundingMode roundMode = kRoundTiesToEven); + RoundingMode roundMode = kRoundTiesToEven, + size_t* charsConsumed = nullptr); /** * This function gets the inner Value struct storing a Decimal128 value. diff --git a/src/mongo/s/commands/cluster_kill_op.cpp b/src/mongo/s/commands/cluster_kill_op.cpp index 91cdc8f1e91..c22cbe06ef9 100644 --- a/src/mongo/s/commands/cluster_kill_op.cpp +++ b/src/mongo/s/commands/cluster_kill_op.cpp @@ -103,7 +103,7 @@ private: auto shard = shardStatus.getValue(); int opId; - uassertStatusOK(parseNumberFromStringWithBase(opToKill.substr(opSepPos + 1), 10, &opId)); + uassertStatusOK(NumberParser().base(10)(opToKill.substr(opSepPos + 1), &opId)); // shardid is actually the opid - keeping for backwards compatibility. result.append("shard", shardIdent); diff --git a/src/mongo/scripting/mozjs/numberlong.cpp b/src/mongo/scripting/mozjs/numberlong.cpp index 32163e6c31e..0fbc1aee999 100644 --- a/src/mongo/scripting/mozjs/numberlong.cpp +++ b/src/mongo/scripting/mozjs/numberlong.cpp @@ -173,8 +173,8 @@ void NumberLongInfo::construct(JSContext* cx, JS::CallArgs args) { // values to fail rather than return 0 (which is the behavior of ToInt64). std::string str = ValueWriter(cx, arg).toString(); - // Call parseNumberFromStringWithBase() function to convert string to a number - Status status = parseNumberFromStringWithBase(str, 10, &numLong); + // Call NumberParser() function to convert string to a number + Status status = NumberParser{}.base(10)(str, &numLong); uassert(ErrorCodes::BadValue, "could not convert string to long long", status.isOK()); } else { numLong = ValueWriter(cx, arg).toInt64(); diff --git a/src/mongo/shell/shell_utils_launcher.cpp b/src/mongo/shell/shell_utils_launcher.cpp index 60a78f9a38d..8cdedda01e6 100644 --- a/src/mongo/shell/shell_utils_launcher.cpp +++ b/src/mongo/shell/shell_utils_launcher.cpp @@ -314,7 +314,8 @@ ProgramRunner::ProgramRunner(const BSONObj& args, const BSONObj& env, bool isMon if (str == "--port") { _port = -2; } else if (_port == -2) { - _port = strtol(str.c_str(), nullptr, 10); + if (!NumberParser::strToAny(10)(str, &_port).isOK()) + _port = 0; // same behavior as strtol } else if (isMongodProgram && str == "--configsvr") { _name = "c"; } diff --git a/src/mongo/util/net/hostandport.cpp b/src/mongo/util/net/hostandport.cpp index eb5e617841f..927754c4421 100644 --- a/src/mongo/util/net/hostandport.cpp +++ b/src/mongo/util/net/hostandport.cpp @@ -195,7 +195,7 @@ Status HostAndPort::initialize(StringData s) { int port; if (colonPos != std::string::npos) { const StringData portPart = s.substr(colonPos + 1); - Status status = parseNumberFromStringWithBase(portPart, 10, &port); + Status status = NumberParser().base(10)(portPart, &port); if (!status.isOK()) { return status; } diff --git a/src/mongo/util/options_parser/options_parser.cpp b/src/mongo/util/options_parser/options_parser.cpp index 462fccde4e2..76fdc7fd42a 100644 --- a/src/mongo/util/options_parser/options_parser.cpp +++ b/src/mongo/util/options_parser/options_parser.cpp @@ -151,7 +151,7 @@ Status stringToValue(const std::string& stringVal, return Status(ErrorCodes::BadValue, sb.str()); } case Double: - ret = parseNumberFromString(stringVal, &doubleVal); + ret = NumberParser{}(stringVal, &doubleVal); if (!ret.isOK()) { StringBuilder sb; sb << "Error parsing option \"" << key << "\" as double in: " << ret.reason(); @@ -160,7 +160,7 @@ Status stringToValue(const std::string& stringVal, *value = Value(doubleVal); return Status::OK(); case Int: - ret = parseNumberFromString(stringVal, &intVal); + ret = NumberParser{}(stringVal, &intVal); if (!ret.isOK()) { StringBuilder sb; sb << "Error parsing option \"" << key << "\" as int: " << ret.reason(); @@ -169,7 +169,7 @@ Status stringToValue(const std::string& stringVal, *value = Value(intVal); return Status::OK(); case Long: - ret = parseNumberFromString(stringVal, &longVal); + ret = NumberParser{}(stringVal, &longVal); if (!ret.isOK()) { StringBuilder sb; sb << "Error parsing option \"" << key << "\" as long: " << ret.reason(); @@ -181,7 +181,7 @@ Status stringToValue(const std::string& stringVal, *value = Value(stringVal); return Status::OK(); case UnsignedLongLong: - ret = parseNumberFromString(stringVal, &unsignedLongLongVal); + ret = NumberParser{}(stringVal, &unsignedLongLongVal); if (!ret.isOK()) { StringBuilder sb; sb << "Error parsing option \"" << key @@ -191,7 +191,7 @@ Status stringToValue(const std::string& stringVal, *value = Value(unsignedLongLongVal); return Status::OK(); case Unsigned: - ret = parseNumberFromString(stringVal, &unsignedVal); + ret = NumberParser{}(stringVal, &unsignedVal); if (!ret.isOK()) { StringBuilder sb; sb << "Error parsing option \"" << key << "\" as unsigned int: " << ret.reason(); diff --git a/src/mongo/util/processinfo_linux.cpp b/src/mongo/util/processinfo_linux.cpp index 32e98f7fc1d..478851ec91c 100644 --- a/src/mongo/util/processinfo_linux.cpp +++ b/src/mongo/util/processinfo_linux.cpp @@ -404,7 +404,7 @@ public: meminfo = meminfo.substr(lineOff); unsigned long long systemMem = 0; - if (mongo::parseNumberFromString(meminfo, &systemMem).isOK()) { + if (mongo::NumberParser{}(meminfo, &systemMem).isOK()) { return systemMem * 1024; // convert from kB to bytes } else log() << "Unable to collect system memory information"; @@ -422,8 +422,7 @@ public: unsigned long long systemMemBytes = getSystemMemorySize(); unsigned long long cgroupMemBytes = 0; std::string cgmemlimit = readLineFromFile("/sys/fs/cgroup/memory/memory.limit_in_bytes"); - if (!cgmemlimit.empty() && - mongo::parseNumberFromString(cgmemlimit, &cgroupMemBytes).isOK()) { + if (!cgmemlimit.empty() && mongo::NumberParser{}(cgmemlimit, &cgroupMemBytes).isOK()) { return std::min(systemMemBytes, cgroupMemBytes); } return systemMemBytes; diff --git a/src/mongo/util/processinfo_solaris.cpp b/src/mongo/util/processinfo_solaris.cpp index 91f73e41dd9..9d7d66f9891 100644 --- a/src/mongo/util/processinfo_solaris.cpp +++ b/src/mongo/util/processinfo_solaris.cpp @@ -172,9 +172,9 @@ void ProcessInfo::SystemInfo::collectSystemInfo() { if (versionComponents.size() > 1) { unsigned majorInt, minorInt; - Status majorStatus = parseNumberFromString<unsigned>(versionComponents[0], &majorInt); + Status majorStatus = NumberParser{}(versionComponents[0], &majorInt); - Status minorStatus = parseNumberFromString<unsigned>(versionComponents[1], &minorInt); + Status minorStatus = NumberParser{}(versionComponents[1], &minorInt); if (!majorStatus.isOK() || !minorStatus.isOK()) { warning() << "Could not parse OS version numbers from uname: " << osVersion; diff --git a/src/mongo/util/procparser.cpp b/src/mongo/util/procparser.cpp index 8e6b203da12..630e2888eee 100644 --- a/src/mongo/util/procparser.cpp +++ b/src/mongo/util/procparser.cpp @@ -263,7 +263,7 @@ Status parseProcStat(const std::vector<StringData>& keys, uint64_t value; - if (!parseNumberFromString(stringValue, &value).isOK()) { + if (!NumberParser{}(stringValue, &value).isOK()) { value = 0; } @@ -275,7 +275,7 @@ Status parseProcStat(const std::vector<StringData>& keys, uint64_t value; - if (!parseNumberFromString(stringValue, &value).isOK()) { + if (!NumberParser{}(stringValue, &value).isOK()) { value = 0; } @@ -368,7 +368,7 @@ Status parseProcMemInfo(const std::vector<StringData>& keys, uint64_t value; - if (!parseNumberFromString(stringValue, &value).isOK()) { + if (!NumberParser{}(stringValue, &value).isOK()) { value = 0; } @@ -478,7 +478,7 @@ Status parseProcNetstat(const std::vector<StringData>& keys, StringData key((*keysIt).begin(), (*keysIt).end()); StringData stringValue((*valuesIt).begin(), (*valuesIt).end()); uint64_t value; - if (parseNumberFromString(stringValue, &value).isOK()) { + if (NumberParser{}(stringValue, &value).isOK()) { builder->appendNumber(prefix.toString() + key.toString(), static_cast<long long>(value)); foundKeys = true; @@ -600,7 +600,7 @@ Status parseProcDiskStats(const std::vector<StringData>& disks, uint64_t value; - if (!parseNumberFromString(stringValue, &value).isOK()) { + if (!NumberParser{}(stringValue, &value).isOK()) { value = 0; } diff --git a/src/mongo/util/str.cpp b/src/mongo/util/str.cpp index 0f04497f95f..440a15f1f15 100644 --- a/src/mongo/util/str.cpp +++ b/src/mongo/util/str.cpp @@ -229,7 +229,7 @@ boost::optional<size_t> parseUnsignedBase10Integer(StringData fieldName) { return boost::none; } unsigned int index; - auto status = parseNumberFromStringWithBase<unsigned int>(fieldName, 10, &index); + auto status = NumberParser().base(10)(fieldName, &index); if (status.isOK()) { return static_cast<size_t>(index); } diff --git a/src/mongo/util/tcmalloc_set_parameter.cpp b/src/mongo/util/tcmalloc_set_parameter.cpp index 8fa4a5f0c37..b7dd65fe752 100644 --- a/src/mongo/util/tcmalloc_set_parameter.cpp +++ b/src/mongo/util/tcmalloc_set_parameter.cpp @@ -112,7 +112,7 @@ StatusWith<size_t> validateTCMallocValue(StringData name, const BSONElement& new } \ Status TCMalloc##cls##ServerParameter::setFromString(const std::string& str) { \ size_t value; \ - Status status = parseNumberFromString(str, &value); \ + Status status = NumberParser{}(str, &value); \ if (!status.isOK()) { \ return status; \ } \ diff --git a/src/mongo/util/time_support.cpp b/src/mongo/util/time_support.cpp index 77277b56cb2..2dfb361829a 100644 --- a/src/mongo/util/time_support.cpp +++ b/src/mongo/util/time_support.cpp @@ -349,10 +349,10 @@ Status parseTimeZoneFromToken(StringData tzStr, int* tzAdjSecs) { } // Parse the hours component of the time zone offset. Note that - // parseNumberFromStringWithBase correctly handles the sign bit, so leave that in. + // NumberParser correctly handles the sign bit, so leave that in. StringData tzHoursStr = tzStr.substr(0, 3); int tzAdjHours = 0; - Status status = parseNumberFromStringWithBase(tzHoursStr, 10, &tzAdjHours); + Status status = NumberParser().base(10)(tzHoursStr, &tzAdjHours); if (!status.isOK()) { return status; } @@ -365,7 +365,7 @@ Status parseTimeZoneFromToken(StringData tzStr, int* tzAdjSecs) { StringData tzMinutesStr = tzStr.substr(3, 2); int tzAdjMinutes = 0; - status = parseNumberFromStringWithBase(tzMinutesStr, 10, &tzAdjMinutes); + status = NumberParser().base(10)(tzMinutesStr, &tzAdjMinutes); if (!status.isOK()) { return status; } @@ -376,7 +376,7 @@ Status parseTimeZoneFromToken(StringData tzStr, int* tzAdjSecs) { return Status(ErrorCodes::BadValue, sb.str()); } - // Use the sign that parseNumberFromStringWithBase found to determine if we need to + // Use the sign that NumberParser::parse found to determine if we need to // flip the sign of our minutes component. Also, we need to flip the sign of our // final result, because the offset passed in by the user represents how far off the // time they are giving us is from UTC, which means that we have to go the opposite @@ -411,7 +411,7 @@ Status parseMillisFromToken(StringData millisStr, int* resultMillis) { return Status(ErrorCodes::BadValue, sb.str()); } - Status status = parseNumberFromStringWithBase(millisStr, 10, resultMillis); + Status status = NumberParser().base(10)(millisStr, resultMillis); if (!status.isOK()) { return status; } @@ -453,7 +453,7 @@ Status parseTmFromTokens(StringData yearStr, return Status(ErrorCodes::BadValue, sb.str()); } - Status status = parseNumberFromStringWithBase(yearStr, 10, &resultTm->tm_year); + Status status = NumberParser().base(10)(yearStr, &resultTm->tm_year); if (!status.isOK()) { return status; } @@ -473,7 +473,7 @@ Status parseTmFromTokens(StringData yearStr, return Status(ErrorCodes::BadValue, sb.str()); } - status = parseNumberFromStringWithBase(monthStr, 10, &resultTm->tm_mon); + status = NumberParser().base(10)(monthStr, &resultTm->tm_mon); if (!status.isOK()) { return status; } @@ -493,7 +493,7 @@ Status parseTmFromTokens(StringData yearStr, return Status(ErrorCodes::BadValue, sb.str()); } - status = parseNumberFromStringWithBase(dayStr, 10, &resultTm->tm_mday); + status = NumberParser().base(10)(dayStr, &resultTm->tm_mday); if (!status.isOK()) { return status; } @@ -511,7 +511,7 @@ Status parseTmFromTokens(StringData yearStr, return Status(ErrorCodes::BadValue, sb.str()); } - status = parseNumberFromStringWithBase(hourStr, 10, &resultTm->tm_hour); + status = NumberParser().base(10)(hourStr, &resultTm->tm_hour); if (!status.isOK()) { return status; } @@ -529,7 +529,7 @@ Status parseTmFromTokens(StringData yearStr, return Status(ErrorCodes::BadValue, sb.str()); } - status = parseNumberFromStringWithBase(minStr, 10, &resultTm->tm_min); + status = NumberParser().base(10)(minStr, &resultTm->tm_min); if (!status.isOK()) { return status; } @@ -551,7 +551,7 @@ Status parseTmFromTokens(StringData yearStr, return Status(ErrorCodes::BadValue, sb.str()); } - status = parseNumberFromStringWithBase(secStr, 10, &resultTm->tm_sec); + status = NumberParser().base(10)(secStr, &resultTm->tm_sec); if (!status.isOK()) { return status; } |