From d11cf03c178c780976cb87d8248c516bc3b8b9ba Mon Sep 17 00:00:00 2001 From: fcostaoliveira Date: Mon, 14 Sep 2026 23:08:06 +0100 Subject: [PATCH 1/3] fix: out-of-range double->int conversion in hdr_timespec_from_double (UBSan) hdr_log_read_header feeds the "#[StartTime: %lf" field straight from the file into hdr_timespec_from_double, which converted it with `int seconds = (int) value;`. Converting a double that is out of int range, or non-finite, to an integer is undefined behaviour, so any log whose StartTime is outside int (for example a millisecond epoch written where seconds were expected) is UB during header parse: src/hdr_time.c:92:19: runtime error: 1.40348e+12 is outside the range of representable values of type 'int' The int accumulator cascades two more UB sites for such a value: the `(int) round(...)` of the resulting huge fraction, and `milliseconds * 1000000` (gcc's UBSan reports that one first, clang reports the cast). Range-check before converting, and widen the result to what tv_sec can actually hold. `int` was too narrow regardless: it truncates every timestamp past 2038 even on platforms whose tv_sec is 64-bit. The bound is derived from sizeof(tv_sec), so a value that does not fit (32-bit tv_sec, or a non-finite/huge double) yields a defined 0/0 rather than a trap; the fields are always written, so a caller passing an uninitialized hdr_timespec cannot read indeterminate values on the reject path. Millisecond resolution is unchanged, as documented in hdr_time.h. Tests: - test_timespec_from_double covers a normal value, a negative value, a value beyond int that a 64-bit tv_sec holds (asserted per sizeof(tv_sec)), +/-1e300, +/-INFINITY and NAN. Aborts under UBSan on the pre-fix code. - handle_wide_start_time reads regression-log-start-time-overflow.hlog through hdr_log_read_header, covering the actual reachable path; the fixture also joins the log_reader_fuzzer seed corpus via .clusterfuzzlite/build.sh (test/*.hlog). Verified: ctest 5/5 under gcc ASan+UBSan and under clang ASan+UBSan+float-cast-overflow (the check the fuzzing build uses), both with -fno-sanitize-recover=all; 5/5 gcc and clang RelWithDebInfo; 4/4 with HDR_LOG_REQUIRED=DISABLED; no new compiler warnings; and the original ClusterFuzzLite crash artifact now replays clean through log_reader_fuzzer. Found-by: ClusterFuzzLite batch fuzzing (log_reader_fuzzer, UBSan) Co-Authored-By: Claude Opus 5 --- src/hdr_time.c | 20 +++++- test/CMakeLists.txt | 2 + test/hdr_histogram_log_test.c | 25 ++++++++ test/hdr_histogram_test.c | 48 ++++++++++++++ test/regression-log-start-time-overflow.hlog | 66 ++++++++++++++++++++ 5 files changed, 158 insertions(+), 3 deletions(-) create mode 100644 test/regression-log-start-time-overflow.hlog diff --git a/src/hdr_time.c b/src/hdr_time.c index ecbf479e..8ebf63de 100644 --- a/src/hdr_time.c +++ b/src/hdr_time.c @@ -5,6 +5,8 @@ */ #include +#include +#include #include @@ -89,9 +91,21 @@ double hdr_timespec_as_double(const hdr_timespec* t) void hdr_timespec_from_double(hdr_timespec* t, double value) { - int seconds = (int) value; - int milliseconds = (int) round((value - seconds) * 1000); + /* 2^(bits-1) is exact as a double and one past what tv_sec can hold */ + const double limit = ldexp(1.0, (int) (sizeof(t->tv_sec) * CHAR_BIT) - 1); + double seconds = trunc(value); + int milliseconds; - t->tv_sec = seconds; + /* converting a non-finite or out-of-range double to an integer is UB */ + if (!isfinite(value) || seconds >= limit || seconds < -limit) + { + t->tv_sec = 0; + t->tv_nsec = 0; + return; + } + + milliseconds = (int) round((value - seconds) * 1000); + + t->tv_sec = (int64_t) seconds; t->tv_nsec = milliseconds * 1000000; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0cf6e18d..e7c545c5 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -85,6 +85,8 @@ foreach(V 2.0.1.logV0 2.0.6.logV1 2.0.7S.logV2 2.0.7S.logV3) configure_file(jHiccup-${V}.hlog jHiccup-${V}.hlog COPYONLY) endforeach() +configure_file(regression-log-start-time-overflow.hlog regression-log-start-time-overflow.hlog COPYONLY) + foreach(V test_tagged_invalid_histogram.txt test_tagged_invalid_tag_key.txt test_tagged_invalid_timestamp.txt test_tagged_missing_histogram.txt) configure_file(${V} ${V} COPYONLY) endforeach() diff --git a/test/hdr_histogram_log_test.c b/test/hdr_histogram_log_test.c index e687b105..7e0ee2c4 100644 --- a/test/hdr_histogram_log_test.c +++ b/test/hdr_histogram_log_test.c @@ -993,6 +993,30 @@ static int parse_line_from_file(const char* filename) return result; } +/* A StartTime beyond int range must not be an out-of-range cast (UBSan). */ +static char* handle_wide_start_time(void) +{ + struct hdr_log_reader reader; + int rc; + + FILE* f = fopen("regression-log-start-time-overflow.hlog", "r"); + mu_assert("Can not open wide start time log", f != NULL); + + hdr_log_reader_init(&reader); + rc = hdr_log_read_header(&reader, f); + fclose(f); + + mu_assert("Failed to read header", compare_int(0, rc)); + if (sizeof(reader.start_timestamp.tv_sec) >= 8) + { + mu_assert( + "Start time wrong", + compare_int64(INT64_C(1403476110183), (int64_t) reader.start_timestamp.tv_sec)); + } + + return 0; +} + static char* handle_invalid_log_lines(void) { mu_assert("Should have invalid histogram", -EINVAL == parse_line_from_file("test_tagged_invalid_histogram.txt")); @@ -1106,6 +1130,7 @@ static struct mu_result all_tests(void) mu_run_test(decode_v1_log); mu_run_test(decode_v0_log); mu_run_test(handle_invalid_log_lines); + mu_run_test(handle_wide_start_time); mu_run_test(test_zig_zag_codec); mu_run_test(test_encode_and_decode_empty); diff --git a/test/hdr_histogram_test.c b/test/hdr_histogram_test.c index 864e6e00..d61ed8dd 100644 --- a/test/hdr_histogram_test.c +++ b/test/hdr_histogram_test.c @@ -11,8 +11,10 @@ #include #include +#include #include #include +#include #include "minunit.h" #include "hdr_test_util.h" @@ -158,6 +160,51 @@ static char* test_invalid_init(void) return 0; } +static char* test_timespec_from_double(void) +{ + hdr_timespec t; + + hdr_timespec_from_double(&t, 1403476110.183); + mu_assert("seconds wrong", compare_int64(INT64_C(1403476110), (int64_t) t.tv_sec)); + mu_assert("nanoseconds wrong", compare_int64(INT64_C(183000000), (int64_t) t.tv_nsec)); + + hdr_timespec_from_double(&t, -2.0); + mu_assert("negative seconds wrong", compare_int64(INT64_C(-2), (int64_t) t.tv_sec)); + mu_assert("negative nanoseconds wrong", compare_int64(INT64_C(0), (int64_t) t.tv_nsec)); + + /* Exceeds int but fits a 64-bit tv_sec; reachable from a log header StartTime. */ + hdr_timespec_from_double(&t, 1403476110183.0); + if (sizeof(t.tv_sec) >= 8) + { + mu_assert("wide seconds wrong", compare_int64(INT64_C(1403476110183), (int64_t) t.tv_sec)); + } + else + { + mu_assert("unrepresentable seconds should be zeroed", compare_int64(INT64_C(0), (int64_t) t.tv_sec)); + } + mu_assert("wide nanoseconds wrong", compare_int64(INT64_C(0), (int64_t) t.tv_nsec)); + + /* Values tv_sec cannot hold, and non-finite values, must not be converted. */ + hdr_timespec_from_double(&t, 1e300); + mu_assert("huge seconds should be zeroed", compare_int64(INT64_C(0), (int64_t) t.tv_sec)); + mu_assert("huge nanoseconds should be zeroed", compare_int64(INT64_C(0), (int64_t) t.tv_nsec)); + + hdr_timespec_from_double(&t, -1e300); + mu_assert("huge negative seconds should be zeroed", compare_int64(INT64_C(0), (int64_t) t.tv_sec)); + + hdr_timespec_from_double(&t, INFINITY); + mu_assert("inf seconds should be zeroed", compare_int64(INT64_C(0), (int64_t) t.tv_sec)); + + hdr_timespec_from_double(&t, -INFINITY); + mu_assert("-inf seconds should be zeroed", compare_int64(INT64_C(0), (int64_t) t.tv_sec)); + + hdr_timespec_from_double(&t, NAN); + mu_assert("nan seconds should be zeroed", compare_int64(INT64_C(0), (int64_t) t.tv_sec)); + mu_assert("nan nanoseconds should be zeroed", compare_int64(INT64_C(0), (int64_t) t.tv_nsec)); + + return 0; +} + static char* test_bucket_config_shift_overflow(void) { struct hdr_histogram* h = NULL; @@ -613,6 +660,7 @@ static struct mu_result all_tests(void) { mu_run_test(test_create); mu_run_test(test_invalid_init); + mu_run_test(test_timespec_from_double); mu_run_test(test_bucket_config_shift_overflow); mu_run_test(test_bucket_config_reject_defines_cfg); mu_run_test(test_create_with_large_values); diff --git a/test/regression-log-start-time-overflow.hlog b/test/regression-log-start-time-overflow.hlog new file mode 100644 index 00000000..b3dd1538 --- /dev/null +++ b/test/regression-log-start-time-overflow.hlog @@ -0,0 +1,66 @@ +#[Logged with jHiccup version 2.0.7-SNAPSHOT] +#[Histogram log format version 1.2] +#[StartTime: 1403476110183.0 (seconds since epoch), Mon Jun 23 10:28:30 NZST 2014] +"StartTimestamp","Interval_Length","Interval_Max","Interval_Compressed_Histogram" +0.127,1.007,2.769,HISTFAAAAEV42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPEBEJISEuATEZMQ4uASkhIR4nrxg9v2lMaxhvMekILGZkKmcCAEf2CsI= +1.134,0.999,0.442,HISTFAAAAEJ42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEWLj45FTExAT4pBSEBKa6UkAgBi1uM7xjfMMlwMDABAC0CCjM= +2.133,1.001,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBE+Ph4OLgk5OSkeIS4+LgEeswIDo1+MbmdYNASYAA51CSo= +3.134,1.001,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBExPiEpITEFGTkRKSEeOR6FkCg1hTeMXvNYlHhYABQ5CTo= +4.135,0.997,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBE2PiERBREpBREhER4+Hj4uvQAdrTlMBldYDDhYAAugCKk= +5.132,1.002,0.426,HISTFAAAAEF42pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPEBEWPhElOR4pARUpKTkpGQkxq2mMegZnGI0+MZuIcAEAHo8Jvw== +6.134,0.999,0.442,HISTFAAAAEF42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEWIS4FITEhDiEJERE+GT6ZkhZGLbl7jEqrWHREmFgAIbAJMw== +7.133,0.999,0.459,HISTFAAAAEJ42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNEhEtMQEBBTk5MQERCRkBEQEWlh9FJbg9jE+MS5ig1LhYmADkkCcE= +8.132,1.000,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEWIREgEOIQEuGT4xHg41Oo0pIqu8LYwVImwMfGBAAfkgkw +9.132,1.751,1551.892,HISTFAAAAJZ42pNpmSzMwMB0nQECmCCUnwIDA7ObwY4FDPYfYDJMXFxsbGwMbBwszDwsDDxsHFw6RWJMLJMZmcqBMJrJmskSiA2ZZJmkgRBCgmheIORGI1H5rEzMQAyDzFhY2EWRWUwMWCBxQtQQhAIWJiyAaEHyFbKwsLHAADYWAWmiFeKS5gACLsIEzdQICAgBIQShEfhFABXDF+M= +10.883,0.250,0.426,HISTFAAAAD142pNpmSzMwMAgxQABTBDKT4GBgdnNYMcCBvsPEBEeFi4mPg4WLhY2BjY2FhYOBSkpASEtoRA+NgDkCQZR +11.133,1.003,0.524,HISTFAAAAER42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPUBk2HgkZKREpEQUeGSEBAQ6xSYxhCnp7GJ02sWgJsbCwMgEAO0AJSQ== +12.136,0.997,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPUBk2AT4eCQURHgkuEREOHjERlSQhhWuMSV9Y7ERYWAAa4gko +13.133,0.998,0.459,HISTFAAAAD942pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPMBkRIR4RMRk5KQE+PgEhMRmzEjWZJ4whW1hMBNiYAB42CTA= +14.131,1.000,0.492,HISTFAAAAEN42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPUBkWFhE5GT4FKQkRCR4ZCREpqwmMBhpHGG16WHx42JgYmAA6swk+ +15.131,1.001,0.442,HISTFAAAAD542pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPMBkuMTEFHgklFRkRATkJERGdKgudfYwRTSwGalwAF2IJOw== +16.132,1.001,0.524,HISTFAAAAEZ42pNpmSzMwMCgxAABTBDKT4GBgdnNYMcCBvsPEBE2IQEFCQkpGREpHj4hKS6NU4z7GDMkuBoYDSYw2wiwMLEyAQBQ3wne +17.133,0.998,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPUBk2DjElIR4RHiExKQE5IT61iCodtXWMdn0sKVJMTAAekAk0 +18.131,1.000,0.459,HISTFAAAAEF42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPUBkWISERJSUJESklHhEJEREhqwZGLakPjDZdLBYCHCwAKOkJPg== +19.131,1.000,0.475,HISTFAAAAEF42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPUAk2HjkJBSk+Pi4BMT4xIQE9pxIluTOMPhtYbITY2JgAKLoJOQ== +20.131,1.004,0.475,HISTFAAAAEF42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBFmPhEJOSEhDi4+ETEeASEhswIVi1+MFjtYvCRYGJgAIP8JNw== +21.135,0.999,0.492,HISTFAAAAEB42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNMhk1AjINDRECAj4+Hi49LKS5CS2EGo1kXa4ANExMDEwAmOQil +22.134,0.997,0.459,HISTFAAAAEB42pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPEBFmHhE+MRExCTEZAS4RMQERvRI1hSuMTidY3KQ4mAAXhgks +23.131,1.004,0.508,HISTFAAAAEB42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNMhotHSEBASEyMg09MQUSIT6tKS2YKY8gfFj8tJmYmJgAsowkz +24.135,0.998,0.492,HISTFAAAAEJ42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPEBEBLjkhETEpET4BISEhCR6FsqAQFY1jjBoTWPQEOJiZAC2aCUY= +25.133,1.002,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPUBkuHh4BITEpMSEpLiE5AS6FoAgdpQuMJk9YzMRYmAAdngk2 +26.135,0.998,0.508,HISTFAAAAER42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPUAkOKSEJKTUJOT4+IQkeIT69LYwVCnIbGI0eMZtJsTAxMwEAQvkJyg== +27.133,0.998,0.442,HISTFAAAAEN42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPEBE2CQUZFTkZOSURKQkRMT6NKYwhbYxaOocY/a4xSUmwAQA4pQpb +28.131,1.002,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBGtFDcHIy0jDQUdPjENFZUzjNNYHCT4uBQkzJiYADIGCcY= +29.133,1.460,968.884,HISTFAAAAJZ42pNpmSzMwMDUwgABTBDKT4GBgdnNYMcCBvsPEBE5AwMDJSUFISk2ETYuAS6PQ0xSXCzsTEw7GZnKgdCTyZLJGog1maSZZIFYGkpLMnEz8QIhOolgcTKxAiEzmGRFYxMShbEYUCAalzRBsjSjARYmTIBNjDKFSIIsIMDGAgPYWJRJE1DIxQEEaAQHF2GCNDVsAE2dFJE= +30.593,0.541,0.459,HISTFAAAAEB42pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPEBEFCxUNBRkFMTE+Pj4ZHgGHFYwGIkJcMiIpbEwMTAAdQQhJ +31.134,0.997,0.737,HISTFAAAAER42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPEJGAHsYexqKaIAcPPRMVKTEhoR6mJUxqfBx8LFwCTOxM0kwAfR8KqA== +32.131,1.002,0.508,HISTFAAAAEJ42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNEJKCDMcHJw8jOTUfNSEZGQuUb4x9GHxkJDg2hMA4WViYmAHWrC2k= +33.133,1.000,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBGXGK8QHS09PRM9BRMxBa55jBOY03REhByE3DhYADicCkc= +34.133,0.998,0.442,HISTFAAAAEB42pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPEBE1NzsfJwMVEw0pFS0hOZm4FqYKPy2FAoUJjFIsTAA/mQql +35.131,1.000,0.459,HISTFAAAAEN42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPEBERMy0jPTk5LRUFJQk1GamYdUzHGO0UxIrUljBKsbEwAQBKXgqU +36.131,1.001,0.557,HISTFAAAAEd42pNpmSzMwMCgygABTBDKT4GBgdnNYMcCBvsPEBExJzcNMyU5PRUpLSkJKYWwHqYWRjslkTKNC4wKHGwMTExArUwAi/IKnA== +37.132,1.002,0.442,HISTFAAAAEJ42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPEBEFLRsVPQkTKTkhPT4ZBTm3V4yTGD20pFoYtZqYxESYAEjICok= +38.134,1.000,0.803,HISTFAAAAEJ42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNERM7Hwk3LRslMSkZMQExDLGQL0yTGIC2pKJ1VjCwcTJpMAFufCso= +39.134,0.997,0.492,HISTFAAAAEN42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPEBE5Oz8DPRsFORM5FQkNKaGCA8wtjCoSfBYSTYxCLEBtTABiWgor +40.131,1.000,0.442,HISTFAAAAEF42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBExJQUNFTElFRUZBRUZDTGfJqYKHzmhHka5ZUwSQmwANK0J+g== +41.131,1.002,0.475,HISTFAAAAEV42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPEBE2Hj45PiEFGSU5EQkpKREJuVmMLYwaWk8YQyYwa3CxMTABAEOgCdQ= +42.133,1.000,0.459,HISTFAAAAD942pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPMBk+Lg4+ER4hMT4hIT4lLh69OAOZZ4wOr1hCpFiYABjUCSY= +43.133,1.002,0.442,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBFmLgEJMTERHjEuCRERBSERoww5rRuMendYPFRYAA3tCTM= +44.135,0.998,0.590,HISTFAAAAEJ42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPUBk+FT0lJTktJSUjOTE1OQGpmnOMdnorGF3WMemxCTIBAEAhCnU= +45.133,0.998,0.442,HISTFAAAAEJ42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEWMS0DIyMFOSsNPTEFMSGNA4x+LxidfOp0VjBKcLAAAECLCv4= +46.131,1.004,0.442,HISTFAAAAEF42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEuMS0VEyMlLSkzGQUJOSkJj6RnjE56WxjNWpik2JgAO34KfQ== +47.135,0.996,0.475,HISTFAAAAEF42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNUgk2GR0ZOQkSAR4aLS0KKTyNtDqOWxjVGu2fMGlJMTEwANsIJvA== +48.131,1.950,1803.551,HISTFAAAAKF42pNpmSzMwMD0mQECmCCUnwIDA7ObwY4FDPYfICKsTExMLCysLCxsbEwMTAIsDHIsWTwsbNsZmcqZKpncmayZLIFYnUmWSRoMIbQkEy8TNxQjkwgWJxMrGDJDaews/KIMKBCNSytBZCYqYGHCBNjEiBckoJAFBNhYYADBwipIhkIC0lwcQIBGcHARJqigBkwKCQgICSAIFA75IlwAeB8ZpQ== +50.081,0.050,0.393,HISTFAAAADl42pNpmSzMwMAgxgABTBDKT4GBgdnNYMcCBvsPEBE2BiYWNiYWZiYGJiZmJg4OLiYuFiYWAMWGBSM= +50.131,1.001,0.442,HISTFAAAAEF42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBE2Lj4VAQkuJT45KTkOKSExI68eRgeDvB2MfcxxckwAJD8JyA== +51.132,0.999,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPUBk2NgUFGSkNAQEeJSkuKSmxhAojhZADjKuYiyS4WAAlWgm/ +52.131,1.002,0.557,HISTFAAAAER42pNpmSzMwMCgxAABTBDKT4GBgdnNYMcCBvsPUBkWPjEFGSMZKQMJJSEhPgkJiyodjZIHjB+YSvh4mBiYWJkAVc8KVw== +53.133,0.998,0.442,HISTFAAAAEJ42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBE2HjklJR0VPSUDHTUxJSkJs02MuxhtrLxKHjH6cbEAADjeCuw= +54.131,1.003,0.442,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPMBkNPzMLIw0NLQ0pFTERCTGLT4wpQSVbGFcwynExAQA/uwsC +55.134,0.997,0.426,HISTFAAAAD942pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPUBkWFTUjCy01BQ0VFRUJGSkJjRamiqA5jHmXGIV4ACoyCmo= +56.131,1.000,0.459,HISTFAAAAEF42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNUhk1FzsrAQElFQ0xCQkJOTEDnE6ObxwrGDsYuJjUODiYASN8KbA== +57.131,1.000,0.459,HISTFAAAAEF42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPMBk5FT0JAzUNKTklKQ0FMaGUJ4wJFjcYk+4wqnAwMAEAQooK6Q== +58.131,1.002,0.442,HISTFAAAAEB42pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPEBEuCRMNJwMlIzUtLR0ZMREZv6IHjFYGdUXLGE14WAA4OwsG +59.133,0.998,0.442,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPMBklExUdIwcdFRUlOTMZPhWXB4wBTssYsy4xKnGwAQA8bAry +60.131,1.000,0.524,HISTFAAAAEJ42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBFmIRcjPR0bFR0lDSk5KQkZpXlMXkF5qxh3MMqIcDIBADy8CoE= +61.131,1.000,26.083,HISTFAAAAF542pNpmSzMwMAQyAABTBDKT4GBgdnNYMcCBvsPMBkFHSMrCzEZLSUFCSkJOTmTf4xRQW2MYT8Y5diYdjIylTNVMrkzWTJZA7EmkzQYykJpSSZeJm4ghpAQFgATDg85 From 113e566e6d92d9fa306cecac17489582625a359c Mon Sep 17 00:00:00 2001 From: fcostaoliveira Date: Tue, 15 Sep 2026 13:54:58 +0100 Subject: [PATCH 2/3] fix: avoid int64_t->long narrowing warning on 32-bit long (MSVC C4244) The Windows x86 legs emit, on the line this PR added: hdr_time.c(109,27): warning C4244: '=': conversion from 'int64_t' to 'long', possible loss of data tv_sec is a long in the Windows/Cygwin hdr_timespec, so assigning an int64_t to it narrows. The -Wall -Wextra -Wconversion comparison in the PR description could not have caught this: it ran on LP64, where int64_t and long are the same type. Key both the bound and the cast to long, so they agree by construction on every platform and neither narrows. This also matches hdr_gettime's Windows branch just above, which already does (long) integral. Deriving the bound from sizeof(long) rather than sizeof(tv_sec) is never narrower than the int it replaces, so no platform loses range relative to the pre-fix code; the only configuration where the previous revision of this patch was more permissive is a 32-bit target with a 64-bit time_t, which is also exactly where it warned. stdint.h is no longer needed here. The test's guard moves to sizeof(long) to match, otherwise it would assert the wide value parses on a 32-bit platform whose tv_sec is 64-bit but whose long is not. Verified: ctest 5/5 under gcc ASan+UBSan and clang ASan+UBSan+float-cast-overflow; warning count on hdr_time.c back to main's 2 pre-existing; boundary sweep still accepts up to 2^63-1024 and exactly -2^63, and rejects one past either end. Co-Authored-By: Claude Opus 5 --- src/hdr_time.c | 7 +++---- test/hdr_histogram_test.c | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/hdr_time.c b/src/hdr_time.c index 8ebf63de..a76dac4b 100644 --- a/src/hdr_time.c +++ b/src/hdr_time.c @@ -6,7 +6,6 @@ #include #include -#include #include @@ -91,8 +90,8 @@ double hdr_timespec_as_double(const hdr_timespec* t) void hdr_timespec_from_double(hdr_timespec* t, double value) { - /* 2^(bits-1) is exact as a double and one past what tv_sec can hold */ - const double limit = ldexp(1.0, (int) (sizeof(t->tv_sec) * CHAR_BIT) - 1); + /* tv_sec is a long on Windows; 2^(bits-1) is exact as a double and one past its max */ + const double limit = ldexp(1.0, (int) (sizeof(long) * CHAR_BIT) - 1); double seconds = trunc(value); int milliseconds; @@ -106,6 +105,6 @@ void hdr_timespec_from_double(hdr_timespec* t, double value) milliseconds = (int) round((value - seconds) * 1000); - t->tv_sec = (int64_t) seconds; + t->tv_sec = (long) seconds; t->tv_nsec = milliseconds * 1000000; } diff --git a/test/hdr_histogram_test.c b/test/hdr_histogram_test.c index d61ed8dd..f8fea02d 100644 --- a/test/hdr_histogram_test.c +++ b/test/hdr_histogram_test.c @@ -172,9 +172,9 @@ static char* test_timespec_from_double(void) mu_assert("negative seconds wrong", compare_int64(INT64_C(-2), (int64_t) t.tv_sec)); mu_assert("negative nanoseconds wrong", compare_int64(INT64_C(0), (int64_t) t.tv_nsec)); - /* Exceeds int but fits a 64-bit tv_sec; reachable from a log header StartTime. */ + /* Exceeds int but fits a 64-bit long; reachable from a log header StartTime. */ hdr_timespec_from_double(&t, 1403476110183.0); - if (sizeof(t.tv_sec) >= 8) + if (sizeof(long) >= 8) { mu_assert("wide seconds wrong", compare_int64(INT64_C(1403476110183), (int64_t) t.tv_sec)); } From 426f5ff0f5b821cd939d40d734e865f878d0c024 Mon Sep 17 00:00:00 2001 From: fcostaoliveira Date: Tue, 29 Sep 2026 16:23:14 +0100 Subject: [PATCH 3/3] fix: use the destination seconds width on POSIX and Windows --- src/hdr_time.c | 8 ++++++-- test/hdr_histogram_test.c | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/hdr_time.c b/src/hdr_time.c index a76dac4b..9dbb4fd6 100644 --- a/src/hdr_time.c +++ b/src/hdr_time.c @@ -90,8 +90,8 @@ double hdr_timespec_as_double(const hdr_timespec* t) void hdr_timespec_from_double(hdr_timespec* t, double value) { - /* tv_sec is a long on Windows; 2^(bits-1) is exact as a double and one past its max */ - const double limit = ldexp(1.0, (int) (sizeof(long) * CHAR_BIT) - 1); + /* Bound by the destination: time_t on POSIX, long on Windows/Cygwin. */ + const double limit = ldexp(1.0, (int) (sizeof(t->tv_sec) * CHAR_BIT) - 1); double seconds = trunc(value); int milliseconds; @@ -105,6 +105,10 @@ void hdr_timespec_from_double(hdr_timespec* t, double value) milliseconds = (int) round((value - seconds) * 1000); +#if defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__) t->tv_sec = (long) seconds; +#else + t->tv_sec = (time_t) seconds; +#endif t->tv_nsec = milliseconds * 1000000; } diff --git a/test/hdr_histogram_test.c b/test/hdr_histogram_test.c index 1915ec60..bce72e2a 100644 --- a/test/hdr_histogram_test.c +++ b/test/hdr_histogram_test.c @@ -173,9 +173,9 @@ static char* test_timespec_from_double(void) mu_assert("negative seconds wrong", compare_int64(INT64_C(-2), (int64_t) t.tv_sec)); mu_assert("negative nanoseconds wrong", compare_int64(INT64_C(0), (int64_t) t.tv_nsec)); - /* Exceeds int but fits a 64-bit long; reachable from a log header StartTime. */ + /* Exceeds int but fits a 64-bit destination; reachable from a log header. */ hdr_timespec_from_double(&t, 1403476110183.0); - if (sizeof(long) >= 8) + if (sizeof(t.tv_sec) >= 8) { mu_assert("wide seconds wrong", compare_int64(INT64_C(1403476110183), (int64_t) t.tv_sec)); }