Coverity and ML (#22120)
thiagoftsm committed
Apr 7, 2026 at 11:20 UTC
e4c83f419757743d374ad14b25381d9790639ffd
2 files changed
+36
-44
src/ml/ml.cc
+20
-33
@@ -30,35 +30,28 @@ static void __attribute__((destructor)) destroy_mutex(void) {
30
31
namespace {
32
33
-template <bool NeedLowerBound, bool NeedUpperBound>
34
-inline bool ml_sqlite_int64_fits_time_t(sqlite3_int64 value)
33
+// Guard against silent truncation when time_t is narrower than sqlite3_int64
34
+// (e.g. 32-bit builds). if constexpr ensures the dead branches are elided at
35
+// compile time on 64-bit platforms, avoiding constant-expression warnings.
36
+static inline bool ml_sqlite_int64_fits_time_t(sqlite3_int64 value)
37
{
36
- (void)value;
38
+ if constexpr (!std::numeric_limits<time_t>::is_signed ||
39
+ std::numeric_limits<time_t>::digits < std::numeric_limits<sqlite3_int64>::digits) {
40
+ // coverity[CONSTANT_EXPRESSION_RESULT] - on 64-bit, Coverity still analyzes
41
+ // this dead branch; reachable only on 32-bit or unsigned time_t builds.
42
+ if (value < (sqlite3_int64) std::numeric_limits<time_t>::min())
43
+ return false;
44
+ }
45
+ if constexpr (std::numeric_limits<time_t>::digits < std::numeric_limits<sqlite3_int64>::digits) {
46
+ // coverity[CONSTANT_EXPRESSION_RESULT] - dead on same-width 64-bit time_t;
47
+ // reachable only when time_t is narrower than sqlite3_int64, but Coverity
48
+ // still analyzes this discarded if constexpr branch.
49
+ if (value > (sqlite3_int64) std::numeric_limits<time_t>::max())
50
+ return false;
51
+ }
52
return true;
53
}
54
40
-template <>
41
-inline bool ml_sqlite_int64_fits_time_t<true, false>(sqlite3_int64 value)
42
-{
43
- const sqlite3_int64 kTimeMin = (sqlite3_int64) std::numeric_limits<time_t>::min();
44
- return value >= kTimeMin;
45
-}
46
-
47
-template <>
48
-inline bool ml_sqlite_int64_fits_time_t<false, true>(sqlite3_int64 value)
49
-{
50
- const sqlite3_int64 kTimeMax = (sqlite3_int64) std::numeric_limits<time_t>::max();
51
- return value <= kTimeMax;
52
-}
53
-
54
-template <>
55
-inline bool ml_sqlite_int64_fits_time_t<true, true>(sqlite3_int64 value)
56
-{
57
- const sqlite3_int64 kTimeMin = (sqlite3_int64) std::numeric_limits<time_t>::min();
58
- const sqlite3_int64 kTimeMax = (sqlite3_int64) std::numeric_limits<time_t>::max();
59
- return value >= kTimeMin && value <= kTimeMax;
60
-}
61
-
55
}
56
57
static inline size_t ml_dimension_smoothing_window(const ml_dimension_t *dim)
@@ -464,16 +457,10 @@ int ml_dimension_load_models(RRDDIM *rd, sqlite3_stmt **active_stmt) {
457
458
sqlite3_int64 raw_after = sqlite3_column_int64(res, 0);
459
sqlite3_int64 raw_before = sqlite3_column_int64(res, 1);
467
- constexpr bool kNeedLowerBound =
468
- !std::numeric_limits<time_t>::is_signed ||
469
- std::numeric_limits<time_t>::digits < std::numeric_limits<sqlite3_int64>::digits;
470
- constexpr bool kNeedUpperBound =
471
- std::numeric_limits<time_t>::digits < std::numeric_limits<sqlite3_int64>::digits;
472
-
460
// Protect against silent truncation when time_t is narrower than int64_t
461
// (e.g. 32-bit builds, corrupted DB, or far-future timestamps).
475
- if (!ml_sqlite_int64_fits_time_t<kNeedLowerBound, kNeedUpperBound>(raw_after) ||
476
- !ml_sqlite_int64_fits_time_t<kNeedLowerBound, kNeedUpperBound>(raw_before)) {
462
+ if (!ml_sqlite_int64_fits_time_t(raw_after) ||
463
+ !ml_sqlite_int64_fits_time_t(raw_before)) {
464
error_report("Skipping ML model row with out-of-range timestamps: after=%" PRId64 " before=%" PRId64,
465
(int64_t) raw_after, (int64_t) raw_before);
466
continue;
src/ml/ml_kmeans.cc
+16
-11
@@ -14,16 +14,21 @@ using std::isnan;
14
15
namespace {
16
17
-template <bool TimeTNarrowerThanInt64>
18
-inline bool ml_int64_fits_nonnegative_time_t(int64_t value)
17
+// Guard against out-of-range timestamps when time_t is narrower than int64_t
18
+// (e.g. 32-bit builds). if constexpr ensures the upper-bound check is elided
19
+// at compile time on 64-bit platforms, avoiding constant-expression warnings.
20
+static inline bool ml_int64_fits_nonnegative_time_t(int64_t value)
21
{
20
- return value >= 0;
21
-}
22
-
23
-template <>
24
-inline bool ml_int64_fits_nonnegative_time_t<true>(int64_t value)
25
-{
26
- return value >= 0 && value <= (int64_t) std::numeric_limits<time_t>::max();
22
+ if (value < 0)
23
+ return false;
24
+ if constexpr (sizeof(time_t) < sizeof(int64_t)) {
25
+ // coverity[CONSTANT_EXPRESSION_RESULT] - on 64-bit, Coverity still analyzes
26
+ // this dead branch; the check is genuinely reachable only on 32-bit builds
27
+ // where time_t is narrower than int64_t.
28
+ if (value > (int64_t) std::numeric_limits<time_t>::max())
29
+ return false;
30
+ }
31
+ return true;
32
}
33
34
}
@@ -204,7 +209,7 @@ bool ml_kmeans_deserialize(ml_kmeans_inlined_t *inlined_km, struct json_object *
209
}
210
int64_t raw_after = json_object_get_int64(value);
211
// Timestamps must be non-negative Unix epoch seconds and fit in time_t.
207
- if (!ml_int64_fits_nonnegative_time_t<(sizeof(time_t) < sizeof(int64_t))>(raw_after)) {
212
+ if (!ml_int64_fits_nonnegative_time_t(raw_after)) {
213
netdata_log_error("Failed to deserialize kmeans: out-of-range value for 'after': %" PRId64, raw_after);
214
return false;
215
}
@@ -220,7 +225,7 @@ bool ml_kmeans_deserialize(ml_kmeans_inlined_t *inlined_km, struct json_object *
225
}
226
int64_t raw_before = json_object_get_int64(value);
227
// Same contract as 'after': non-negative and fits in time_t.
223
- if (!ml_int64_fits_nonnegative_time_t<(sizeof(time_t) < sizeof(int64_t))>(raw_before)) {
228
+ if (!ml_int64_fits_nonnegative_time_t(raw_before)) {
229
netdata_log_error("Failed to deserialize kmeans: out-of-range value for 'before': %" PRId64, raw_before);
230
return false;
231
}