Fix strange and wrong code around DateTime64#11875
Conversation
| } | ||
|
|
||
| DataTypeDateTime64::DataTypeDateTime64(UInt32 scale_, const TimezoneMixin & time_zone_info) | ||
| : DataTypeDecimalBase<DateTime64>(DecimalUtils::maxPrecision<DateTime64>() - scale_, scale_), |
There was a problem hiding this comment.
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())) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
We must check for overflow inside (possibly) signed data type.
|
|
||
| if (buf.count() - initial_pos + 1 >= std::numeric_limits<T>::max_digits10) | ||
| { | ||
| if (common::mulOverflow(res, static_cast<decltype(res)>(10), res) |
There was a problem hiding this comment.
This decltype is ugly.
| } | ||
|
|
||
| end: | ||
| x = negative ? -res : res; |
There was a problem hiding this comment.
And we must check for overflow here.
| return ReturnType(false); | ||
| } | ||
|
|
||
| DB::DecimalUtils::DecimalComponents<DateTime64::NativeType> c{static_cast<DateTime64::NativeType>(whole), 0}; |
There was a problem hiding this comment.
c is bad name for variable.
| if (!buf.eof() && *buf.position() == '.') | ||
| { | ||
| buf.ignore(1); // skip separator | ||
| const auto pos_before_fractional = buf.count(); |
There was a problem hiding this comment.
This is absolutely wrong.
Mind +123 or +1---23.
| } | ||
| else if (adjust_scale < 0) | ||
| { | ||
| c.fractional /= common::exp10_i64(-1 * adjust_scale); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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).
|
@Enmk I highlighted all the bugs in comments. |
|
Relevant tests: |
|
No surprise: unit test is wrong, getLeastSuperType implementation is also wrong. |
|
I will also add a functional test to demonstrate what code was totally and absolutely wrong. |
…a2fa7a83f36dd85e7f748d1579637 Cherry pick #11875 to 20.5: Fix strange and wrong code around DateTime64
Backport #11875 to 20.5: Fix strange and wrong code around DateTime64
Fix strange and wrong code around DateTime64 (cherry picked from commit c2fba51)
Fix strange and wrong code around DateTime64 (cherry picked from commit c2fba51)
Changelog category (leave one):
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.