Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
1d2aa2e
RULE-7-0-1 - NoConversionFromBool
lcartey Jun 10, 2025
5b810c3
Rule 7.0.1: Address review issues
lcartey Jun 10, 2025
62d5dcc
RULE-7-0-2 - NoImplicitBoolConversion
lcartey Jun 11, 2025
f92a2c9
Rule 7.0.2: Extend test case, support member function pointers
lcartey Jun 11, 2025
b62394a
Add Conversions exclusions file
lcartey Jun 12, 2025
a2c9912
RULE-7-0-6 - NumericAssignmentTypeMismatch
lcartey Jun 17, 2025
229705f
Rule 7.0.6: Update test with better variable names
lcartey Jun 17, 2025
f870023
Rule 7.0.6: Fix id-expression detection
lcartey Jun 17, 2025
0ed1689
Rule 7.0.6: Support reference types
lcartey Jun 17, 2025
778b344
Rule 7.0.6: Ignore compound expressions
lcartey Jun 17, 2025
acfb253
Rule 7.0.6: Additional function return test cases
lcartey Jun 17, 2025
9dbc39f
Rule 7.0.6: Clarify pass-by-value on parameters
lcartey Jun 17, 2025
b310e14
Rule 7.0.6: Add user defined operator tests
lcartey Jun 17, 2025
af2ff95
MISRA C++ 2023: Create StandardConversions library
lcartey Jun 17, 2025
ea7d168
MISRA C++ StandardConversions - improve detection of bitfield types
lcartey Jun 17, 2025
5843258
Rule 7.0.6: Improve bitfield support
lcartey Jun 18, 2025
01b517d
StandardConversions: Improve detection of numeric type category
lcartey Jun 18, 2025
06eddfa
Rule 7.0.6: Add a test case for non-numeric (not covered)
lcartey Jun 18, 2025
a65c4cc
StandardConversions: Handle aggregate initialization
lcartey Jun 18, 2025
04613cd
Add a library for determining constant expressions
lcartey Jun 19, 2025
4be1395
Rule 7.0.6: Use BigInt for constant expressions
lcartey Jun 19, 2025
e87892e
Rule 7.0.6: add tests for cv-qualified types
lcartey Jun 19, 2025
916fb3d
Rule 7.0.6: Support pointer to member cases
lcartey Jun 19, 2025
2ec2814
Rule 7.0.6: Support functions with default parameters
lcartey Jun 19, 2025
e659944
Rule 7.0.6: Ignore deleted overloads
lcartey Jun 19, 2025
c0fe44d
Rule 7.0.6: Refactor overload independent code
lcartey Jun 19, 2025
06ffbfb
Rule 7.0.6: Move aggregate tests to new file
lcartey Jun 19, 2025
fc63db1
Rule 7.0.6: Move operator tests to separate file
lcartey Jun 19, 2025
f68658a
Rule 7.0.6: Support constructor field initializers
lcartey Jun 19, 2025
3fdaa98
Rule 7.0.6: Handle explicit conversions
lcartey Jun 19, 2025
063a5cc
Rule 7.0.6: Improve tests for templates
lcartey Jun 20, 2025
96d5c1b
MISRA C++ 2023: Rename StandardConversions library
lcartey Jun 20, 2025
7c5fb87
Rule 7.0.6: Address performance issues
lcartey Jun 20, 2025
6ddab35
Create Call library
lcartey Jun 20, 2025
983f256
Extend ios stubs for C++
lcartey Jun 25, 2025
bb9f5a1
RULE-7-11-3 - FunctionPointerConversionContext
lcartey Jun 25, 2025
2e75310
C++: Improve char_traits stubs
lcartey Jun 25, 2025
4bf8d51
Improve C++ stubs for locales
lcartey Jun 6, 2025
4d5e35f
Extend C++ stubs for locale
lcartey Jun 6, 2025
f2b5410
C++: Add optional stubs
lcartey Jun 25, 2025
74946cf
Rule 7.0.3: NoCharacterNumericalValue.ql
lcartey Jun 25, 2025
a2d7ee3
RULE-7-0-5 - NoSignednessChangeFromPromotion
lcartey Jun 26, 2025
c25057d
Add sizeOfInt() predicate
lcartey Jun 27, 2025
3a8dab1
Rule 7.0.5: Refactor to enable non-Conversions
lcartey Jun 27, 2025
3cf9eac
Improve detection of integer promotions and usual arithmetic conversions
lcartey Jun 27, 2025
f50baa9
Format test case
lcartey Jun 27, 2025
a7ce6f5
Rule 7.0.5: Expand test cases
lcartey Jun 27, 2025
9aed463
Ruley 7.0.5: Support lvalue conversions on assign operations
lcartey Jun 27, 2025
aac2dc2
Refactor to use NumericType
lcartey Jun 27, 2025
92c7dac
Rule 7.0.5: Add pointer tests (should be ignored)
lcartey Jun 27, 2025
4be88a3
Rule 7.0.5: Add failing test case
lcartey Jun 27, 2025
f1502d6
Rule 7.0.5: Add test cases for enum conversions
lcartey Jul 1, 2025
89485d5
Conversions: Swap some queries around
lcartey Jul 1, 2025
6845cdc
RULE-7-0-4 - InappropriateBitwiseOrShiftOperands
lcartey Jul 4, 2025
db58704
Test large and negative constants, use BigInt
lcartey Jul 4, 2025
3d2616a
Rule 7.0.4: Add support for shift-assignment operators
lcartey Jul 7, 2025
05cfc2b
Rule 7.0.5: Add an implementation_scope
lcartey Jul 7, 2025
dd34127
Merge branch 'main' into lcartey/cpp-conversions
lcartey Aug 18, 2025
1cc458b
Rule 7.0.3: Address review feedback
lcartey Aug 18, 2025
51938af
Rule 7.0.1: Address review feedback
lcartey Aug 18, 2025
9bbef6f
Rule 7.0.2: Address review feedback
lcartey Aug 18, 2025
b20ea88
Add a utility library for unifying binary ops
lcartey Aug 19, 2025
3e2534b
Move isSigned/isUnsigned to BuiltInTypes
lcartey Aug 19, 2025
1af908a
Rule 7.0.4: Improve reporting
lcartey Aug 19, 2025
cd8c960
Rule 7.0.6: Correctly handle constructor exception
lcartey Aug 19, 2025
feccaf7
ConstantExpressions: Correct NotExpr to ComplementExpr
lcartey Aug 19, 2025
2c0d06b
Make CanonicalIntegerType singular, use it more widely
lcartey Aug 20, 2025
21cf410
CanonicalTypes: refactor library
lcartey Aug 20, 2025
bc7bf51
Rule 7.0.5: Limit to `Cast`s and refactor naming
lcartey Aug 20, 2025
f7cd25e
BuiltInTypeRules: Handle bit fields in switch cases
lcartey Aug 20, 2025
2d14c81
Fix CanonicalIntegralType classes
lcartey Aug 20, 2025
9ddf645
BuiltInTypeRules: Rename realType to builtInType
lcartey Aug 21, 2025
8be7b3c
BuiltInTypeRules: Add isSameType API
lcartey Aug 21, 2025
bcf8ae9
BuiltInTypeRules: Rename getRealSize to getBuiltInSize
lcartey Aug 21, 2025
94cfcda
MisraType: Avoid misuse of getSize()
lcartey Aug 21, 2025
2c97905
BuiltInTypes: Wrap in MisraCpp23BuiltInTypes module
lcartey Aug 21, 2025
5ca11e9
Add TypeCategory suffix
lcartey Aug 21, 2025
6110770
Update reference to MisraBuiltInTypes
lcartey Aug 21, 2025
8b999e9
BuiltInTypes: Refactor bitfield handling
lcartey Aug 21, 2025
258dadf
Create MISRA arithmetic conversions library
lcartey Aug 21, 2025
3186c70
Test case formatting
lcartey Aug 21, 2025
23be3db
More test case formatting
lcartey Aug 21, 2025
61ae04f
More formatting
lcartey Aug 21, 2025
668e518
Address compilation issue with FunctionType.
lcartey Aug 21, 2025
3575db1
Rule 7.0.2: Use getUnconverted.
lcartey Aug 21, 2025
0b9e2cb
Rule 7.0.2: Treat nullptr_t as a pointer for this rule
lcartey Aug 21, 2025
8d0bb29
Rule 7.0.1: Expand test case to cover non_compliant cases
lcartey Aug 22, 2025
96b644e
Rule 7.0.2: Add extra cast test cases
lcartey Aug 22, 2025
189f8c0
Update expected results after formatting
lcartey Aug 22, 2025
9f46766
Rule 7.0.3: Split unevaluated operands test case
lcartey Aug 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Rule 7.0.3: NoCharacterNumericalValue.ql
Adds a query that identifies when a numerical value of a
character has been used.

Also fixes a character type category bug, exposed getBuiltinType
and provide a CharacterType class.
  • Loading branch information
lcartey committed Jun 25, 2025
commit 74946cf7f0d46f66606a71d3436c987e73308889
49 changes: 36 additions & 13 deletions cpp/misra/src/codingstandards/cpp/misra/BuiltInTypeRules.qll
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ newtype TypeCategory =
*/
TypeCategory getTypeCategory(BuiltInType t) {
(
t instanceof CharType or
t instanceof PlainCharType or
t instanceof WideCharType or
t instanceof Char16Type or
t instanceof Char32Type or
Expand Down Expand Up @@ -58,13 +58,45 @@ TypeCategory getTypeCategory(BuiltInType t) {
result = Other()
}

/**
* Gets the built-in type of a type, if it is a built-in type.
*
* This function will strip specifiers and typedefs to get the underlying built-in type.
*/
BuiltInType getBuiltInType(Type t) {
// Get the built-in type of a type, if it is a built-in type
result = t
or
// Strip specifiers and typedefs to get the built-in type
result = getBuiltInType(t.getUnspecifiedType())
or
// For reference types, get the base type and then the built-in type
result = getBuiltInType(t.(ReferenceType).getBaseType())
or
// For enum types, get the explicit underlying type and then the built-in type
result = getBuiltInType(t.(Enum).getExplicitUnderlyingType())
}

/**
* The signedness of a MISRA C++ 2023 numeric type.
*/
newtype Signedness =
Signed() or
Unsigned()

class CharacterType extends Type {
// The actual character type, which is either a plain char or a wide char
BuiltInType realType;

CharacterType() {
// A type whose type category is character
getTypeCategory(realType) = Character() and
realType = getBuiltInType(this)
}

Type getRealType() { result = realType }
}

/**
* A MISRA C++ 2023 numeric type is a type that represents a number, either an integral or a floating-point.
*
Expand All @@ -78,18 +110,9 @@ class NumericType extends Type {
Type realType;

NumericType() {
// A type which is either an integral or a floating-point type category
getTypeCategory(this) = [Integral().(TypeCategory), FloatingPoint()] and
realType = this
or
// Any type which, after stripping specifiers and typedefs, is a numeric type
realType = this.getUnspecifiedType().(NumericType).getRealType()
or
// Any reference type where the base type is a numeric type
realType = this.(ReferenceType).getBaseType().(NumericType).getRealType()
or
// Any Enum type with an explicit underlying type that is a numeric type
realType = this.(Enum).getExplicitUnderlyingType().(NumericType).getRealType()
// A type whose type category is either integral or a floating-point
getTypeCategory(realType) = [Integral().(TypeCategory), FloatingPoint()] and
realType = getBuiltInType(this)
}

Signedness getSignedness() {
Expand Down
61 changes: 61 additions & 0 deletions cpp/misra/src/rules/RULE-7-0-3/NoCharacterNumericalValue.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* @id cpp/misra/no-character-numerical-value
* @name RULE-7-0-3: The numerical value of a character shall not be used
* @description Using the numerical value of a character type may lead to inconsistent behavior due
* to encoding dependencies and should be avoided in favor of safer C++ Standard
* Library functions.
* @kind problem
* @precision very-high
* @problem.severity error
* @tags external/misra/id/rule-7-0-3
* scope/single-translation-unit
* external/misra/enforcement/decidable
* external/misra/obligation/required
*/

import cpp
import codingstandards.cpp.misra.BuiltInTypeRules

from Conversion c, Expr expr, Type sourceType, Type targetType
where
expr = c.getExpr() and
sourceType = expr.getType() and
targetType = c.getType() and
(
// Conversion from character type to non-character type
sourceType instanceof CharacterType and
not targetType instanceof CharacterType
or
// Conversion from non-character type to character type
not sourceType instanceof CharacterType and
targetType instanceof CharacterType
// or
// // Conversion between different character types
// getTypeCategory(sourceType) instanceof Character and
// getTypeCategory(targetType) instanceof Character and
// not sourceType = targetType
) and
// Exclude conversions where both operands have the same character type in equality operations
not exists(EqualityOperation eq, CharacterType leftType, CharacterType rightType |
eq.getAnOperand() = expr and
leftType = eq.getLeftOperand().getType() and
rightType = eq.getRightOperand().getType() and
leftType.getRealType() = rightType.getRealType()
) and
// Exclude unevaluated operands
not (
expr.getParent*() instanceof SizeofExprOperator or
expr.getParent*() instanceof SizeofTypeOperator or
expr.getParent*() instanceof TypeidOperator or
expr.getParent*() = any(Decltype dt).getExpr() or
expr.getParent*() instanceof StaticAssert
) and
// Exclude optional comparisons that don't involve conversion
not exists(FunctionCall fc |
fc.getTarget().hasName("operator==") and
fc.getAnArgument() = expr and
fc.getQualifier().getType().hasName("optional")
)
select expr,
"Conversion of character type '" + sourceType.getName() + "' to '" + targetType.getName() +
"' uses numerical value of character."
27 changes: 27 additions & 0 deletions cpp/misra/test/rules/RULE-7-0-3/NoCharacterNumericalValue.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
| test.cpp:17:13:17:14 | 10 | Conversion of character type 'int' to 'char' uses numerical value of character. |
| test.cpp:18:13:18:14 | 65 | Conversion of character type 'int' to 'char' uses numerical value of character. |
| test.cpp:19:13:19:13 | 0 | Conversion of character type 'int' to 'char' uses numerical value of character. |
| test.cpp:24:20:24:22 | 97 | Conversion of character type 'char' to 'int8_t' uses numerical value of character. |
| test.cpp:25:21:25:24 | 13 | Conversion of character type 'char' to 'uint8_t' uses numerical value of character. |
| test.cpp:26:12:26:14 | 98 | Conversion of character type 'char' to 'int' uses numerical value of character. |
| test.cpp:43:13:43:14 | l1 | Conversion of character type 'char' to 'int' uses numerical value of character. |
| test.cpp:43:13:43:20 | ... - ... | Conversion of character type 'int' to 'char' uses numerical value of character. |
| test.cpp:43:18:43:20 | 48 | Conversion of character type 'char' to 'int' uses numerical value of character. |
| test.cpp:44:13:44:14 | l1 | Conversion of character type 'char' to 'int' uses numerical value of character. |
| test.cpp:44:13:44:18 | ... + ... | Conversion of character type 'int' to 'char' uses numerical value of character. |
| test.cpp:45:13:45:14 | l1 | Conversion of character type 'char' to 'int' uses numerical value of character. |
| test.cpp:45:13:45:18 | ... * ... | Conversion of character type 'int' to 'char' uses numerical value of character. |
| test.cpp:51:7:51:8 | l1 | Conversion of character type 'char' to 'bool' uses numerical value of character. |
| test.cpp:51:13:51:14 | l2 | Conversion of character type 'char' to 'bool' uses numerical value of character. |
| test.cpp:53:7:53:8 | l1 | Conversion of character type 'char' to 'bool' uses numerical value of character. |
| test.cpp:55:8:55:9 | l2 | Conversion of character type 'char' to 'bool' uses numerical value of character. |
| test.cpp:73:39:73:41 | 97 | Conversion of character type 'char' to 'int_type' uses numerical value of character. |
| test.cpp:89:8:89:9 | l1 | Conversion of character type 'char' to 'int' uses numerical value of character. |
| test.cpp:89:14:89:16 | 48 | Conversion of character type 'char' to 'int' uses numerical value of character. |
| test.cpp:89:23:89:24 | l1 | Conversion of character type 'char' to 'int' uses numerical value of character. |
| test.cpp:89:29:89:31 | 57 | Conversion of character type 'char' to 'int' uses numerical value of character. |
| test.cpp:102:25:102:26 | l1 | Conversion of character type 'char' to 'int' uses numerical value of character. |
| test.cpp:117:15:117:16 | l2 | Conversion of character type 'int_type' to 'char' uses numerical value of character. |
| test.cpp:123:31:123:32 | 65 | Conversion of character type 'int' to 'char' uses numerical value of character. |
| test.cpp:124:29:124:31 | 65 | Conversion of character type 'char' to 'int' uses numerical value of character. |
| test.cpp:130:6:130:7 | l2 | Conversion of character type 'char' to 'int' uses numerical value of character. |
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
rules/RULE-7-0-3/NoCharacterNumericalValue.ql
131 changes: 131 additions & 0 deletions cpp/misra/test/rules/RULE-7-0-3/test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
#include <cassert>
#include <cctype>
#include <cstdint>
#include <iostream>
#include <locale>
#include <optional>
#include <string>

void test_character_literal_assignment() {
char l1 = 'a'; // COMPLIANT
char l2 = '\r'; // COMPLIANT
char l3 = '\n'; // COMPLIANT
char l4 = '\0'; // COMPLIANT
}

void test_implicit_conversion_from_int() {
char l1 = 10; // NON_COMPLIANT
char l2 = 65; // NON_COMPLIANT
char l3 = 0; // NON_COMPLIANT
}

void test_implicit_conversion_to_int() {
char l1 = 'a';
std::int8_t l2 = 'a'; // NON_COMPLIANT
std::uint8_t l3 = '\r'; // NON_COMPLIANT
int l4 = 'b'; // NON_COMPLIANT
}

void test_signed_char_assignment() {
signed char l1 = 11; // COMPLIANT
signed char l2 = 65; // COMPLIANT
}

void test_conversion_between_character_types() {
char l1 = L'A'; // COMPLIANT
wchar_t l2 = 'B'; // COMPLIANT
char16_t l3 = 'C'; // COMPLIANT
char32_t l4 = 'D'; // COMPLIANT
}

void test_arithmetic_operations() {
char l1 = 'a';
char l2 = l1 - '0'; // NON_COMPLIANT
char l3 = l1 + 1; // NON_COMPLIANT
char l4 = l1 * 2; // NON_COMPLIANT
}

void test_boolean_conversion() {
char l1 = 'a';
char l2 = '\0';
if (l1 && l2) { // NON_COMPLIANT
}
if (l1) { // NON_COMPLIANT
}
if (!l2) { // NON_COMPLIANT
}
}

void test_same_type_comparison() {
char l1 = 'a';
if (l1 != 'q') { // COMPLIANT
}
if (l1 == 'b') { // COMPLIANT
}
}

void test_char_traits_usage() {
using CT = std::char_traits<char>;
char l1 = 'a';
if (CT::eq(l1, 'q')) { // COMPLIANT
}
auto l2 = CT::to_int_type('a'); // COMPLIANT
auto l3 = static_cast<CT::int_type>('a'); // NON_COMPLIANT
}

void test_optional_comparison() {
std::optional<char> l1;
if (l1 == 'r') { // COMPLIANT
}
}

void test_unevaluated_operand() {
decltype('s' + 't') l1; // COMPLIANT
static_assert(sizeof('x') > 0); // COMPLIANT
}

void test_range_check_non_compliant() {
char l1 = 'a';
if ((l1 >= '0') && (l1 <= '9')) { // NON_COMPLIANT
}
}

void test_range_check_compliant() {
using CT = std::char_traits<char>;
char l1 = 'a';
if (!CT::lt(l1, '0') && !CT::lt('9', l1)) { // COMPLIANT
}
}

void test_isdigit_non_compliant() {
char l1 = 'a';
if (0 == std::isdigit(l1)) { // NON_COMPLIANT
}
}

void test_isdigit_compliant() {
char l1 = 'a';
if (std::isdigit(l1, std::locale{})) { // COMPLIANT
}
}

void test_stream_conversion() {
std::istream &l1 = std::cin;
auto l2 = l1.get();
using CT = std::char_traits<char>;
if (CT::not_eof(l2)) {
char l3 = l2; // NON_COMPLIANT
char l4 = CT::to_char_type(l2); // COMPLIANT
}
}

void test_explicit_cast() {
char l1 = static_cast<char>(65); // NON_COMPLIANT
int l2 = static_cast<int>('A'); // NON_COMPLIANT
}

void test_function_parameter_conversion() {
auto f1 = [](int l1) {};
char l2 = 'x';
f1(l2); // NON_COMPLIANT
}