/**
* Copyright (C) 2012 10gen Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the GNU Affero General Public License in all respects
* for all of the code used other than as permitted herein. If you modify
* file(s) with this exception, you may extend this exception to your
* version of the file(s), but you are not obligated to do so. If you do not
* wish to do so, delete this exception statement from your version. If you
* delete this exception statement from all source files in the program,
* then also delete it in the license file.
*/
#include "mongo/platform/basic.h"
#include
#include
#include
#include "mongo/base/parse_number.h"
#include "mongo/base/status.h"
#include "mongo/unittest/unittest.h"
#include "mongo/util/mongoutils/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(EXPECTED_VALUE), v); \
} while (false)
#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(EXPECTED_VALUE), v); \
} while (false)
namespace mongo {
namespace {
template
class CommonNumberParsingTests {
public:
typedef _NumberType NumberType;
typedef std::numeric_limits Limits;
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));
}
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);
} else {
NumberType ignored;
ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("-10", &ignored));
ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("-0xff", &ignored));
ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("-077", &ignored));
}
}
static void TestParsingGarbage() {
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));
}
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));
}
static void TestParsingLimits() {
using namespace mongoutils;
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));
}
}
};
#define GENERAL_NUMBER_TESTS(SHORT_NAME, TYPE) \
class ParseNumberTests##SHORT_NAME : public unittest::Test { \
public: \
typedef CommonNumberParsingTests 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(); \
}
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(ParseNumber, NotNullTerminated) {
ASSERT_PARSES(int, StringData("1234", 3), 123);
}
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));
for (int32_t i = -128; i <= 127; ++i)
ASSERT_PARSES(int8_t, std::string(mongoutils::str::stream() << i), i);
}
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));
for (uint32_t i = 0; i <= 255; ++i)
ASSERT_PARSES(uint8_t, std::string(mongoutils::str::stream() << i), i);
}
TEST(ParseNumber, TestParsingOverflow) {
uint64_t u64;
// 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));
// 2**64 exactly. This will overflow the add.
ASSERT_EQUALS(ErrorCodes::FailedToParse,
parseNumberFromStringWithBase("18446744073709551616", 10, &u64));
uint32_t u32;
// Too large when down-converting.
ASSERT_EQUALS(ErrorCodes::FailedToParse,
parseNumberFromStringWithBase("0xfffffffff", 16, &u32));
int32_t i32;
// Too large when down-converting.
ASSERT_EQUALS(
ErrorCodes::FailedToParse,
parseNumberFromString(std::to_string(std::numeric_limits::max()), &i32));
}
TEST(Double, TestRejectingBadBases) {
double ignored;
// Only supported base for parseNumberFromStringWithBase 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));
}
TEST(Double, TestParsingGarbage) {
double d;
CommonNumberParsingTests::TestParsingGarbage();
ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("1.0.1", &d));
ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("1.0-1", &d));
ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString(" 1.0", &d));
ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("1.0P4", &d));
ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("1e6 ", &d));
ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString(" 1e6", &d));
ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("1e6 ", &d));
ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString(" 1e6", &d));
ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("0xabcab.defPa", &d));
ASSERT_EQUALS(ErrorCodes::FailedToParse, parseNumberFromString("1.0\0garbage"_sd, &d));
}
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));
}
TEST(Double, TestParsingNan) {
double d = 0;
ASSERT_OK(parseNumberFromString("NaN", &d));
ASSERT_TRUE(std::isnan(d));
}
TEST(Double, TestParsingNegativeZero) {
double d = 0;
ASSERT_OK(parseNumberFromString("-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_TRUE(std::isinf(d));
d = 0;
ASSERT_OK(parseNumberFromString("-Infinity", &d));
ASSERT_TRUE(std::isinf(d));
}
TEST(Double, TestParsingNormal) {
ASSERT_PARSES(double, "10", 10);
ASSERT_PARSES(double, "0", 0);
ASSERT_PARSES(double, "1", 1);
ASSERT_PARSES(double, "-10", -10);
ASSERT_PARSES(double, "1e8", 1e8);
ASSERT_PARSES(double, "1e-8", 1e-8);
ASSERT_PARSES(double, "12e-8", 12e-8);
ASSERT_PARSES(double, "-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.
ASSERT_PARSES(double, "0xff", 255);
ASSERT_PARSES(double, "-0xff", -255);
ASSERT_PARSES(double, "0xabcab.defdefP-10", 687.16784283419838);
#endif
}
} // namespace
} // namespace mongo