diff options
author | Kyle Suarez <kyle.suarez@mongodb.com> | 2017-10-23 17:31:25 -0400 |
---|---|---|
committer | Kyle Suarez <kyle.suarez@mongodb.com> | 2017-10-23 17:31:54 -0400 |
commit | dbe347e2ec54858a39a2b4c59d5812214b4b94cd (patch) | |
tree | 46cc71453270c3df1daeca448a23d13c95c3b1fb /src/mongo/db/matcher | |
parent | ec7af3523d4aa5130c56a05d76169755d9b5a611 (diff) | |
download | mongo-dbe347e2ec54858a39a2b4c59d5812214b4b94cd.tar.gz |
SERVER-31623 MatcherTypeSet must not cast large doubles to int
Diffstat (limited to 'src/mongo/db/matcher')
-rw-r--r-- | src/mongo/db/matcher/expression_parser.cpp | 15 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_parser.h | 9 | ||||
-rw-r--r-- | src/mongo/db/matcher/matcher_type_set.cpp | 12 | ||||
-rw-r--r-- | src/mongo/db/matcher/matcher_type_set_test.cpp | 60 |
4 files changed, 89 insertions, 7 deletions
diff --git a/src/mongo/db/matcher/expression_parser.cpp b/src/mongo/db/matcher/expression_parser.cpp index 0cd9ec13a53..6e7098a9b75 100644 --- a/src/mongo/db/matcher/expression_parser.cpp +++ b/src/mongo/db/matcher/expression_parser.cpp @@ -179,6 +179,21 @@ StatusWith<long long> MatchExpressionParser::parseIntegerElementToLong(BSONEleme return number; } +StatusWith<int> MatchExpressionParser::parseIntegerElementToInt(BSONElement elem) { + auto parsedLong = MatchExpressionParser::parseIntegerElementToLong(elem); + if (!parsedLong.isOK()) { + return parsedLong.getStatus(); + } + + auto valueLong = parsedLong.getValue(); + if (valueLong < std::numeric_limits<int>::min() || + valueLong > std::numeric_limits<int>::max()) { + return {ErrorCodes::FailedToParse, + str::stream() << "Cannot represent " << elem << " in an int"}; + } + return static_cast<int>(valueLong); +} + namespace { // Forward declarations. diff --git a/src/mongo/db/matcher/expression_parser.h b/src/mongo/db/matcher/expression_parser.h index f997b4c1a49..bfc3affbff4 100644 --- a/src/mongo/db/matcher/expression_parser.h +++ b/src/mongo/db/matcher/expression_parser.h @@ -148,5 +148,14 @@ public: * - Too large in the positive or negative direction to fit within a 64-bit signed integer. */ static StatusWith<long long> parseIntegerElementToLong(BSONElement elem); + + /** + * Parses a BSONElement of any numeric type into an integer, failing if the value is: + * + * - NaN + * - a non-integral number + * - too large in the positive or negative direction to fit in an int + */ + static StatusWith<int> parseIntegerElementToInt(BSONElement elem); }; } // namespace mongo diff --git a/src/mongo/db/matcher/matcher_type_set.cpp b/src/mongo/db/matcher/matcher_type_set.cpp index f2765adaf33..c5c80ea9988 100644 --- a/src/mongo/db/matcher/matcher_type_set.cpp +++ b/src/mongo/db/matcher/matcher_type_set.cpp @@ -30,6 +30,7 @@ #include "mongo/db/matcher/matcher_type_set.h" +#include "mongo/db/matcher/expression_parser.h" #include "mongo/db/matcher/schema/json_schema_parser.h" namespace mongo { @@ -78,19 +79,18 @@ Status parseSingleType(BSONElement elt, return addAliasToTypeSet(elt.valueStringData(), aliasMap, typeSet); } - invariant(elt.isNumber()); - int typeInt = elt.numberInt(); - if (elt.type() != BSONType::NumberInt && typeInt != elt.number()) { + auto valueAsInt = MatchExpressionParser::parseIntegerElementToInt(elt); + if (!valueAsInt.isOK()) { return Status(ErrorCodes::BadValue, str::stream() << "Invalid numerical type code: " << elt.number()); } - if (!isValidBSONType(typeInt)) { + if (!isValidBSONType(valueAsInt.getValue())) { return Status(ErrorCodes::BadValue, - str::stream() << "Invalid numerical type code: " << typeInt); + str::stream() << "Invalid numerical type code: " << elt.number()); } - typeSet->bsonTypes.insert(static_cast<BSONType>(typeInt)); + typeSet->bsonTypes.insert(static_cast<BSONType>(valueAsInt.getValue())); return Status::OK(); } diff --git a/src/mongo/db/matcher/matcher_type_set_test.cpp b/src/mongo/db/matcher/matcher_type_set_test.cpp index b23765f6784..204ad077fbf 100644 --- a/src/mongo/db/matcher/matcher_type_set_test.cpp +++ b/src/mongo/db/matcher/matcher_type_set_test.cpp @@ -133,13 +133,71 @@ TEST(MatcherTypeSetTest, ParseFailsWhenElementIsNonRoundDoubleTypeCode) { ASSERT_NOT_OK(result.getStatus()); } -TEST(MatcherTypeSetTest, ParseFailsWhenDoubleElementIsTooLargeForInteger) { +TEST(MatcherTypeSetTest, ParseFromElementCanParseRoundDecimalTypeCode) { + auto obj = BSON("" << Decimal128(2)); + auto result = MatcherTypeSet::parse(obj.firstElement(), MatcherTypeSet::kTypeAliasMap); + ASSERT_OK(result.getStatus()); + ASSERT_FALSE(result.getValue().allNumbers); + ASSERT_EQ(result.getValue().bsonTypes.size(), 1U); + ASSERT_TRUE(result.getValue().hasType(BSONType::String)); +} + +TEST(MatcherTypeSetTest, ParseFailsWhenElementIsNonRoundDecimalTypeCode) { + auto obj = BSON("" << Decimal128(2.5)); + auto result = MatcherTypeSet::parse(obj.firstElement(), MatcherTypeSet::kTypeAliasMap); + ASSERT_NOT_OK(result.getStatus()); +} + +TEST(MatcherTypeSetTest, ParseFailsWhenDoubleElementIsTooPositiveForInteger) { double doubleTooLarge = scalbn(1, std::numeric_limits<long long>::digits); auto obj = BSON("" << doubleTooLarge); auto result = MatcherTypeSet::parse(obj.firstElement(), MatcherTypeSet::kTypeAliasMap); ASSERT_NOT_OK(result.getStatus()); } +TEST(MatcherTypeSetTest, ParseFailsWhenDoubleElementIsTooNegativeForInteger) { + double doubleTooNegative = std::numeric_limits<double>::lowest(); + auto obj = BSON("" << doubleTooNegative); + auto result = MatcherTypeSet::parse(obj.firstElement(), MatcherTypeSet::kTypeAliasMap); + ASSERT_NOT_OK(result.getStatus()); +} + +TEST(MatcherTypeSetTest, ParseFailsWhenDoubleElementIsNaN) { + auto obj = BSON("" << std::nan("")); + auto result = MatcherTypeSet::parse(obj.firstElement(), MatcherTypeSet::kTypeAliasMap); + ASSERT_NOT_OK(result.getStatus()); +} + +TEST(MatcherTypeSetTest, ParseFailsWhenDoubleElementIsInfinite) { + auto obj = BSON("" << std::numeric_limits<double>::infinity()); + auto result = MatcherTypeSet::parse(obj.firstElement(), MatcherTypeSet::kTypeAliasMap); + ASSERT_NOT_OK(result.getStatus()); +} + +TEST(MatcherTypeSetTest, ParseFailsWhenDecimalElementIsTooPositiveForInteger) { + auto obj = BSON("" << Decimal128(static_cast<int64_t>(std::numeric_limits<int>::max()) + 1)); + auto result = MatcherTypeSet::parse(obj.firstElement(), MatcherTypeSet::kTypeAliasMap); + ASSERT_NOT_OK(result.getStatus()); +} + +TEST(MatcherTypeSetTest, ParseFailsWhenDecimalElementIsTooNegativeForInteger) { + auto obj = BSON("" << Decimal128(static_cast<int64_t>(std::numeric_limits<int>::min()) - 1)); + auto result = MatcherTypeSet::parse(obj.firstElement(), MatcherTypeSet::kTypeAliasMap); + ASSERT_NOT_OK(result.getStatus()); +} + +TEST(MatcherTypeSetTest, ParseFailsWhenDecimalElementIsNaN) { + auto obj = BSON("" << Decimal128::kPositiveNaN); + auto result = MatcherTypeSet::parse(obj.firstElement(), MatcherTypeSet::kTypeAliasMap); + ASSERT_NOT_OK(result.getStatus()); +} + +TEST(MatcherTypeSetTest, ParseFailsWhenDecimalElementIsInfinite) { + auto obj = BSON("" << Decimal128::kPositiveInfinity); + auto result = MatcherTypeSet::parse(obj.firstElement(), MatcherTypeSet::kTypeAliasMap); + ASSERT_NOT_OK(result.getStatus()); +} + TEST(MatcherTypeSetTest, ParseFromElementFailsWhenArrayHasUnknownType) { auto obj = BSON("" << BSON_ARRAY("long" << "unknown")); |