https://github.com/earnol updated https://github.com/llvm/llvm-project/pull/197554
>From b98cd84e154a284805ec4ba17b5866f9d0848afb 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
