summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
authorNathan Brown <nathan.brown@10gen.com>2019-06-06 10:39:58 -0400
committerNathan Brown <nathan.brown@10gen.com>2019-06-27 10:04:18 -0400
commit0fefcf84347a2bcc4961baec0295b9d04047d86f (patch)
treece86921e5b14ee83a00400f4f4e8712093505cb8 /src/mongo
parenta700238800aa2bf1e10c255337ec35373ffd2667 (diff)
downloadmongo-0fefcf84347a2bcc4961baec0295b9d04047d86f.tar.gz
SERVER-7143 replace standard library number parsing with custom NumberParser
Diffstat (limited to 'src/mongo')
-rw-r--r--src/mongo/base/parse_number.cpp160
-rw-r--r--src/mongo/base/parse_number.h105
-rw-r--r--src/mongo/base/parse_number_test.cpp594
-rw-r--r--src/mongo/bson/bsonelement.cpp8
-rw-r--r--src/mongo/bson/json.cpp175
-rw-r--r--src/mongo/bson/json.h8
-rw-r--r--src/mongo/bson/oid_test.cpp5
-rw-r--r--src/mongo/client/sasl_scram_client_conversation.cpp2
-rw-r--r--src/mongo/db/commands/parameters.cpp2
-rw-r--r--src/mongo/db/namespace_string.cpp11
-rw-r--r--src/mongo/db/pipeline/expression.cpp4
-rw-r--r--src/mongo/db/pipeline/expression_convert_test.cpp12
-rw-r--r--src/mongo/db/query/datetime/date_time_support.cpp8
-rw-r--r--src/mongo/db/query/parsed_distinct.cpp2
-rw-r--r--src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.cpp10
-rw-r--r--src/mongo/db/storage/wiredtiger/wiredtiger_oplog_manager.cpp2
-rw-r--r--src/mongo/db/storage/wiredtiger/wiredtiger_parameters.cpp2
-rw-r--r--src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit.cpp2
-rw-r--r--src/mongo/idl/server_parameter_specialized_test.cpp6
-rw-r--r--src/mongo/idl/server_parameter_with_storage.h3
-rw-r--r--src/mongo/platform/decimal128.cpp45
-rw-r--r--src/mongo/platform/decimal128.h7
-rw-r--r--src/mongo/s/commands/cluster_kill_op.cpp2
-rw-r--r--src/mongo/scripting/mozjs/numberlong.cpp4
-rw-r--r--src/mongo/shell/shell_utils_launcher.cpp3
-rw-r--r--src/mongo/util/net/hostandport.cpp2
-rw-r--r--src/mongo/util/options_parser/options_parser.cpp10
-rw-r--r--src/mongo/util/processinfo_linux.cpp5
-rw-r--r--src/mongo/util/processinfo_solaris.cpp4
-rw-r--r--src/mongo/util/procparser.cpp10
-rw-r--r--src/mongo/util/str.cpp2
-rw-r--r--src/mongo/util/tcmalloc_set_parameter.cpp2
-rw-r--r--src/mongo/util/time_support.cpp22
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;
}