https://github.com/bozicrHT updated 
https://github.com/llvm/llvm-project/pull/209856

From 1359a8294268cc2ff32f1656d0967daede96b07a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Radovan=20Bo=C5=BEi=C4=87?= <[email protected]>
Date: Wed, 15 Jul 2026 13:44:08 +0200
Subject: [PATCH 1/4] [clang-tidy] Add optional time_t narrowing diagnostics

Extend bugprone-narrowing-conversion with a new
WarnOnTimeTNarrowingConversion option for detecting conversions from
time_t values to integer types that cannot represent the full range of
time_t.

This is useful for Y2038-related audits, where truncating time_t into
int, or other narrower integer types can silently lose information on
platforms with 64-bit time_t.

The new mode detects both implicit and explicit conversions involving
time_t, including cases where time_t is hidden behind typedef chains,
std::time_t, or used inside arithmetic expressions. It also covers
explicit casts such as:

  uint32_t u32 = (uint32_t)time(nullptr);

The option is disabled by default, so the existing behavior of
bugprone-narrowing-conversion is unchanged unless the new option is
enabled. Bool conversions are intentionally ignored and diagnostics are
only emitted when destination integer type is not wide enough to
preserve the full range of time_t.

Add tests for implicit conversions, function arguments, explicit C-style
casts, static_cast, functional casts, typedef aliases, std::time_t,
macro-expanded casts, and time_t expressions.
---
 .../bugprone/NarrowingConversionsCheck.cpp    | 122 ++++++++++++++++++
 .../bugprone/NarrowingConversionsCheck.h      |  10 ++
 .../bugprone/narrowing-conversions-time-t.cpp |  78 +++++++++++
 3 files changed, 210 insertions(+)
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/bugprone/narrowing-conversions-time-t.cpp

diff --git 
a/clang-tools-extra/clang-tidy/bugprone/NarrowingConversionsCheck.cpp 
b/clang-tools-extra/clang-tidy/bugprone/NarrowingConversionsCheck.cpp
index 934b365a07cad..3310c9f77123d 100644
--- a/clang-tools-extra/clang-tidy/bugprone/NarrowingConversionsCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/NarrowingConversionsCheck.cpp
@@ -55,6 +55,7 @@ 
NarrowingConversionsCheck::NarrowingConversionsCheck(StringRef Name,
       WarnWithinTemplateInstantiation(
           Options.get("WarnWithinTemplateInstantiation", false)),
       WarnOnEquivalentBitWidth(Options.get("WarnOnEquivalentBitWidth", true)),
+      
WarnOnTimeTNarrowingConversion(Options.get("WarnOnTimeTNarrowingConversion", 
false)),
       IgnoreConversionFromTypes(Options.get("IgnoreConversionFromTypes", "")),
       PedanticMode(Options.get("PedanticMode", false)) {}
 
@@ -69,6 +70,7 @@ void NarrowingConversionsCheck::storeOptions(
   Options.store(Opts, "WarnWithinTemplateInstantiation",
                 WarnWithinTemplateInstantiation);
   Options.store(Opts, "WarnOnEquivalentBitWidth", WarnOnEquivalentBitWidth);
+  Options.store(Opts, "WarnOnTimeTNarrowingConversion", 
WarnOnTimeTNarrowingConversion);
   Options.store(Opts, "IgnoreConversionFromTypes", IgnoreConversionFromTypes);
   Options.store(Opts, "PedanticMode", PedanticMode);
 }
@@ -184,6 +186,24 @@ void 
NarrowingConversionsCheck::registerMatchers(MatchFinder *Finder) {
           unless(hasOperatorName("=")))
           .bind("binary_op"),
       this);
+
+  // Explicit casts are checked only in the time_t narrowing mode,
+  // so the default behavior of this check remains unchanged.
+  if (WarnOnTimeTNarrowingConversion) {
+    Finder->addMatcher(
+        traverse(
+            TK_AsIs,
+            explicitCastExpr(
+                hasDestinationType(hasUnqualifiedDesugaredType(builtinType())),
+                hasSourceExpression(
+                    expr(hasType(hasUnqualifiedDesugaredType(builtinType())))),
+                unless(hasSourceExpression(IsCeilFloorCallExpr)),
+                WarnWithinTemplateInstantiation
+                    ? stmt()
+                    : stmt(unless(isInTemplateInstantiation())))
+                .bind("explicit_cast")),
+        this);
+  }
 }
 
 static const BuiltinType *getBuiltinType(const Expr &E) {
@@ -238,6 +258,48 @@ struct IntegerRange {
   llvm::APSInt Upper;
 };
 
+bool hasTimeTTypedef(QualType QT) {
+  while (true) {
+    const auto *T = QT.getTypePtrOrNull();
+    if (!T)
+      return false;
+
+    if (const auto *TT = dyn_cast<TypedefType>(T)) {
+      const auto *TD = TT->getDecl();
+      if (TD->getName() == "time_t" || TD->getName() == "__time_t")
+        return true;
+    }
+
+    QualType NextInChain = QT->getLocallyUnqualifiedSingleStepDesugaredType();
+    NextInChain = NextInChain.getNonReferenceType().getUnqualifiedType();
+    if (NextInChain == QT)
+      break;
+
+    QT = NextInChain;
+  }
+
+  return false;
+}
+
+bool exprMentionsTimeT(const Expr *E) {
+
+  if (!E)
+    return false;
+
+  E = E->IgnoreParenImpCasts();
+
+  if (hasTimeTTypedef(E->getType()))
+    return true;
+
+  for (const Stmt *Child : E->children()) {
+    if (const auto *ChildExpr = dyn_cast_or_null<Expr>(Child))
+      if (exprMentionsTimeT(ChildExpr))
+        return true;
+  }
+
+  return false;
+}
+
 } // namespace
 
 static IntegerRange createFromType(const ASTContext &Context,
@@ -387,6 +449,54 @@ void NarrowingConversionsCheck::diagNarrowTypeOrConstant(
     diagNarrowType(SourceLoc, Lhs, Rhs);
 }
 
+void NarrowingConversionsCheck::diagTimeTConversion(SourceLocation SourceLoc,
+                                                    const Expr &Lhs,
+                                                    const Expr &Rhs) {
+  diag(SourceLoc,
+       "conversion from %0 to %1 may not preserve the full range of time_t")
+      << getUnqualifiedType(Rhs) << getUnqualifiedType(Lhs);
+}
+
+void NarrowingConversionsCheck::handleTimeTOnlyCast(
+    const ASTContext &Context, const CastExpr &Cast) {
+  const Expr &Lhs = Cast;
+  const Expr &Rhs = *Cast.getSubExprAsWritten();
+
+  if (Lhs.isInstantiationDependent() || Rhs.isInstantiationDependent())
+    return;
+
+  const SourceLocation SourceLoc = Cast.getExprLoc();
+  handleTimeTConversion(Context, SourceLoc, Lhs, Rhs);
+}
+
+void NarrowingConversionsCheck::handleTimeTConversion(
+  const ASTContext &Context, SourceLocation SourceLoc,
+  const Expr &Lhs, const Expr &Rhs) {
+  if (!WarnOnTimeTNarrowingConversion)
+    return;
+
+  if (!exprMentionsTimeT(&Rhs))
+    return;
+
+  const BuiltinType *FromType = getBuiltinType(Rhs);
+  const BuiltinType *ToType = getBuiltinType(Lhs);
+
+  if (!FromType || !ToType)
+    return;
+
+  if (!ToType->isInteger() || !FromType->isInteger())
+    return;
+
+  // Usually not useful for Y2038. Keep bool conversions quiet.
+  if (ToType->getKind() == BuiltinType::Bool)
+    return;
+
+  if (isWideEnoughToHold(Context, *FromType, *ToType))
+    return;
+
+  diagTimeTConversion(SourceLoc, Lhs, Rhs);
+}
+
 void NarrowingConversionsCheck::handleIntegralCast(const ASTContext &Context,
                                                    SourceLocation SourceLoc,
                                                    const Expr &Lhs,
@@ -572,6 +682,10 @@ void 
NarrowingConversionsCheck::handleConditionalOperatorArgument(
 
 void NarrowingConversionsCheck::handleImplicitCast(
     const ASTContext &Context, const ImplicitCastExpr &Cast) {
+  if (WarnOnTimeTNarrowingConversion) {
+    handleTimeTOnlyCast(Context, Cast);
+    return;
+  }
   if (Cast.getExprLoc().isMacroID())
     return;
   const Expr &Lhs = Cast;
@@ -626,6 +740,12 @@ void NarrowingConversionsCheck::handleBinaryOperator(const 
ASTContext &Context,
   const Expr &Rhs = *Op.getRHS();
   if (Lhs.isInstantiationDependent() || Rhs.isInstantiationDependent())
     return;
+
+  if (WarnOnTimeTNarrowingConversion) {
+    handleTimeTConversion(Context, Op.getExprLoc(), Lhs, Rhs);
+    return;
+  }
+
   if (handleConditionalOperator(Context, Lhs, Rhs))
     return;
   handleBinaryOperator(Context, Rhs.getBeginLoc(), Lhs, Rhs);
@@ -636,6 +756,8 @@ void NarrowingConversionsCheck::check(const 
MatchFinder::MatchResult &Result) {
     handleBinaryOperator(*Result.Context, *Op);
   else if (const auto *Cast = Result.Nodes.getNodeAs<ImplicitCastExpr>("cast"))
     handleImplicitCast(*Result.Context, *Cast);
+  else if (const auto *ECast = 
Result.Nodes.getNodeAs<ExplicitCastExpr>("explicit_cast"))
+    handleTimeTOnlyCast(*Result.Context, *ECast);
   else
     llvm_unreachable("must be binary operator or cast expression");
 }
diff --git a/clang-tools-extra/clang-tidy/bugprone/NarrowingConversionsCheck.h 
b/clang-tools-extra/clang-tidy/bugprone/NarrowingConversionsCheck.h
index e506e5b0315db..035a82383a0a5 100644
--- a/clang-tools-extra/clang-tidy/bugprone/NarrowingConversionsCheck.h
+++ b/clang-tools-extra/clang-tidy/bugprone/NarrowingConversionsCheck.h
@@ -53,6 +53,9 @@ class NarrowingConversionsCheck : public ClangTidyCheck {
                                 SourceLocation SourceLoc, const Expr &Lhs,
                                 const Expr &Rhs);
 
+  void diagTimeTConversion(SourceLocation SourceLoc, const Expr &Lhs,
+                           const Expr &Rhs);
+
   void handleIntegralCast(const ASTContext &Context, SourceLocation SourceLoc,
                           const Expr &Lhs, const Expr &Rhs);
 
@@ -79,6 +82,10 @@ class NarrowingConversionsCheck : public ClangTidyCheck {
   void handleFloatingCast(const ASTContext &Context, SourceLocation SourceLoc,
                           const Expr &Lhs, const Expr &Rhs);
 
+  void handleTimeTConversion(const ASTContext &Context,
+                             SourceLocation SourceLoc, const Expr &Lhs,
+                             const Expr &Rhs);
+
   void handleBinaryOperator(const ASTContext &Context, SourceLocation 
SourceLoc,
                             const Expr &Lhs, const Expr &Rhs);
 
@@ -93,6 +100,8 @@ class NarrowingConversionsCheck : public ClangTidyCheck {
   void handleBinaryOperator(const ASTContext &Context,
                             const BinaryOperator &Op);
 
+  void handleTimeTOnlyCast(const ASTContext &Context, const CastExpr &Cast);
+
   bool isWarningInhibitedByEquivalentSize(const ASTContext &Context,
                                           const BuiltinType &FromType,
                                           const BuiltinType &ToType) const;
@@ -102,6 +111,7 @@ class NarrowingConversionsCheck : public ClangTidyCheck {
   const bool WarnOnFloatingPointNarrowingConversion;
   const bool WarnWithinTemplateInstantiation;
   const bool WarnOnEquivalentBitWidth;
+  const bool WarnOnTimeTNarrowingConversion;
   const StringRef IgnoreConversionFromTypes;
   const bool PedanticMode;
 };
diff --git 
a/clang-tools-extra/test/clang-tidy/checkers/bugprone/narrowing-conversions-time-t.cpp
 
b/clang-tools-extra/test/clang-tidy/checkers/bugprone/narrowing-conversions-time-t.cpp
new file mode 100644
index 0000000000000..de2880e6d83b6
--- /dev/null
+++ 
b/clang-tools-extra/test/clang-tidy/checkers/bugprone/narrowing-conversions-time-t.cpp
@@ -0,0 +1,78 @@
+// RUN: %check_clang_tidy -check-suffix=DEFAULT %s \
+// RUN: bugprone-narrowing-conversions %t -- \
+// RUN: -config='{CheckOptions: 
{bugprone-narrowing-conversions.WarnOnTimeTNarrowingConversion: true}}'
+
+typedef long time_t;
+typedef time_t mytime;
+typedef mytime opaque_time;
+
+namespace std {
+    using ::time_t;
+}
+
+using int32_t = int;
+using uint32_t = unsigned int;
+using int64_t = long;
+using uint64_t = unsigned long;
+
+time_t time(time_t *);
+
+void takes_int(int);
+int returns_int(float f) {
+    return f;
+}
+
+void ignore(int i, long l, double d) {
+    short s1 = i;
+    short s2 = (short)i;
+    int i1 = l, i2 = (int)l;
+    float f = d;
+    takes_int(l);
+}
+
+void implicit(time_t t) {
+
+    int32_t i32 = t;
+    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:19: warning: conversion from 
'time_t' (aka 'long') to 'int32_t' (aka 'int') may not preserve the full range 
of time_t
+
+    uint32_t u32 = t;
+    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:20: warning: conversion from 
'time_t' (aka 'long') to 'uint32_t' (aka 'unsigned int') may not preserve the 
full range of time_t
+
+    takes_int(t);
+    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:15: warning: conversion from 
'time_t' (aka 'long') to 'int' may not preserve the full range of time_t
+
+    bool b = t;
+}
+
+void explicit_test(time_t t) {
+    int i = (int)t;
+    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:13: warning: conversion from 
'time_t' (aka 'long') to 'int' may not preserve the full range of time_t
+
+    int32_t i32 = (int32_t)t;
+    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:19: warning: conversion from 
'time_t' (aka 'long') to 'int32_t' (aka 'int') may not preserve the full range 
of time_t
+
+    short s = (short)t;
+    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:15: warning: conversion from 
'time_t' (aka 'long') to 'short' may not preserve the full range of time_t
+
+    int c = static_cast<int>(t);
+    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:13: warning: conversion from 
'time_t' (aka 'long') to 'int' may not preserve the full range of time_t
+
+    int j = int(t);
+    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:13: warning: conversion from 
'time_t' (aka 'long') to 'int' may not preserve the full range of time_t
+}
+
+void misc(std::time_t t, int offset) {
+    int i = t;
+    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:13: warning: conversion from 
'std::time_t' (aka 'long') to 'int' may not preserve the full range of time_t
+
+    opaque_time op = time(nullptr);
+    uint32_t u32 = (uint32_t)op;
+    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:20: warning: conversion from 
'opaque_time' (aka 'long') to 'uint32_t' (aka 'unsigned int') may not preserve 
the full range of time_t
+
+    int oper = t + offset;
+    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:16: warning: conversion from 
'std::time_t' (aka 'long') to 'int' may not preserve the full range of time_t
+
+#define TO_UINT32(arg) ((uint32_t)(arg))
+    uint32_t trunc = TO_UINT32(t);
+    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:22: warning: conversion from 
'std::time_t' (aka 'long') to 'uint32_t' (aka 'unsigned int') may not preserve 
the full range of time_t
+}

From de689515cb5cdb69c1b47389bafd84122442f2ee Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Radovan=20Bo=C5=BEi=C4=87?= <[email protected]>
Date: Thu, 23 Jul 2026 08:47:37 +0200
Subject: [PATCH 2/4] Fix formatting and test on Windows

---
 .../bugprone/NarrowingConversionsCheck.cpp    | 20 ++++++++------
 .../bugprone/narrowing-conversions-time-t.cpp | 26 +++++++++----------
 2 files changed, 25 insertions(+), 21 deletions(-)

diff --git 
a/clang-tools-extra/clang-tidy/bugprone/NarrowingConversionsCheck.cpp 
b/clang-tools-extra/clang-tidy/bugprone/NarrowingConversionsCheck.cpp
index 3310c9f77123d..a89d3581f9133 100644
--- a/clang-tools-extra/clang-tidy/bugprone/NarrowingConversionsCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/NarrowingConversionsCheck.cpp
@@ -55,7 +55,8 @@ 
NarrowingConversionsCheck::NarrowingConversionsCheck(StringRef Name,
       WarnWithinTemplateInstantiation(
           Options.get("WarnWithinTemplateInstantiation", false)),
       WarnOnEquivalentBitWidth(Options.get("WarnOnEquivalentBitWidth", true)),
-      
WarnOnTimeTNarrowingConversion(Options.get("WarnOnTimeTNarrowingConversion", 
false)),
+      WarnOnTimeTNarrowingConversion(
+          Options.get("WarnOnTimeTNarrowingConversion", false)),
       IgnoreConversionFromTypes(Options.get("IgnoreConversionFromTypes", "")),
       PedanticMode(Options.get("PedanticMode", false)) {}
 
@@ -70,7 +71,8 @@ void NarrowingConversionsCheck::storeOptions(
   Options.store(Opts, "WarnWithinTemplateInstantiation",
                 WarnWithinTemplateInstantiation);
   Options.store(Opts, "WarnOnEquivalentBitWidth", WarnOnEquivalentBitWidth);
-  Options.store(Opts, "WarnOnTimeTNarrowingConversion", 
WarnOnTimeTNarrowingConversion);
+  Options.store(Opts, "WarnOnTimeTNarrowingConversion",
+                WarnOnTimeTNarrowingConversion);
   Options.store(Opts, "IgnoreConversionFromTypes", IgnoreConversionFromTypes);
   Options.store(Opts, "PedanticMode", PedanticMode);
 }
@@ -457,8 +459,8 @@ void 
NarrowingConversionsCheck::diagTimeTConversion(SourceLocation SourceLoc,
       << getUnqualifiedType(Rhs) << getUnqualifiedType(Lhs);
 }
 
-void NarrowingConversionsCheck::handleTimeTOnlyCast(
-    const ASTContext &Context, const CastExpr &Cast) {
+void NarrowingConversionsCheck::handleTimeTOnlyCast(const ASTContext &Context,
+                                                    const CastExpr &Cast) {
   const Expr &Lhs = Cast;
   const Expr &Rhs = *Cast.getSubExprAsWritten();
 
@@ -469,9 +471,10 @@ void NarrowingConversionsCheck::handleTimeTOnlyCast(
   handleTimeTConversion(Context, SourceLoc, Lhs, Rhs);
 }
 
-void NarrowingConversionsCheck::handleTimeTConversion(
-  const ASTContext &Context, SourceLocation SourceLoc,
-  const Expr &Lhs, const Expr &Rhs) {
+void NarrowingConversionsCheck::handleTimeTConversion(const ASTContext 
&Context,
+                                                      SourceLocation SourceLoc,
+                                                      const Expr &Lhs,
+                                                      const Expr &Rhs) {
   if (!WarnOnTimeTNarrowingConversion)
     return;
 
@@ -756,7 +759,8 @@ void NarrowingConversionsCheck::check(const 
MatchFinder::MatchResult &Result) {
     handleBinaryOperator(*Result.Context, *Op);
   else if (const auto *Cast = Result.Nodes.getNodeAs<ImplicitCastExpr>("cast"))
     handleImplicitCast(*Result.Context, *Cast);
-  else if (const auto *ECast = 
Result.Nodes.getNodeAs<ExplicitCastExpr>("explicit_cast"))
+  else if (const auto *ECast =
+               Result.Nodes.getNodeAs<ExplicitCastExpr>("explicit_cast"))
     handleTimeTOnlyCast(*Result.Context, *ECast);
   else
     llvm_unreachable("must be binary operator or cast expression");
diff --git 
a/clang-tools-extra/test/clang-tidy/checkers/bugprone/narrowing-conversions-time-t.cpp
 
b/clang-tools-extra/test/clang-tidy/checkers/bugprone/narrowing-conversions-time-t.cpp
index de2880e6d83b6..d0374ba9b88d3 100644
--- 
a/clang-tools-extra/test/clang-tidy/checkers/bugprone/narrowing-conversions-time-t.cpp
+++ 
b/clang-tools-extra/test/clang-tidy/checkers/bugprone/narrowing-conversions-time-t.cpp
@@ -2,7 +2,7 @@
 // RUN: bugprone-narrowing-conversions %t -- \
 // RUN: -config='{CheckOptions: 
{bugprone-narrowing-conversions.WarnOnTimeTNarrowingConversion: true}}'
 
-typedef long time_t;
+typedef long long time_t;
 typedef time_t mytime;
 typedef mytime opaque_time;
 
@@ -33,46 +33,46 @@ void ignore(int i, long l, double d) {
 void implicit(time_t t) {
 
     int32_t i32 = t;
-    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:19: warning: conversion from 
'time_t' (aka 'long') to 'int32_t' (aka 'int') may not preserve the full range 
of time_t
+    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:19: warning: conversion from 
'time_t' (aka 'long long') to 'int32_t' (aka 'int') may not preserve the full 
range of time_t
 
     uint32_t u32 = t;
-    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:20: warning: conversion from 
'time_t' (aka 'long') to 'uint32_t' (aka 'unsigned int') may not preserve the 
full range of time_t
+    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:20: warning: conversion from 
'time_t' (aka 'long long') to 'uint32_t' (aka 'unsigned int') may not preserve 
the full range of time_t
 
     takes_int(t);
-    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:15: warning: conversion from 
'time_t' (aka 'long') to 'int' may not preserve the full range of time_t
+    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:15: warning: conversion from 
'time_t' (aka 'long long') to 'int' may not preserve the full range of time_t
 
     bool b = t;
 }
 
 void explicit_test(time_t t) {
     int i = (int)t;
-    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:13: warning: conversion from 
'time_t' (aka 'long') to 'int' may not preserve the full range of time_t
+    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:13: warning: conversion from 
'time_t' (aka 'long long') to 'int' may not preserve the full range of time_t
 
     int32_t i32 = (int32_t)t;
-    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:19: warning: conversion from 
'time_t' (aka 'long') to 'int32_t' (aka 'int') may not preserve the full range 
of time_t
+    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:19: warning: conversion from 
'time_t' (aka 'long long') to 'int32_t' (aka 'int') may not preserve the full 
range of time_t
 
     short s = (short)t;
-    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:15: warning: conversion from 
'time_t' (aka 'long') to 'short' may not preserve the full range of time_t
+    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:15: warning: conversion from 
'time_t' (aka 'long long') to 'short' may not preserve the full range of time_t
 
     int c = static_cast<int>(t);
-    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:13: warning: conversion from 
'time_t' (aka 'long') to 'int' may not preserve the full range of time_t
+    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:13: warning: conversion from 
'time_t' (aka 'long long') to 'int' may not preserve the full range of time_t
 
     int j = int(t);
-    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:13: warning: conversion from 
'time_t' (aka 'long') to 'int' may not preserve the full range of time_t
+    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:13: warning: conversion from 
'time_t' (aka 'long long') to 'int' may not preserve the full range of time_t
 }
 
 void misc(std::time_t t, int offset) {
     int i = t;
-    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:13: warning: conversion from 
'std::time_t' (aka 'long') to 'int' may not preserve the full range of time_t
+    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:13: warning: conversion from 
'std::time_t' (aka 'long long') to 'int' may not preserve the full range of 
time_t
 
     opaque_time op = time(nullptr);
     uint32_t u32 = (uint32_t)op;
-    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:20: warning: conversion from 
'opaque_time' (aka 'long') to 'uint32_t' (aka 'unsigned int') may not preserve 
the full range of time_t
+    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:20: warning: conversion from 
'opaque_time' (aka 'long long') to 'uint32_t' (aka 'unsigned int') may not 
preserve the full range of time_t
 
     int oper = t + offset;
-    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:16: warning: conversion from 
'std::time_t' (aka 'long') to 'int' may not preserve the full range of time_t
+    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:16: warning: conversion from 
'std::time_t' (aka 'long long') to 'int' may not preserve the full range of 
time_t
 
 #define TO_UINT32(arg) ((uint32_t)(arg))
     uint32_t trunc = TO_UINT32(t);
-    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:22: warning: conversion from 
'std::time_t' (aka 'long') to 'uint32_t' (aka 'unsigned int') may not preserve 
the full range of time_t
+    // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:22: warning: conversion from 
'std::time_t' (aka 'long long') to 'uint32_t' (aka 'unsigned int') may not 
preserve the full range of time_t
 }

From dfd7b3e76ca9bb7937002a87fec8553bd908145c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Radovan=20Bo=C5=BEi=C4=87?= <[email protected]>
Date: Fri, 24 Jul 2026 11:33:31 +0200
Subject: [PATCH 3/4] Fix clang-format and clang-tidy CI failures

---
 .../bugprone/NarrowingConversionsCheck.cpp    | 49 +++++++++----------
 1 file changed, 23 insertions(+), 26 deletions(-)

diff --git 
a/clang-tools-extra/clang-tidy/bugprone/NarrowingConversionsCheck.cpp 
b/clang-tools-extra/clang-tidy/bugprone/NarrowingConversionsCheck.cpp
index a89d3581f9133..5f8a8dde786e5 100644
--- a/clang-tools-extra/clang-tidy/bugprone/NarrowingConversionsCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/NarrowingConversionsCheck.cpp
@@ -17,6 +17,7 @@
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/SmallString.h"
 
+#include <algorithm>
 #include <cstdint>
 
 using namespace clang::ast_matchers;
@@ -243,24 +244,7 @@ static bool getFloatingConstantExprValue(const ASTContext 
&Context,
   return true;
 }
 
-namespace {
-
-struct IntegerRange {
-  bool contains(const IntegerRange &From) const {
-    return llvm::APSInt::compareValues(Lower, From.Lower) <= 0 &&
-           llvm::APSInt::compareValues(Upper, From.Upper) >= 0;
-  }
-
-  bool contains(const llvm::APSInt &Value) const {
-    return llvm::APSInt::compareValues(Lower, Value) <= 0 &&
-           llvm::APSInt::compareValues(Upper, Value) >= 0;
-  }
-
-  llvm::APSInt Lower;
-  llvm::APSInt Upper;
-};
-
-bool hasTimeTTypedef(QualType QT) {
+static bool hasTimeTTypedef(QualType QT) {
   while (true) {
     const auto *T = QT.getTypePtrOrNull();
     if (!T)
@@ -283,8 +267,7 @@ bool hasTimeTTypedef(QualType QT) {
   return false;
 }
 
-bool exprMentionsTimeT(const Expr *E) {
-
+static bool exprMentionsTimeT(const Expr *E) {
   if (!E)
     return false;
 
@@ -293,14 +276,28 @@ bool exprMentionsTimeT(const Expr *E) {
   if (hasTimeTTypedef(E->getType()))
     return true;
 
-  for (const Stmt *Child : E->children()) {
-    if (const auto *ChildExpr = dyn_cast_or_null<Expr>(Child))
-      if (exprMentionsTimeT(ChildExpr))
-        return true;
+  return std::any_of(E->child_begin(), E->child_end(), [](const Stmt *Child) {
+    const auto *ChildExpr = dyn_cast_or_null<Expr>(Child);
+    return ChildExpr && exprMentionsTimeT(ChildExpr);
+  });
+}
+
+namespace {
+
+struct IntegerRange {
+  bool contains(const IntegerRange &From) const {
+    return llvm::APSInt::compareValues(Lower, From.Lower) <= 0 &&
+           llvm::APSInt::compareValues(Upper, From.Upper) >= 0;
   }
 
-  return false;
-}
+  bool contains(const llvm::APSInt &Value) const {
+    return llvm::APSInt::compareValues(Lower, Value) <= 0 &&
+           llvm::APSInt::compareValues(Upper, Value) >= 0;
+  }
+
+  llvm::APSInt Lower;
+  llvm::APSInt Upper;
+};
 
 } // namespace
 

From fef606d20c9273648da1f2cbde944937bd1d17ee Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Radovan=20Bo=C5=BEi=C4=87?= <[email protected]>
Date: Fri, 24 Jul 2026 12:11:37 +0200
Subject: [PATCH 4/4] Add documentation and ReleaseNote entry

---
 clang-tools-extra/docs/ReleaseNotes.rst       |  6 +++++
 .../checks/bugprone/narrowing-conversions.rst | 25 +++++++++++++++++--
 2 files changed, 29 insertions(+), 2 deletions(-)

diff --git a/clang-tools-extra/docs/ReleaseNotes.rst 
b/clang-tools-extra/docs/ReleaseNotes.rst
index 64dad5fc29905..d87548109227b 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -865,6 +865,12 @@ Changes in existing checks
   note to suggest materializing the temporary range when iterating over 
temporary
   range expressions or initializer lists, as reusing them directly could be 
unsafe.
 
+- Improved :doc:`bugprone-narrowing-conversions
+  <clang-tidy/checks/bugprone/narrowing-conversions>` check by adding the
+  :option:`WarnOnTimeTNarrowingConversion` option to diagnose conversions from
+  ``time_t`` values to integer types that may not preserve the full range of
+  ``time_t``. This option is disabled by default.
+
 Removed checks
 ^^^^^^^^^^^^^^
 
diff --git 
a/clang-tools-extra/docs/clang-tidy/checks/bugprone/narrowing-conversions.rst 
b/clang-tools-extra/docs/clang-tidy/checks/bugprone/narrowing-conversions.rst
index 4327bc09babab..e7fea226dde39 100644
--- 
a/clang-tools-extra/docs/clang-tidy/checks/bugprone/narrowing-conversions.rst
+++ 
b/clang-tools-extra/docs/clang-tidy/checks/bugprone/narrowing-conversions.rst
@@ -15,14 +15,18 @@ We flag narrowing conversions from:
    if WarnOnIntegerNarrowingConversion Option is set,
  - an integer to a narrower floating-point (e.g. ``uint64_t`` to ``float``)
    if WarnOnIntegerToFloatingPointNarrowingConversion Option is set,
+ - a ``time_t`` value, or and expression involving ``time_t``, to an integer
+   type that may not preserve the full range of ``time_t`` if
+   WarnOnTimeTNarrowingConversion Option is set,
  - a floating-point to an integer (e.g. ``double`` to ``int``),
  - a floating-point to a narrower floating-point (e.g. ``double`` to ``float``)
    if WarnOnFloatingPointNarrowingConversion Option is set.
 
 This check will flag:
  - All narrowing conversions that are not marked by an explicit cast (c-style
-   or ``static_cast``). For example: ``int i = 0; i += 0.1;``,
-   ``void f(int); f(0.1);``,
+   or ``static_cast``), except conversions from ``time_t`` when
+   WarnOnTimeTNarrowingConversion Option is set. For example:
+   ``int i = 0; i += 0.1;``, ``void f(int); f(0.1);``,
  - All applications of binary operators with a narrowing conversions.
    For example: ``int i; i+= 0.1;``.
 
@@ -105,6 +109,23 @@ Options
     representable in the destination type (e.g. ``int i = 1.0;``).
     `false` by default.
 
+.. option:: WarnOnTimeTNarrowingConversion
+
+    When `true`, the check will warn on conversion from ``time_t`` or a
+    typedef of ``time_t`` to an integer type that may not preserve the full
+    range of ``time_t``. This includes explicit casts.
+
+    This option is intended to help audit code for Y2038-related truncation
+    issues. For example:
+
+    .. code-block:: c++
+
+      time_t t = time(nullptr);
+      int i = t;
+      uint32_t u32 = (uint32_t)t;
+
+    `false` by default.
+
 FAQ
 ---
 

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to