https://github.com/shivaansharma updated https://github.com/llvm/llvm-project/pull/208162
>From 9bd6d6edb4e9f9d58186c4645ba9c0e66bbd9bea Mon Sep 17 00:00:00 2001 From: shivaan sharma <[email protected]> Date: Wed, 8 Jul 2026 13:01:01 +0530 Subject: [PATCH] [clang-tidy] Don't warn on misplaced-widening-cast for provably non-overflowing constexpr values The check now evaluates constexpr operands at compile time and skips the diagnostic when the value provably does not overflow the narrower type. Non-constexpr const values and constexpr values that do overflow still warn as before. Fixes #208078 --- .../bugprone/MisplacedWideningCastCheck.cpp | 26 +++++++++--- .../bugprone/MisplacedWideningCastCheck.h | 1 + clang-tools-extra/docs/ReleaseNotes.rst | 8 ++++ .../bugprone/misplaced-widening-cast.rst | 42 +++++++++++++++++++ .../misplaced-widening-cast-explicit-only.cpp | 24 +++++++++++ 5 files changed, 95 insertions(+), 6 deletions(-) diff --git a/clang-tools-extra/clang-tidy/bugprone/MisplacedWideningCastCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/MisplacedWideningCastCheck.cpp index 61bbbf8af2abd..378f368119a5e 100644 --- a/clang-tools-extra/clang-tidy/bugprone/MisplacedWideningCastCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/MisplacedWideningCastCheck.cpp @@ -17,11 +17,13 @@ namespace clang::tidy::bugprone { MisplacedWideningCastCheck::MisplacedWideningCastCheck( StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), - CheckImplicitCasts(Options.get("CheckImplicitCasts", false)) {} + CheckImplicitCasts(Options.get("CheckImplicitCasts", false)), + IgnoreConstexprOverflowProven(Options.get("IgnoreConstexprOverflowProven", false)) {} void MisplacedWideningCastCheck::storeOptions( ClangTidyOptions::OptionMap &Opts) { Options.store(Opts, "CheckImplicitCasts", CheckImplicitCasts); + Options.store(Opts, "IgnoreConstexprOverflowProven", IgnoreConstexprOverflowProven); } void MisplacedWideningCastCheck::registerMatchers(MatchFinder *Finder) { @@ -55,12 +57,13 @@ void MisplacedWideningCastCheck::registerMatchers(MatchFinder *Finder) { } static unsigned getMaxCalculationWidth(const ASTContext &Context, - const Expr *E) { + const Expr *E, + bool IgnoreConstexprOverflowProven) { E = E->IgnoreParenImpCasts(); if (const auto *Bop = dyn_cast<BinaryOperator>(E)) { - const unsigned LHSWidth = getMaxCalculationWidth(Context, Bop->getLHS()); - const unsigned RHSWidth = getMaxCalculationWidth(Context, Bop->getRHS()); + const unsigned LHSWidth = getMaxCalculationWidth(Context, Bop->getLHS(),IgnoreConstexprOverflowProven); + const unsigned RHSWidth = getMaxCalculationWidth(Context, Bop->getRHS(),IgnoreConstexprOverflowProven); if (Bop->getOpcode() == BO_Mul) return LHSWidth + RHSWidth; if (Bop->getOpcode() == BO_Add) @@ -90,8 +93,18 @@ static unsigned getMaxCalculationWidth(const ASTContext &Context, return T->isIntegerType() ? Context.getIntWidth(T) : 1024U; } else if (const auto *I = dyn_cast<IntegerLiteral>(E)) { return I->getValue().getActiveBits(); + } else if (IgnoreConstexprOverflowProven) { + if (const auto *DRE = dyn_cast<DeclRefExpr>(E)) { + if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) { + if (VD->isConstexpr()) { + Expr::EvalResult Result; + if (E->EvaluateAsInt(Result, Context) && + !Result.Val.getInt().isNegative()) + return Result.Val.getInt().getActiveBits(); + } + } + } } - return Context.getIntWidth(E->getType()); } @@ -235,7 +248,8 @@ void MisplacedWideningCastCheck::check(const MatchFinder::MatchResult &Result) { // Don't write a warning if we can easily see that the result is not // truncated. - if (Context.getIntWidth(CalcType) >= getMaxCalculationWidth(Context, Calc)) + if (Context.getIntWidth(CalcType) >= + getMaxCalculationWidth(Context, Calc, IgnoreConstexprOverflowProven)) return; diag(Cast->getBeginLoc(), "either cast from %0 to %1 is ineffective, or " diff --git a/clang-tools-extra/clang-tidy/bugprone/MisplacedWideningCastCheck.h b/clang-tools-extra/clang-tidy/bugprone/MisplacedWideningCastCheck.h index 151bbcf028783..4c14d242ff7f7 100644 --- a/clang-tools-extra/clang-tidy/bugprone/MisplacedWideningCastCheck.h +++ b/clang-tools-extra/clang-tidy/bugprone/MisplacedWideningCastCheck.h @@ -34,6 +34,7 @@ class MisplacedWideningCastCheck : public ClangTidyCheck { private: const bool CheckImplicitCasts; + const bool IgnoreConstexprOverflowProven; }; } // namespace clang::tidy::bugprone diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index b85ece288881e..e210f43878158 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -417,6 +417,14 @@ Changes in existing checks positive on bit field assignments when the `CheckImplicitCasts` option is enabled. The check now uses the actual bit field width instead of the declared type to determine if widening occurs. + +- Improved :doc:`bugprone-misplaced-widening-cast + <clang-tidy/checks/bugprone/misplaced-widening-cast>` check by adding the + :option:`IgnoreConstexprOverflowProven` option to suppress false positives + when the calculation operand is a ``constexpr`` variable whose compile-time + value provably fits inside the narrower type without overflow. The check + still warns when the operand is a non-``constexpr`` ``const``, or when the + ``constexpr`` value is negative or overflows. - Improved :doc:`bugprone-move-forwarding-reference <clang-tidy/checks/bugprone/move-forwarding-reference>` check by fixing some diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/misplaced-widening-cast.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/misplaced-widening-cast.rst index cec49c55309ad..70417458b6806 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/misplaced-widening-cast.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/misplaced-widening-cast.rst @@ -57,9 +57,51 @@ written for this code: return (double)(x * 10.0f); } +Constexpr operands +------------------ + +If :option:`IgnoreConstexprOverflowProven` is set to `true` and the calculation +operand is a ``constexpr`` variable, the check evaluates its compile-time value. +If the value provably does not overflow the narrower type and is non-negative, +no warning is issued, since the cast is then not actually masking any loss of +precision: + +.. code-block:: c++ + + void bar(std::size_t); + + void f() { + constexpr int x = 256; + bar(static_cast<std::size_t>(x * 2)); // No warning: x * 2 == 512, + // which safely fits in int. + } + +A plain ``const`` (not ``constexpr``) variable does not get this exception, +since the check does not evaluate non-``constexpr`` values as compile-time +constants. + +A ``constexpr`` value that results in a negative value or signed integer +overflow will still be diagnosed: + +.. code-block:: c++ + + void bar(std::size_t); + + void f() { + constexpr int x = 2147483647; // INT_MAX + bar(static_cast<std::size_t>(x * 2)); // Still warns: calculation overflows int. + } + Options ------- .. option:: CheckImplicitCasts If `true`, enables detection of implicit casts. Default is `false`. + +.. option:: IgnoreConstexprOverflowProven + + When set to `true`, the check evaluates the compile-time value of ``constexpr`` + calculation operands. If the value provably does not overflow the narrower + type and is non-negative, the warning is suppressed because the widening cast + is not masking an unintended truncation. Default is `false`. \ No newline at end of file diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/misplaced-widening-cast-explicit-only.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/misplaced-widening-cast-explicit-only.cpp index 56eba15f2cd0e..d5cd43de67bb7 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/misplaced-widening-cast-explicit-only.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/misplaced-widening-cast-explicit-only.cpp @@ -80,3 +80,27 @@ void nextDay(DaysEnum day) { if (day < SUN) day = static_cast<DaysEnum>(day + 1); } + +// Constexpr values that are provably non-overflowing should not warn, +// even though the cast is technically "misplaced" relative to the +// multiplication. See https://github.com/llvm/llvm-project/issues/208078 +void constexprNoOverflow() { + constexpr int x = 256; + long l = static_cast<long>(x * 2); +} + +// A non-constexpr const does NOT get this exception: 'const' alone does not +// prove the compiler evaluated it as a compile-time constant expression from +// the check's point of view, and this mirrors clang's own narrowing checks. +void constNotConstexprStillWarns() { + const int x = 256; + long l = static_cast<long>(x * 2); + // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: either cast from 'int' to 'long' +} + +// A constexpr value that DOES overflow the narrow type must still warn. +void constexprOverflowStillWarns() { + constexpr int x = -1; + long l = static_cast<long>(x * 2); + // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: either cast from 'int' to 'long' +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
