diff --git a/change_notes/2026-08-20-fix-fp-rule-7-0-4-non-numeric-operand-types.md b/change_notes/2026-08-20-fix-fp-rule-7-0-4-non-numeric-operand-types.md new file mode 100644 index 0000000000..d8415a8ca3 --- /dev/null +++ b/change_notes/2026-08-20-fix-fp-rule-7-0-4-non-numeric-operand-types.md @@ -0,0 +1,11 @@ +- `RULE-7-0-4` - `InappropriateBitwiseOrShiftOperands.ql`: + - Fixes #1177 - the rule no longer reports operands whose type is not a MISRA numeric type. The + operand checks used `not isUnsignedType(operandType)`, which is vacuously true for every type + that has no MISRA numeric type at all, such as class types and the unresolved dependent types + of uninstantiated template bodies. As a result the rule reported operations that do not use + the built-in operators, most notably the stream insertion and extraction operators. The checks + now use `isSignedType(operandType)` instead. + - Operands of character type, of a non-standard integral type, and of an unscoped enumeration + type without a fixed underlying type are consequently no longer reported, because none of them + has a MISRA numeric type. Unscoped enumerations without a fixed underlying type are covered by + `RULE-10-2-3`. diff --git a/cpp/misra/src/rules/RULE-7-0-4/InappropriateBitwiseOrShiftOperands.ql b/cpp/misra/src/rules/RULE-7-0-4/InappropriateBitwiseOrShiftOperands.ql index f0992d2f0f..344efbc934 100644 --- a/cpp/misra/src/rules/RULE-7-0-4/InappropriateBitwiseOrShiftOperands.ql +++ b/cpp/misra/src/rules/RULE-7-0-4/InappropriateBitwiseOrShiftOperands.ql @@ -63,7 +63,7 @@ where | x = op.getLeftOperand() and operandType = op.getLeftOperand().getExplicitlyConverted().getType() and - not MisraCpp23BuiltInTypes::isUnsignedType(operandType) and + MisraCpp23BuiltInTypes::isSignedType(operandType) and message = "Bitwise operator '" + op.getOperator() + "' requires unsigned numeric operands, but the left operand has type '" + operandType + @@ -71,7 +71,7 @@ where or x = op.getRightOperand() and operandType = op.getRightOperand().getExplicitlyConverted().getType() and - not MisraCpp23BuiltInTypes::isUnsignedType(operandType) and + MisraCpp23BuiltInTypes::isSignedType(operandType) and message = "Bitwise operator '" + op.getOperator() + "' requires unsigned numeric operands, but the right operand has type '" + operandType + @@ -82,7 +82,7 @@ where exists(ComplementExpr comp, Type opType | x = comp.getOperand() and opType = comp.getOperand().getExplicitlyConverted().getType() and - not MisraCpp23BuiltInTypes::isUnsignedType(opType) and + MisraCpp23BuiltInTypes::isSignedType(opType) and message = "Bit complement operator '~' requires unsigned operand, but has type '" + opType + "'." ) @@ -91,7 +91,7 @@ where exists(BinaryShiftOpOrAssignOp shift, Type leftType | x = shift.getLeftOperand() and leftType = shift.getLeftOperand().getExplicitlyConverted().getType() and - not MisraCpp23BuiltInTypes::isUnsignedType(leftType) and + MisraCpp23BuiltInTypes::isSignedType(leftType) and not isSignedConstantLeftShiftException(shift) and message = "Shift operator '" + shift.getOperator() + @@ -112,7 +112,7 @@ where "Shift operator '" + shift.getOperator() + "' shifts by " + right.getValue().toInt() + " which is not within the valid range 0.." + ((leftType.getSize() * 8) - 1) + "." else ( - not MisraCpp23BuiltInTypes::isUnsignedType(rightType) and + MisraCpp23BuiltInTypes::isSignedType(rightType) and message = "Shift operator '" + shift.getOperator() + "' requires unsigned right operand, but has type '" + rightType + "'." diff --git a/cpp/misra/test/rules/RULE-7-0-4/InappropriateBitwiseOrShiftOperands.expected b/cpp/misra/test/rules/RULE-7-0-4/InappropriateBitwiseOrShiftOperands.expected index 3022ea3923..3d7323b3cf 100644 --- a/cpp/misra/test/rules/RULE-7-0-4/InappropriateBitwiseOrShiftOperands.expected +++ b/cpp/misra/test/rules/RULE-7-0-4/InappropriateBitwiseOrShiftOperands.expected @@ -47,3 +47,6 @@ | test.cpp:156:3:156:12 | 1073741824 | Shift operator '<<' requires unsigned left operand, but has type 'int'. | | test.cpp:162:3:162:5 | s32 | Shift operator '<<' requires unsigned left operand, but has type 'int32_t'. | | test.cpp:170:3:170:5 | s32 | Shift operator '>>' requires unsigned left operand, but has type 'int32_t'. | +| test.cpp:201:3:201:7 | value | Shift operator '<<' requires unsigned left operand, but has type 'signed int'. | +| test.cpp:226:3:226:4 | e3 | Bitwise operator '&' requires unsigned numeric operands, but the left operand has type 'UnscopedEnumSignedUnderlyingType'. | +| test.cpp:226:7:226:8 | e3 | Bitwise operator '&' requires unsigned numeric operands, but the right operand has type 'UnscopedEnumSignedUnderlyingType'. | diff --git a/cpp/misra/test/rules/RULE-7-0-4/test.cpp b/cpp/misra/test/rules/RULE-7-0-4/test.cpp index 110c50070e..674e6ca4a3 100644 --- a/cpp/misra/test/rules/RULE-7-0-4/test.cpp +++ b/cpp/misra/test/rules/RULE-7-0-4/test.cpp @@ -168,4 +168,70 @@ void test_right_shift_signed_operands() { u32 >> 1U; // COMPLIANT s32 >> 1U; // NON_COMPLIANT -} \ No newline at end of file +} + +class TestStream { +public: + TestStream &operator<<(std::int32_t value); + TestStream &operator>>(std::int32_t &value); +}; + +void test_overloaded_shift_operators() { + TestStream stream; + std::int32_t s32 = 1; + + // User provided operators, not the built-in shift operators + stream << 1; // COMPLIANT + stream << s32; // COMPLIANT + stream >> s32; // COMPLIANT +} + +template +void test_overloaded_shift_operators_in_template(TestStream &stream, + const T &value) { + // The left operand of the second `<<` is the `TestStream &` returned by the + // first one, and the right operand is dependent, so the operation is + // unresolved in the uninstantiated template body + stream << 1 << value; // COMPLIANT +} + +template void test_dependent_shift_operands(T value) { + // Dependent operands are unresolved in the uninstantiated template body, but + // reported through the instantiation below + value << 2; // NON_COMPLIANT +} + +void test_template_instantiations() { + TestStream stream; + test_overloaded_shift_operators_in_template(stream, 1); + test_dependent_shift_operands(1); +} +enum UnscopedEnumNoFixedUnderlyingType { EnumeratorA = 1, EnumeratorB = 2 }; + +enum UnscopedEnumUnsignedUnderlyingType : unsigned int { EnumeratorC = 1 }; + +enum UnscopedEnumSignedUnderlyingType : int { EnumeratorD = 1 }; + +void test_enum_operands() { + UnscopedEnumNoFixedUnderlyingType e1 = EnumeratorA; + UnscopedEnumUnsignedUnderlyingType e2 = EnumeratorC; + UnscopedEnumSignedUnderlyingType e3 = EnumeratorD; + + // Without a fixed underlying type the enum has no MISRA numeric type, so the + // operands are not analysed by this rule + e1 &e1; // COMPLIANT + + e2 &e2; // COMPLIANT + + e3 &e3; // NON_COMPLIANT +} + +void test_character_type_operands() { + char32_t c32 = 1; + + // `char32_t` is of character type, not of numeric type, and is always + // unsigned + c32 &c32; // COMPLIANT + c32 << 1U; // COMPLIANT + ~c32; // COMPLIANT +}