https://github.com/earnol updated 
https://github.com/llvm/llvm-project/pull/197554

>From 871f3fb7a6a353bafc81e1494c71afc63e818905 Mon Sep 17 00:00:00 2001
From: Vladislav Aranov <[email protected]>
Date: Mon, 18 May 2026 13:45:42 +0200
Subject: [PATCH] [clang-tidy] Fix bugprone-misplaced-widening-cast false
 positive on bit field assignments

When CheckImplicitCasts=true, the checker used the declared type of a
bit field instead of the actual bit field width to determine if widening
occurs. This caused false positives when assigning to a bit field whose
declared type is wider than the source, but whose bit field width
actually matches the source type. This behavior is fixed.
---
 .../bugprone/MisplacedWideningCastCheck.cpp   |  29 ++-
 clang-tools-extra/docs/ReleaseNotes.rst       |   6 +
 ...d-widening-cast-bitfield-explicit-only.cpp |  60 +++++
 ...idening-cast-bitfield-implicit-enabled.cpp | 235 ++++++++++++++++++
 4 files changed, 327 insertions(+), 3 deletions(-)
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/bugprone/misplaced-widening-cast-bitfield-explicit-only.cpp
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/bugprone/misplaced-widening-cast-bitfield-implicit-enabled.cpp

diff --git 
a/clang-tools-extra/clang-tidy/bugprone/MisplacedWideningCastCheck.cpp 
b/clang-tools-extra/clang-tidy/bugprone/MisplacedWideningCastCheck.cpp
index f040235322a4f..c69ba66065438 100644
--- a/clang-tools-extra/clang-tidy/bugprone/MisplacedWideningCastCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/MisplacedWideningCastCheck.cpp
@@ -42,7 +42,10 @@ void 
MisplacedWideningCastCheck::registerMatchers(MatchFinder *Finder) {
   Finder->addMatcher(varDecl(hasInitializer(Cast)), this);
   Finder->addMatcher(returnStmt(hasReturnValue(Cast)), this);
   Finder->addMatcher(callExpr(hasAnyArgument(Cast)), this);
-  Finder->addMatcher(binaryOperator(hasOperatorName("="), hasRHS(Cast)), this);
+  Finder->addMatcher(binaryOperator(hasOperatorName("="),
+                                    hasLHS(expr().bind("AssignLHS")),
+                                    hasRHS(Cast)),
+                     this);
   Finder->addMatcher(
       binaryOperator(isComparisonOperator(), hasEitherOperand(Cast)), this);
 }
@@ -195,14 +198,34 @@ void MisplacedWideningCastCheck::check(const 
MatchFinder::MatchResult &Result) {
   const QualType CastType = Cast->getType();
   const QualType CalcType = Calc->getType();
 
+  // If assigning to a bit field, use the bit field width as the effective
+  // target width. The declared type may be wider than the actual bit field
+  // storage.
+  unsigned TargetWidth = Context.getIntWidth(CastType);
+  bool IsBitfieldAssign = false;
+  if (const auto *AssignLHS = Result.Nodes.getNodeAs<Expr>("AssignLHS")) {
+    if (const auto *ME =
+            dyn_cast<MemberExpr>(AssignLHS->IgnoreParenImpCasts())) {
+      if (const auto *FD = dyn_cast<FieldDecl>(ME->getMemberDecl())) {
+        if (FD->isBitField()) {
+          TargetWidth = FD->getBitWidthValue();
+          IsBitfieldAssign = true;
+        }
+      }
+    }
+  }
+
   // Explicit truncation using cast.
-  if (Context.getIntWidth(CastType) < Context.getIntWidth(CalcType))
+  if (TargetWidth < Context.getIntWidth(CalcType))
     return;
 
   // If CalcType and CastType have same size then there is no real danger, but
   // there can be a portability problem.
 
-  if (Context.getIntWidth(CastType) == Context.getIntWidth(CalcType)) {
+  if (TargetWidth == Context.getIntWidth(CalcType)) {
+    // Bit field width is fixed across platforms — no portability concern.
+    if (IsBitfieldAssign)
+      return;
     const auto *CastBuiltinType =
         dyn_cast<BuiltinType>(CastType->getUnqualifiedDesugaredType());
     const auto *CalcBuiltinType =
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst 
b/clang-tools-extra/docs/ReleaseNotes.rst
index 89fb1684bba7c..a35e955121ff5 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -372,6 +372,12 @@ Changes in existing checks
   <clang-tidy/checks/bugprone/macro-parentheses>` check by printing the macro
   definition in the warning message if the macro is defined on command line.
 
+- Improved :doc:`bugprone-misplaced-widening-cast
+  <clang-tidy/checks/bugprone/misplaced-widening-cast>` check by fixing a false
+  positive on bit field assignments when `CheckImplicitCasts` is enabled. The
+  check now uses the actual bit field width instead of the declared type to
+  determine if widening occurs.
+
 - Improved :doc:`bugprone-move-forwarding-reference
   <clang-tidy/checks/bugprone/move-forwarding-reference>` check by fixing some
   false positives in the context of moved lambda captures.
diff --git 
a/clang-tools-extra/test/clang-tidy/checkers/bugprone/misplaced-widening-cast-bitfield-explicit-only.cpp
 
b/clang-tools-extra/test/clang-tidy/checkers/bugprone/misplaced-widening-cast-bitfield-explicit-only.cpp
new file mode 100644
index 0000000000000..a1ae0d46f3d44
--- /dev/null
+++ 
b/clang-tools-extra/test/clang-tidy/checkers/bugprone/misplaced-widening-cast-bitfield-explicit-only.cpp
@@ -0,0 +1,60 @@
+// RUN: %check_clang_tidy %s bugprone-misplaced-widening-cast %t -- \
+// RUN:     -config="{CheckOptions: 
{bugprone-misplaced-widening-cast.CheckImplicitCasts: false}}" \
+// RUN:     -- -target x86_64-unknown-unknown
+// RUN: %check_clang_tidy %s bugprone-misplaced-widening-cast %t -- \
+// RUN:     -config="{CheckOptions: 
{bugprone-misplaced-widening-cast.CheckImplicitCasts: false}}" \
+// RUN:     -- -target i386-unknown-unknown
+
+// Tests rely on specific type sizes:
+// unsigned int = 32, unsigned short = 16, unsigned char = 8,
+// unsigned long = 64, unsigned long long = 64 bits.
+
+struct BitfieldHeader {
+  unsigned long long field40 : 40;
+  unsigned long field16 : 16;
+};
+
+// Source (unsigned short, 16-bit) == bit field width (16-bit). No warnings.
+void explicit_cast_same_to_declared(unsigned short size) {
+  struct BitfieldHeader h = {};
+  h.field16 = (unsigned long)(size << 1);
+}
+
+void explicit_cast_same_to_bitfield(unsigned short size) {
+  struct BitfieldHeader h = {};
+  h.field16 = (unsigned short)(size << 1);
+}
+
+void explicit_cast_same_to_narrower(unsigned short size) {
+  struct BitfieldHeader h = {};
+  h.field16 = (unsigned char)(size << 1);
+}
+
+// Source (unsigned int, 32-bit) > bit field width (16-bit). Truncation, no 
warnings.
+void explicit_cast_wider_to_declared(unsigned int size) {
+  struct BitfieldHeader h = {};
+  h.field16 = (unsigned long)(size << 1U);
+}
+
+void explicit_cast_wider_to_bitfield(unsigned int size) {
+  struct BitfieldHeader h = {};
+  h.field16 = (unsigned short)(size << 1U);
+}
+
+void explicit_cast_wider_to_narrower(unsigned int size) {
+  struct BitfieldHeader h = {};
+  h.field16 = (unsigned char)(size << 1U);
+}
+
+// Source (unsigned int, 32-bit) < bit field width (40-bit). Widening — should 
warn.
+void explicit_cast_widen_shift(unsigned int size) {
+  struct BitfieldHeader h = {};
+  h.field40 = (unsigned long long)(size << 1U);
+  // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: either cast from 'unsigned int' 
to 'unsigned long long'
+}
+
+void explicit_cast_widen_multiply(unsigned int size) {
+  struct BitfieldHeader h = {};
+  h.field40 = (unsigned long long)(size * 2U);
+  // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: either cast from 'unsigned int' 
to 'unsigned long long'
+}
diff --git 
a/clang-tools-extra/test/clang-tidy/checkers/bugprone/misplaced-widening-cast-bitfield-implicit-enabled.cpp
 
b/clang-tools-extra/test/clang-tidy/checkers/bugprone/misplaced-widening-cast-bitfield-implicit-enabled.cpp
new file mode 100644
index 0000000000000..e7a90757201ad
--- /dev/null
+++ 
b/clang-tools-extra/test/clang-tidy/checkers/bugprone/misplaced-widening-cast-bitfield-implicit-enabled.cpp
@@ -0,0 +1,235 @@
+// RUN: %check_clang_tidy %s bugprone-misplaced-widening-cast %t -- \
+// RUN:     -config="{CheckOptions: 
{bugprone-misplaced-widening-cast.CheckImplicitCasts: true}}" \
+// RUN:     -- -target x86_64-unknown-unknown
+// RUN: %check_clang_tidy %s bugprone-misplaced-widening-cast %t -- \
+// RUN:     -config="{CheckOptions: 
{bugprone-misplaced-widening-cast.CheckImplicitCasts: true}}" \
+// RUN:     -- -target i386-unknown-unknown
+
+// Tests rely on specific type sizes:
+// unsigned int = 32, unsigned short = 16, unsigned char = 8,
+// unsigned long = 64, unsigned long long = 64 bits.
+
+struct BitfieldHeader {
+  unsigned long long field32 : 32;
+  unsigned long field16 : 16;
+  unsigned int field8 : 8;
+  unsigned long long field40 : 40;
+  unsigned long long field24 : 24;
+  long long sfield32 : 32;
+  long sfield16 : 16;
+};
+
+// 32-bit bit field from unsigned int (32-bit) — no widening.
+void bitfield32_shift(unsigned int size) {
+  struct BitfieldHeader h = {};
+  h.field32 = size << 1U;
+}
+
+void bitfield32_multiply(unsigned int size) {
+  struct BitfieldHeader h = {};
+  h.field32 = size * 2U;
+}
+
+void bitfield32_add(unsigned int size) {
+  struct BitfieldHeader h = {};
+  h.field32 = size + 1U;
+}
+
+void bitfield32_subtract(unsigned int size) {
+  struct BitfieldHeader h = {};
+  h.field32 = size - 1U;
+}
+
+void bitfield32_not(unsigned int size) {
+  struct BitfieldHeader h = {};
+  h.field32 = ~size;
+}
+
+// 16-bit bit field from unsigned short (16-bit) — no widening.
+// Note: integer promotion makes CalcType 'int' (32-bit), but bit field is 
16-bit,
+// so this is truncation, not widening. No warning expected.
+void bitfield16_shift(unsigned short size) {
+  struct BitfieldHeader h = {};
+  h.field16 = size << 1;
+}
+
+void bitfield16_multiply(unsigned short size) {
+  struct BitfieldHeader h = {};
+  h.field16 = size * 2;
+}
+
+void bitfield16_add(unsigned short size) {
+  struct BitfieldHeader h = {};
+  h.field16 = size + 1;
+}
+
+void bitfield16_subtract(unsigned short size) {
+  struct BitfieldHeader h = {};
+  h.field16 = size - 1;
+}
+
+void bitfield16_not(unsigned short size) {
+  struct BitfieldHeader h = {};
+  h.field16 = ~size;
+}
+
+// 8-bit bit field from unsigned char (8-bit) — no widening.
+// Same: integer promotion makes CalcType 'int' (32-bit), bit field is 8-bit = 
truncation.
+void bitfield8_shift(unsigned char size) {
+  struct BitfieldHeader h = {};
+  h.field8 = size << 1;
+}
+
+void bitfield8_multiply(unsigned char size) {
+  struct BitfieldHeader h = {};
+  h.field8 = size * 2;
+}
+
+void bitfield8_add(unsigned char size) {
+  struct BitfieldHeader h = {};
+  h.field8 = size + 1;
+}
+
+void bitfield8_subtract(unsigned char size) {
+  struct BitfieldHeader h = {};
+  h.field8 = size - 1;
+}
+
+void bitfield8_not(unsigned char size) {
+  struct BitfieldHeader h = {};
+  h.field8 = ~size;
+}
+
+// 40-bit bit field from unsigned int (32-bit) — widening DOES occur. Should 
warn.
+void bitfield40_shift(unsigned int size) {
+  struct BitfieldHeader h = {};
+  h.field40 = size << 1U;
+  // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: either cast from 'unsigned int' 
to 'unsigned long long'
+}
+
+void bitfield40_multiply(unsigned int size) {
+  struct BitfieldHeader h = {};
+  h.field40 = size * 2U;
+  // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: either cast from 'unsigned int' 
to 'unsigned long long'
+}
+
+void bitfield40_add(unsigned int size) {
+  struct BitfieldHeader h = {};
+  h.field40 = size + 1U;
+  // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: either cast from 'unsigned int' 
to 'unsigned long long'
+}
+
+void bitfield40_subtract(unsigned int size) {
+  struct BitfieldHeader h = {};
+  h.field40 = size - 1U;
+  // FIXME: checker doesn't detect potential widening for subtraction.
+  // E.g. if size==0, result is 0xFFFFFFFF (32-bit), but in 40-bit space
+  // it should be 0xFFFFFFFFFF. Limitation of getMaxCalculationWidth.
+}
+
+void bitfield40_not(unsigned int size) {
+  struct BitfieldHeader h = {};
+  h.field40 = ~size;
+  // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: either cast from 'unsigned int' 
to 'unsigned long long'
+}
+
+// 24-bit bit field from unsigned short (16-bit) — after integer promotion,
+// CalcType is 'int' (32-bit) which is wider than the 24-bit bit field.
+// This is truncation, not widening. No warning expected.
+void bitfield24_shift(unsigned short size) {
+  struct BitfieldHeader h = {};
+  h.field24 = size << 1;
+}
+
+void bitfield24_multiply(unsigned short size) {
+  struct BitfieldHeader h = {};
+  h.field24 = size * 2;
+}
+
+void bitfield24_add(unsigned short size) {
+  struct BitfieldHeader h = {};
+  h.field24 = size + 1;
+}
+
+void bitfield24_subtract(unsigned short size) {
+  struct BitfieldHeader h = {};
+  h.field24 = size - 1;
+}
+
+void bitfield24_not(unsigned short size) {
+  struct BitfieldHeader h = {};
+  h.field24 = ~size;
+}
+
+// Bit field assigned to a normal (non-bit field) variable.
+// h.field8 has declared type 'unsigned int' (32-bit), so h.field8 << 1 is 
'unsigned int'.
+// Assigning to 'long' (64-bit) is widening.
+void bitfield_to_normal_widen(struct BitfieldHeader h) {
+  long l;
+  l = h.field8 << 1;
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: either cast from 'int' to 'long'
+}
+
+void bitfield_to_normal_no_warn(struct BitfieldHeader h) {
+  unsigned int i;
+  i = h.field8 << 1;
+}
+
+// Bit fields of different sizes assigned to each other.
+void bitfield_small_to_large(struct BitfieldHeader h) {
+  struct BitfieldHeader h2 = {};
+  h2.field40 = h.field8 << 1;
+  // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: either cast from 'int' to 
'unsigned long long'
+}
+
+void bitfield_same_size(struct BitfieldHeader h) {
+  struct BitfieldHeader h2 = {};
+  h2.field32 = h.field32 << 1;
+}
+
+void bitfield_large_to_small(struct BitfieldHeader h) {
+  struct BitfieldHeader h2 = {};
+  h2.field8 = h.field32 << 1;
+}
+
+// Signed bit field tests: no false positive when bit field width == source 
type width.
+// int is 32 bits on x86, sfield32 is 32-bit signed — no widening.
+void sbitfield32_shift(int size) {
+  struct BitfieldHeader h = {};
+  h.sfield32 = size << 1;
+}
+
+void sbitfield32_multiply(int size) {
+  struct BitfieldHeader h = {};
+  h.sfield32 = size * 2;
+}
+
+void sbitfield32_add(int size) {
+  struct BitfieldHeader h = {};
+  h.sfield32 = size + 1;
+}
+
+// short promotes to int (32-bit), sfield16 is 16-bit — truncation, no warning.
+void sbitfield16_shift(short size) {
+  struct BitfieldHeader h = {};
+  h.sfield16 = size << 1;
+}
+
+void sbitfield16_multiply(short size) {
+  struct BitfieldHeader h = {};
+  h.sfield16 = size * 2;
+}
+
+void sbitfield16_add(short size) {
+  struct BitfieldHeader h = {};
+  h.sfield16 = size + 1;
+}
+
+// FIXME: Subtraction with short: short promotes to int (32-bit), assigning to 
long long
+// (64-bit) is widening. Checker doesn't warn for '-' (limitation of 
getMaxCalculationWidth).
+// E.g. if size==0, (short)0 - 1 = -1 (0xFFFFFFFF as int), but in 64-bit
+// it should be 0xFFFFFFFFFFFFFFFF.
+void subtract_short_widen(short size) {
+  long long l;
+  l = size - 1;
+}

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

Reply via email to