Skip to content

Fix strange and wrong code around DateTime64#11875

Merged
alexey-milovidov merged 10 commits intomasterfrom
fix-fpe-datetime64
Jun 24, 2020
Merged

Fix strange and wrong code around DateTime64#11875
alexey-milovidov merged 10 commits intomasterfrom
fix-fpe-datetime64

Conversation

@alexey-milovidov
Copy link
Copy Markdown
Member

Changelog category (leave one):

  • Bug Fix

Changelog entry (a user-readable short description of the changes that goes to CHANGELOG.md):
Fix potential floating point exception when parsing DateTime64. This fixes #11374.

@alexey-milovidov alexey-milovidov requested a review from Enmk June 22, 2020 22:09
}

DataTypeDateTime64::DataTypeDateTime64(UInt32 scale_, const TimezoneMixin & time_zone_info)
: DataTypeDecimalBase<DateTime64>(DecimalUtils::maxPrecision<DateTime64>() - scale_, scale_),
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very strange and ridiculous. Maybe this constructor is not used at all?

if (unlikely(precision < 1 || precision > maxPrecision()))
throw Exception("Precision " + std::to_string(precision) + " is out of bounds", ErrorCodes::ARGUMENT_OUT_OF_BOUND);
if (unlikely(scale < 0 || static_cast<UInt32>(scale) > maxPrecision()))
if (unlikely(scale > maxPrecision()))
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was strange code from @4ertus2
Also I'm surprised that tautological comparison is not detected by compiler.

/// number of decimal digits so far is close to the max for given type.
/// Example: 20 * 10 will overflow Int8.

if (buf.count() - initial_pos + 1 >= std::numeric_limits<T>::max_digits10)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Look at this + 1.

{
if (common::mulOverflow(res, static_cast<decltype(res)>(10), res)
|| common::addOverflow(res, static_cast<decltype(res)>(*buf.position() - '0'), res))
T signed_res = res;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We must check for overflow inside (possibly) signed data type.

@blinkov blinkov added the pr-bugfix Pull request with bugfix, not backported by default label Jun 22, 2020

if (buf.count() - initial_pos + 1 >= std::numeric_limits<T>::max_digits10)
{
if (common::mulOverflow(res, static_cast<decltype(res)>(10), res)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This decltype is ugly.

}

end:
x = negative ? -res : res;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And we must check for overflow here.

return ReturnType(false);
}

DB::DecimalUtils::DecimalComponents<DateTime64::NativeType> c{static_cast<DateTime64::NativeType>(whole), 0};
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

c is bad name for variable.

if (!buf.eof() && *buf.position() == '.')
{
buf.ignore(1); // skip separator
const auto pos_before_fractional = buf.count();
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is absolutely wrong.
Mind +123 or +1---23.

}
else if (adjust_scale < 0)
{
c.fractional /= common::exp10_i64(-1 * adjust_scale);
Copy link
Copy Markdown
Member Author

@alexey-milovidov alexey-milovidov Jun 22, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adjust_scale can appear too big.
For example, if
scale is 0
and we have parsed
1111111111111111111
(this number successfully passes overflow check)
then we will have to divide by
10000000000000000000
that does not fit in Int64
and buffer overflow happens.

(this is the main reason for the bug)

}
else if (adjust_scale < 0)
{
c.fractional /= common::exp10_i64(-1 * adjust_scale);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Writing -1 * x is strange, we can write just -x.

{
buf.ignore(1); // skip separator
const auto pos_before_fractional = buf.count();
if (!tryReadIntText<ReadIntTextCheckOverflow::CHECK_OVERFLOW>(c.fractional, buf))
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also wrong, because we don't need to parse integer (like -123).
We just need to parse a stream of digits (like 001).

@alexey-milovidov
Copy link
Copy Markdown
Member Author

@Enmk I highlighted all the bugs in comments.

@qoega
Copy link
Copy Markdown
Member

qoega commented Jun 23, 2020

Relevant tests:

[ RUN      ] data_type/LeastSuperTypeTest.getLeastSupertype/20
unknown file: Failure
C++ exception with description "Scale 12 is too large for DateTime64. Maximum is up to nanoseconds (9)." thrown in SetUp().
[  FAILED  ] data_type/LeastSuperTypeTest.getLeastSupertype/20, where GetParam() = TypesTestCase{"DateTime DateTime64(12)", "DateTime64(8)"} (0 ms)
[ RUN      ] data_type/LeastSuperTypeTest.getLeastSupertype/21
unknown file: Failure
C++ exception with description "Scale 15 is too large for DateTime64. Maximum is up to nanoseconds (9)." thrown in SetUp().
[  FAILED  ] data_type/LeastSuperTypeTest.getLeastSupertype/21, where GetParam() = TypesTestCase{"Date DateTime64(15)", "DateTime64(13)"} (1 ms)

@alexey-milovidov
Copy link
Copy Markdown
Member Author

No surprise: unit test is wrong, getLeastSuperType implementation is also wrong.

@alexey-milovidov
Copy link
Copy Markdown
Member Author

I will also add a functional test to demonstrate what code was totally and absolutely wrong.

abyss7 added a commit that referenced this pull request Jun 25, 2020
…a2fa7a83f36dd85e7f748d1579637

Cherry pick #11875 to 20.5: Fix strange and wrong code around DateTime64
alexey-milovidov added a commit that referenced this pull request Jun 27, 2020
Backport #11875 to 20.5: Fix strange and wrong code around DateTime64
alesapin pushed a commit that referenced this pull request Jul 10, 2020
Fix strange and wrong code around DateTime64

(cherry picked from commit c2fba51)
alesapin pushed a commit that referenced this pull request Jul 10, 2020
Fix strange and wrong code around DateTime64

(cherry picked from commit c2fba51)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-docs-needed pr-bugfix Pull request with bugfix, not backported by default

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Received signal Floating point exception (8).

4 participants