Index: clang-tools-extra/clang-tidy/cppcoreguidelines/NarrowingConversionsCheck.cpp =================================================================== --- clang-tools-extra/clang-tidy/cppcoreguidelines/NarrowingConversionsCheck.cpp +++ clang-tools-extra/clang-tidy/cppcoreguidelines/NarrowingConversionsCheck.cpp @@ -83,6 +83,28 @@ binaryOperator(hasOperands(IsConversionFromIgnoredType, hasType(isInteger())))); + // Reading a small unsigned bitfield member will be wrapped by an implicit + // cast to 'int' triggering this checker. But this is known to be safe by the + // compiler since it chose 'int' instead of 'unsigned int' as the type of the + // whole expression, so let's ignore this case. + // + // struct SmallBitfield { unsigned int id : 4; }; + // x.id << 1; + // + // BinaryOperator 'int' '<<' + // |-ImplicitCastExpr 'int' + // | `-ImplicitCastExpr 'unsigned int' + // | `-MemberExpr 'unsigned int' lvalue bitfield .id + // | `-DeclRefExpr 'SmallBitfield' lvalue ParmVar 'x' 'SmallBitfield' + // `-IntegerLiteral 'int' 1 + const auto ShiftingWidenedBitfieldValue = castExpr( + hasCastKind(CK_IntegralCast), hasType(asString("int")), + has(castExpr( + castExpr(hasCastKind(CK_LValueToRValue), + has(memberExpr(hasDeclaration(fieldDecl(isBitField()))))))), + hasParent( + binaryOperator(anyOf(hasOperatorName("<<"), hasOperatorName(">>"))))); + // Casts: // i = 0.5; // void f(int); f(0.5); @@ -100,7 +122,8 @@ IgnoreConversionFromTypes.empty() ? castExpr() : castExpr(unless(hasSourceExpression( - IsIgnoredTypeTwoLevelsDeep)))) + IsIgnoredTypeTwoLevelsDeep))), + unless(ShiftingWidenedBitfieldValue)) .bind("cast")), this); Index: clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines-narrowing-conversions-bitfields.cpp =================================================================== --- /dev/null +++ clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines-narrowing-conversions-bitfields.cpp @@ -0,0 +1,107 @@ +// RUN: %check_clang_tidy %s cppcoreguidelines-narrowing-conversions %t \ +// RUN: -std=c++17 -- -target x86_64-unknown-linux + +#define CHAR_BITS 8 +static_assert(sizeof(unsigned int) == 32 / CHAR_BITS); + +template +struct is_same { + static constexpr bool value = false; +}; +template +struct is_same { + static constexpr bool value = true; +}; + +template +static constexpr bool is_same_v = is_same::value; + +struct NoBitfield { + unsigned int id; +}; +struct SmallBitfield { + unsigned int id : 4; +}; +struct BigBitfield { + unsigned int id : 32; +}; + +void test_binary_and(SmallBitfield x) { + // CHECK-MESSAGES: [[@LINE+1]]:36: warning: narrowing conversion from 'unsigned int' to signed type 'int' is implementation-defined [cppcoreguidelines-narrowing-conversions] + static_assert(is_same_v); + static_assert(is_same_v); // no-warning + + // CHECK-MESSAGES: [[@LINE+1]]:3: warning: narrowing conversion from 'unsigned int' to signed type 'int' is implementation-defined [cppcoreguidelines-narrowing-conversions] + x.id & 1; + x.id & 1u; // no-warning + + // CHECK-MESSAGES: [[@LINE+1]]:7: warning: narrowing conversion from 'unsigned int' to signed type 'int' is implementation-defined [cppcoreguidelines-narrowing-conversions] + 1 & x.id; + 1u & x.id; // no-warning +} + +void test_binary_or(SmallBitfield x) { + // CHECK-MESSAGES: [[@LINE+1]]:36: warning: narrowing conversion from 'unsigned int' to signed type 'int' is implementation-defined [cppcoreguidelines-narrowing-conversions] + static_assert(is_same_v); + static_assert(is_same_v); // no-warning + + // CHECK-MESSAGES: [[@LINE+1]]:3: warning: narrowing conversion from 'unsigned int' to signed type 'int' is implementation-defined [cppcoreguidelines-narrowing-conversions] + x.id | 1; + x.id | 1u; // no-warning + + // CHECK-MESSAGES: [[@LINE+1]]:7: warning: narrowing conversion from 'unsigned int' to signed type 'int' is implementation-defined [cppcoreguidelines-narrowing-conversions] + 1 | x.id; + 1u | x.id; // no-warning +} + +void test(NoBitfield x) { + // no-warning in this function + static_assert(is_same_v); + static_assert(is_same_v); + + x.id << 1; + x.id << 1u; + x.id >> 1; + x.id >> 1u; + + 1 << x.id; + 1u << x.id; + 1 >> x.id; + 1u >> x.id; +} + +void test(SmallBitfield x) { + // no-warning in this function: bitfield reads should be ignored within shift operations + + // 'int' is large enough to hold the value of 'x.id', since that could at + // most populate 4 bits and an 'int' has 32. + static_assert(is_same_v); + static_assert(is_same_v); + + x.id << 1; + x.id << 1u; + x.id >> 1; + x.id >> 1u; + + 1 << x.id; + 1u << x.id; + 1 >> x.id; + 1u >> x.id; +} + +void test(BigBitfield x) { + // no-warning in this function: there is no int -> unsigned conversion here + + static_assert(is_same_v); + static_assert(is_same_v); + + x.id << 1; + x.id << 1u; + x.id >> 1; + x.id >> 1u; + + 1 << x.id; + 1u << x.id; + 1 >> x.id; + 1u >> x.id; +}