From 948e065e29ea96d6d248c50f4cfa8fb71cbcec55 Mon Sep 17 00:00:00 2001 From: Arun Banala Date: Wed, 21 Oct 2020 17:39:22 +0100 Subject: SERVER-50445 Return the value in double when NumberLong subtraction overflows in ExpressionSubtract (cherry picked from commit 3518bd82e49b6941ee7a2f3a868df40114b0d8fc) (cherry picked from commit 309b631dd16e90e1f3fb8bf3567df1fedc92d715) --- src/mongo/db/pipeline/expression.cpp | 10 +++++++--- src/mongo/db/pipeline/expression_test.cpp | 33 +++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) (limited to 'src/mongo/db') diff --git a/src/mongo/db/pipeline/expression.cpp b/src/mongo/db/pipeline/expression.cpp index 304c0d302fe..51a284e6a52 100644 --- a/src/mongo/db/pipeline/expression.cpp +++ b/src/mongo/db/pipeline/expression.cpp @@ -4447,9 +4447,13 @@ Value ExpressionSubtract::evaluate(const Document& root, Variables* variables) c double left = lhs.coerceToDouble(); return Value(left - right); } else if (diffType == NumberLong) { - long long right = rhs.coerceToLong(); - long long left = lhs.coerceToLong(); - return Value(left - right); + long long result; + + // If there is an overflow, convert the values to doubles. + if (mongoSignedSubtractOverflow64(lhs.coerceToLong(), rhs.coerceToLong(), &result)) { + return Value(lhs.coerceToDouble() - rhs.coerceToDouble()); + } + return Value(result); } else if (diffType == NumberInt) { long long right = rhs.coerceToLong(); long long left = lhs.coerceToLong(); diff --git a/src/mongo/db/pipeline/expression_test.cpp b/src/mongo/db/pipeline/expression_test.cpp index 56a87101f87..0e08a8661e9 100644 --- a/src/mongo/db/pipeline/expression_test.cpp +++ b/src/mongo/db/pipeline/expression_test.cpp @@ -5948,4 +5948,37 @@ public: SuiteInstance myall; +TEST(ExpressionSubtractTest, OverflowLong) { + const auto maxLong = std::numeric_limits::max(); + const auto minLong = std::numeric_limits::min(); + intrusive_ptr expCtx(new ExpressionContextForTest()); + + // The following subtractions should not fit into a long long data type. + BSONObj obj = BSON("$subtract" << BSON_ARRAY(maxLong << minLong)); + auto expression = Expression::parseExpression(expCtx, obj, expCtx->variablesParseState); + Value result = expression->evaluate({}, &expCtx->variables); + ASSERT_EQ(result.getType(), BSONType::NumberDouble); + ASSERT_EQ(result.getDouble(), static_cast(maxLong) - minLong); + + obj = BSON("$subtract" << BSON_ARRAY(minLong << maxLong)); + expression = Expression::parseExpression(expCtx, obj, expCtx->variablesParseState); + result = expression->evaluate({}, &expCtx->variables); + ASSERT_EQ(result.getType(), BSONType::NumberDouble); + ASSERT_EQ(result.getDouble(), static_cast(minLong) - maxLong); + + // minLong = -1 - maxLong. The below subtraction should fit into long long data type. + obj = BSON("$subtract" << BSON_ARRAY(-1 << maxLong)); + expression = Expression::parseExpression(expCtx, obj, expCtx->variablesParseState); + result = expression->evaluate({}, &expCtx->variables); + ASSERT_EQ(result.getType(), BSONType::NumberLong); + ASSERT_EQ(result.getLong(), -1LL - maxLong); + + // The minLong's negation does not fit into long long, hence it should be converted to double + // data type. + obj = BSON("$subtract" << BSON_ARRAY(0 << minLong)); + expression = Expression::parseExpression(expCtx, obj, expCtx->variablesParseState); + result = expression->evaluate({}, &expCtx->variables); + ASSERT_EQ(result.getType(), BSONType::NumberDouble); + ASSERT_EQ(result.getDouble(), static_cast(minLong) * -1); +} } // namespace ExpressionTests -- cgit v1.2.1