Skip to content

Commit abae1d8

Browse files
ubytemaVovk
authored andcommitted
TDuration/TInstant named constructors should return staturated value on overflow
`TDuration::Seconds(sec)` now returns `TDuration::Max()` if `sec` is greater than the largest representable value. Previously, a silent wrap-around could produce a significantly shorter interval (even a zero one). commit_hash:552fde08fa3b8201ede23894bb363df2d6bbd5af
1 parent 5bee478 commit abae1d8

File tree

2 files changed

+143
-43
lines changed

2 files changed

+143
-43
lines changed

util/datetime/base.h

Lines changed: 99 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,50 @@ TString DateToString(time_t when, long* sec = nullptr);
9393
TString YearToString(const struct tm& theTm);
9494
TString YearToString(time_t when);
9595

96+
namespace NDateTimeHelpers {
97+
template <typename T>
98+
struct TPrecisionHelper {
99+
using THighPrecision = ui64;
100+
};
101+
102+
template <>
103+
struct TPrecisionHelper<float> {
104+
using THighPrecision = double;
105+
};
106+
107+
template <>
108+
struct TPrecisionHelper<double> {
109+
using THighPrecision = double;
110+
};
111+
112+
template <ui64 b, typename T>
113+
static constexpr ui64 MulWithSaturation(T a) = delete;
114+
115+
template <ui64 b>
116+
constexpr ui64 MulWithSaturation(ui64 a) {
117+
constexpr ui64 maxMultiplicand = Max<ui64>() / b;
118+
#if defined(__GNUC__) && defined(__cpp_lib_is_constant_evaluated)
119+
if (!std::is_constant_evaluated()) {
120+
if constexpr (std::is_same<ui64, unsigned long>::value) {
121+
unsigned long r = 0;
122+
return __builtin_umull_overflow(a, b, &r) ? Max<ui64>() : r;
123+
}
124+
if constexpr (std::is_same<ui64, unsigned long long>::value) {
125+
unsigned long long r = 0;
126+
return __builtin_umulll_overflow(a, b, &r) ? Max<ui64>() : r;
127+
}
128+
}
129+
#endif
130+
return a <= maxMultiplicand ? a * b : Max<ui64>();
131+
}
132+
133+
template <ui64 b>
134+
constexpr ui64 MulWithSaturation(double a) {
135+
double r = a * b;
136+
return r < 0 ? 0 : (r <= MaxFloor<ui64>() ? static_cast<ui64>(r) : Max<ui64>());
137+
}
138+
} // namespace NDateTimeHelpers
139+
96140
template <class S>
97141
class TTimeBase {
98142
public:
@@ -172,25 +216,32 @@ class TTimeBase {
172216
}
173217

174218
protected:
175-
TValue Value_; // microseconds count
176-
};
219+
/* noexcept(false) as conversion from T might throw, for example FromString("abc") */
220+
template <typename T>
221+
static constexpr S MilliSecondsConvertFrom(T milliseconds) noexcept(false) {
222+
auto ms = typename NDateTimeHelpers::TPrecisionHelper<T>::THighPrecision(milliseconds);
223+
return S::MicroSeconds(NDateTimeHelpers::MulWithSaturation<ui64(1'000)>(ms));
224+
}
177225

178-
namespace NDateTimeHelpers {
226+
/* noexcept(false) as conversion from T might throw, for example FromString("abc") */
179227
template <typename T>
180-
struct TPrecisionHelper {
181-
using THighPrecision = ui64;
182-
};
228+
static constexpr S SecondsConvertFrom(T seconds) noexcept(false) {
229+
auto s = typename NDateTimeHelpers::TPrecisionHelper<T>::THighPrecision(seconds);
230+
if constexpr (std::is_same_v<decltype(s), double>) {
231+
return MilliSecondsConvertFrom(s * 1000);
232+
}
233+
return S::MicroSeconds(NDateTimeHelpers::MulWithSaturation<ui64(1'000'000)>(s));
234+
}
183235

184-
template <>
185-
struct TPrecisionHelper<float> {
186-
using THighPrecision = double;
187-
};
236+
static constexpr S Minutes(ui64 m) noexcept;
188237

189-
template <>
190-
struct TPrecisionHelper<double> {
191-
using THighPrecision = double;
192-
};
193-
} // namespace NDateTimeHelpers
238+
static constexpr S Hours(ui64 h) noexcept;
239+
240+
static constexpr S Days(ui64 d) noexcept;
241+
242+
protected:
243+
TValue Value_; // microseconds count
244+
};
194245

195246
class TDuration: public TTimeBase<TDuration> {
196247
using TBase = TTimeBase<TDuration>;
@@ -267,7 +318,7 @@ class TDuration: public TTimeBase<TDuration> {
267318
/* noexcept(false) as conversion from T might throw, for example FromString("abc") */
268319
template <typename T>
269320
static constexpr TDuration MilliSeconds(T ms) noexcept(false) {
270-
return MicroSeconds((ui64)(typename NDateTimeHelpers::TPrecisionHelper<T>::THighPrecision(ms) * 1000));
321+
return TBase::MilliSecondsConvertFrom(std::move(ms));
271322
}
272323

273324
using TBase::Days;
@@ -277,6 +328,12 @@ class TDuration: public TTimeBase<TDuration> {
277328
using TBase::Minutes;
278329
using TBase::Seconds;
279330

331+
/// Base class static methods:
332+
///
333+
/// static constexpr TDuration Minutes(ui64 m) noexcept;
334+
/// static constexpr TDuration Hours(ui64 h) noexcept;
335+
/// static constexpr TDuration Days(ui64 d) noexcept;
336+
280337
/// DeadLineFromTimeOut
281338
inline TInstant ToDeadLine() const;
282339
constexpr TInstant ToDeadLine(TInstant now) const;
@@ -292,19 +349,7 @@ class TDuration: public TTimeBase<TDuration> {
292349
/* noexcept(false) as conversion from T might throw, for example FromString("abc") */
293350
template <typename T>
294351
static constexpr TDuration Seconds(T s) noexcept(false) {
295-
return MilliSeconds(typename NDateTimeHelpers::TPrecisionHelper<T>::THighPrecision(s) * 1000);
296-
}
297-
298-
static constexpr TDuration Minutes(ui64 m) noexcept {
299-
return Seconds(m * 60);
300-
}
301-
302-
static constexpr TDuration Hours(ui64 h) noexcept {
303-
return Minutes(h * 60);
304-
}
305-
306-
static constexpr TDuration Days(ui64 d) noexcept {
307-
return Hours(d * 24);
352+
return TBase::SecondsConvertFrom(std::move(s));
308353
}
309354

310355
/// parses strings like 10s, 15ms, 15.05s, 20us, or just 25 (s). See parser_ut.cpp for details
@@ -396,30 +441,26 @@ class TInstant: public TTimeBase<TInstant> {
396441
return TInstant(us);
397442
}
398443

399-
/// ms since epoch
444+
/// milliseconds since epoch
400445
static constexpr TInstant MilliSeconds(ui64 ms) noexcept {
401-
return MicroSeconds(ms * 1000);
446+
return TBase::MilliSecondsConvertFrom(ms);
402447
}
403448

404449
/// seconds since epoch
405450
static constexpr TInstant Seconds(ui64 s) noexcept {
406-
return MilliSeconds(s * 1000);
451+
return TBase::SecondsConvertFrom(s);
407452
}
408453

454+
/// Base class static methods:
455+
///
409456
/// minutes since epoch
410-
static constexpr TInstant Minutes(ui64 m) noexcept {
411-
return Seconds(m * 60);
412-
}
413-
457+
/// static constexpr TInstant Minutes(ui64 m) noexcept;
458+
///
414459
/// hours since epoch
415-
static constexpr TInstant Hours(ui64 h) noexcept {
416-
return Minutes(h * 60);
417-
}
418-
460+
/// static constexpr TInstant Hours(ui64 h) noexcept;
461+
///
419462
/// days since epoch
420-
static constexpr TInstant Days(ui64 d) noexcept {
421-
return Hours(d * 24);
422-
}
463+
/// static constexpr TInstant Days(ui64 d) noexcept;
423464

424465
constexpr time_t TimeT() const noexcept {
425466
return (time_t)Seconds();
@@ -818,6 +859,21 @@ static inline TInstant Now() noexcept {
818859
return TInstant::Now();
819860
}
820861

862+
template <class S>
863+
constexpr S TTimeBase<S>::Minutes(ui64 m) noexcept {
864+
return S::MicroSeconds(NDateTimeHelpers::MulWithSaturation<ui64(1'000'000) * 60>(m));
865+
}
866+
867+
template <class S>
868+
constexpr S TTimeBase<S>::Hours(ui64 h) noexcept {
869+
return S::MicroSeconds(NDateTimeHelpers::MulWithSaturation<ui64(1'000'000) * 60 * 60>(h));
870+
}
871+
872+
template <class S>
873+
constexpr S TTimeBase<S>::Days(ui64 d) noexcept {
874+
return S::MicroSeconds(NDateTimeHelpers::MulWithSaturation<ui64(1'000'000) * 60 * 60 * 24>(d));
875+
}
876+
821877
#ifdef _MSC_VER
822878
#pragma warning(pop)
823879
#endif // _MSC_VER

util/datetime/base_ut.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,11 @@ Y_UNIT_TEST_SUITE(DateTimeTest) {
413413
}
414414
}
415415

416+
Y_UNIT_TEST(TestFromConverableType) {
417+
std::atomic<ui64> timestamp{42};
418+
UNIT_ASSERT_EQUAL(TInstant::Seconds(42), TInstant::Seconds(timestamp));
419+
}
420+
416421
Y_UNIT_TEST(TestSleep) {
417422
// check does not throw
418423
Sleep(TDuration::Seconds(0));
@@ -582,6 +587,45 @@ Y_UNIT_TEST_SUITE(DateTimeTest) {
582587
static_cast<float>(::Max<ui64>()) / 1000 + 0.1}));
583588
}
584589

590+
template <class T>
591+
void TestDurationCurationConstructorFunctionSaturation() {
592+
const ui64 maxMilliseconds = ::Max<ui64>() / T::MilliSeconds(1).GetValue();
593+
const ui64 maxSeconds = ::Max<ui64>() / T::Seconds(1).GetValue();
594+
const ui64 maxMinutes = ::Max<ui64>() / T::Minutes(1).GetValue();
595+
const ui64 maxHours = ::Max<ui64>() / T::Hours(1).GetValue();
596+
const ui64 maxDays = ::Max<ui64>() / T::Days(1).GetValue();
597+
598+
auto test = [](ui64 maxValue, T (*constructor)(ui64), TStringBuf name) {
599+
UNIT_ASSERT_VALUES_UNEQUAL_C(constructor(maxValue), T::Max(), LabeledOutput(name));
600+
UNIT_ASSERT_VALUES_EQUAL_C(constructor(Max<ui64>()), T::Max(), LabeledOutput(name));
601+
for (ui64 v = maxValue + 1; v != Max<ui64>(); v = std::midpoint(Max<ui64>(), v)) {
602+
UNIT_ASSERT_VALUES_EQUAL_C(constructor(v), T::Max(), LabeledOutput(name, v));
603+
}
604+
for (ui64 v = maxValue; v != 0; v = std::midpoint(ui64(0), v)) {
605+
UNIT_ASSERT_VALUES_UNEQUAL_C(constructor(v), T::Max(), LabeledOutput(name, v));
606+
}
607+
};
608+
test(maxMilliseconds, &T::MilliSeconds, "MilliSeconds");
609+
test(maxSeconds, &T::Seconds, "Seconds");
610+
test(maxMinutes, &T::Minutes, "Minutes");
611+
test(maxHours, &T::Hours, "Hours");
612+
test(maxDays, &T::Days, "Days");
613+
}
614+
615+
Y_UNIT_TEST(TestDurationCurationConstructorFunctionSaturation) {
616+
UNIT_ASSERT_VALUES_EQUAL(TDuration::Seconds(-5.f), TDuration::Zero());
617+
UNIT_ASSERT_VALUES_EQUAL(TDuration::MilliSeconds(-5.f), TDuration::Zero());
618+
UNIT_ASSERT_VALUES_EQUAL(TDuration::MilliSeconds(0x1p63), TDuration::Max());
619+
UNIT_ASSERT_VALUES_EQUAL(TDuration::Seconds(0x1p45), TDuration::Max());
620+
UNIT_ASSERT_VALUES_UNEQUAL(TDuration::Seconds(0x1p44), TDuration::Max());
621+
622+
TestDurationCurationConstructorFunctionSaturation<TDuration>();
623+
}
624+
625+
Y_UNIT_TEST(TestInstantCurationConstructorFunctionSaturation) {
626+
TestDurationCurationConstructorFunctionSaturation<TInstant>();
627+
}
628+
585629
Y_UNIT_TEST(TestTDurationCompareWithStdChronoDuration) {
586630
UNIT_ASSERT(TDuration::Zero() == 0ms);
587631
UNIT_ASSERT(TDuration::Seconds(42) == 42s);

0 commit comments

Comments
 (0)