From a0f0606e2554618f6633476faabfaae36d1ee94b Mon Sep 17 00:00:00 2001 From: Jan Schlosser Date: Fri, 21 Aug 2026 08:39:47 +0200 Subject: [PATCH] Fix RULE-7-0-4 false positives for non-numeric operand types `InappropriateBitwiseOrShiftOperands.ql` reported operations that do not use the built-in bitwise or shift operators, most notably the stream insertion and extraction operators, e.g. `stream << value`. The operand checks used `not MisraCpp23BuiltInTypes::isUnsignedType(type)`. That predicate takes a `NumericType`, so it does not hold for a type that has no MISRA numeric type at all, and negating it makes the check vacuously true for every such type. Class types and the unresolved dependent types of uninstantiated template bodies were therefore reported as non-compliant operands. The checks now use `MisraCpp23BuiltInTypes::isSignedType(type)` instead, which only holds for an operand that has a MISRA numeric type and is signed. This does not introduce false negatives for dependent operands, because the template instantiations, in which the operand types are known, are still reported. 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. Fixes #1177 --- ...fp-rule-7-0-4-non-numeric-operand-types.md | 11 +++ .../InappropriateBitwiseOrShiftOperands.ql | 10 +-- ...appropriateBitwiseOrShiftOperands.expected | 3 + cpp/misra/test/rules/RULE-7-0-4/test.cpp | 68 ++++++++++++++++++- 4 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 change_notes/2026-08-20-fix-fp-rule-7-0-4-non-numeric-operand-types.md 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 +}