summaryrefslogtreecommitdiff
path: root/src/mongo/db/matcher
diff options
context:
space:
mode:
authorKyle Suarez <kyle.suarez@mongodb.com>2017-10-23 17:31:25 -0400
committerKyle Suarez <kyle.suarez@mongodb.com>2017-10-23 17:31:54 -0400
commitdbe347e2ec54858a39a2b4c59d5812214b4b94cd (patch)
tree46cc71453270c3df1daeca448a23d13c95c3b1fb /src/mongo/db/matcher
parentec7af3523d4aa5130c56a05d76169755d9b5a611 (diff)
downloadmongo-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.cpp15
-rw-r--r--src/mongo/db/matcher/expression_parser.h9
-rw-r--r--src/mongo/db/matcher/matcher_type_set.cpp12
-rw-r--r--src/mongo/db/matcher/matcher_type_set_test.cpp60
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"));