@cryptotaxi247 / netdata-1 / commits / e4c83f419

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 }