Skip to content

Commit 74946cf

Browse files
committed
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.
1 parent f2b5410 commit 74946cf

File tree

5 files changed

+256
-13
lines changed

5 files changed

+256
-13
lines changed

cpp/misra/src/codingstandards/cpp/misra/BuiltInTypeRules.qll

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ newtype TypeCategory =
2323
*/
2424
TypeCategory getTypeCategory(BuiltInType t) {
2525
(
26-
t instanceof CharType or
26+
t instanceof PlainCharType or
2727
t instanceof WideCharType or
2828
t instanceof Char16Type or
2929
t instanceof Char32Type or
@@ -58,13 +58,45 @@ TypeCategory getTypeCategory(BuiltInType t) {
5858
result = Other()
5959
}
6060

61+
/**
62+
* Gets the built-in type of a type, if it is a built-in type.
63+
*
64+
* This function will strip specifiers and typedefs to get the underlying built-in type.
65+
*/
66+
BuiltInType getBuiltInType(Type t) {
67+
// Get the built-in type of a type, if it is a built-in type
68+
result = t
69+
or
70+
// Strip specifiers and typedefs to get the built-in type
71+
result = getBuiltInType(t.getUnspecifiedType())
72+
or
73+
// For reference types, get the base type and then the built-in type
74+
result = getBuiltInType(t.(ReferenceType).getBaseType())
75+
or
76+
// For enum types, get the explicit underlying type and then the built-in type
77+
result = getBuiltInType(t.(Enum).getExplicitUnderlyingType())
78+
}
79+
6180
/**
6281
* The signedness of a MISRA C++ 2023 numeric type.
6382
*/
6483
newtype Signedness =
6584
Signed() or
6685
Unsigned()
6786

87+
class CharacterType extends Type {
88+
// The actual character type, which is either a plain char or a wide char
89+
BuiltInType realType;
90+
91+
CharacterType() {
92+
// A type whose type category is character
93+
getTypeCategory(realType) = Character() and
94+
realType = getBuiltInType(this)
95+
}
96+
97+
Type getRealType() { result = realType }
98+
}
99+
68100
/**
69101
* A MISRA C++ 2023 numeric type is a type that represents a number, either an integral or a floating-point.
70102
*
@@ -78,18 +110,9 @@ class NumericType extends Type {
78110
Type realType;
79111

80112
NumericType() {
81-
// A type which is either an integral or a floating-point type category
82-
getTypeCategory(this) = [Integral().(TypeCategory), FloatingPoint()] and
83-
realType = this
84-
or
85-
// Any type which, after stripping specifiers and typedefs, is a numeric type
86-
realType = this.getUnspecifiedType().(NumericType).getRealType()
87-
or
88-
// Any reference type where the base type is a numeric type
89-
realType = this.(ReferenceType).getBaseType().(NumericType).getRealType()
90-
or
91-
// Any Enum type with an explicit underlying type that is a numeric type
92-
realType = this.(Enum).getExplicitUnderlyingType().(NumericType).getRealType()
113+
// A type whose type category is either integral or a floating-point
114+
getTypeCategory(realType) = [Integral().(TypeCategory), FloatingPoint()] and
115+
realType = getBuiltInType(this)
93116
}
94117

95118
Signedness getSignedness() {
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* @id cpp/misra/no-character-numerical-value
3+
* @name RULE-7-0-3: The numerical value of a character shall not be used
4+
* @description Using the numerical value of a character type may lead to inconsistent behavior due
5+
* to encoding dependencies and should be avoided in favor of safer C++ Standard
6+
* Library functions.
7+
* @kind problem
8+
* @precision very-high
9+
* @problem.severity error
10+
* @tags external/misra/id/rule-7-0-3
11+
* scope/single-translation-unit
12+
* external/misra/enforcement/decidable
13+
* external/misra/obligation/required
14+
*/
15+
16+
import cpp
17+
import codingstandards.cpp.misra.BuiltInTypeRules
18+
19+
from Conversion c, Expr expr, Type sourceType, Type targetType
20+
where
21+
expr = c.getExpr() and
22+
sourceType = expr.getType() and
23+
targetType = c.getType() and
24+
(
25+
// Conversion from character type to non-character type
26+
sourceType instanceof CharacterType and
27+
not targetType instanceof CharacterType
28+
or
29+
// Conversion from non-character type to character type
30+
not sourceType instanceof CharacterType and
31+
targetType instanceof CharacterType
32+
// or
33+
// // Conversion between different character types
34+
// getTypeCategory(sourceType) instanceof Character and
35+
// getTypeCategory(targetType) instanceof Character and
36+
// not sourceType = targetType
37+
) and
38+
// Exclude conversions where both operands have the same character type in equality operations
39+
not exists(EqualityOperation eq, CharacterType leftType, CharacterType rightType |
40+
eq.getAnOperand() = expr and
41+
leftType = eq.getLeftOperand().getType() and
42+
rightType = eq.getRightOperand().getType() and
43+
leftType.getRealType() = rightType.getRealType()
44+
) and
45+
// Exclude unevaluated operands
46+
not (
47+
expr.getParent*() instanceof SizeofExprOperator or
48+
expr.getParent*() instanceof SizeofTypeOperator or
49+
expr.getParent*() instanceof TypeidOperator or
50+
expr.getParent*() = any(Decltype dt).getExpr() or
51+
expr.getParent*() instanceof StaticAssert
52+
) and
53+
// Exclude optional comparisons that don't involve conversion
54+
not exists(FunctionCall fc |
55+
fc.getTarget().hasName("operator==") and
56+
fc.getAnArgument() = expr and
57+
fc.getQualifier().getType().hasName("optional")
58+
)
59+
select expr,
60+
"Conversion of character type '" + sourceType.getName() + "' to '" + targetType.getName() +
61+
"' uses numerical value of character."
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
| test.cpp:17:13:17:14 | 10 | Conversion of character type 'int' to 'char' uses numerical value of character. |
2+
| test.cpp:18:13:18:14 | 65 | Conversion of character type 'int' to 'char' uses numerical value of character. |
3+
| test.cpp:19:13:19:13 | 0 | Conversion of character type 'int' to 'char' uses numerical value of character. |
4+
| test.cpp:24:20:24:22 | 97 | Conversion of character type 'char' to 'int8_t' uses numerical value of character. |
5+
| test.cpp:25:21:25:24 | 13 | Conversion of character type 'char' to 'uint8_t' uses numerical value of character. |
6+
| test.cpp:26:12:26:14 | 98 | Conversion of character type 'char' to 'int' uses numerical value of character. |
7+
| test.cpp:43:13:43:14 | l1 | Conversion of character type 'char' to 'int' uses numerical value of character. |
8+
| test.cpp:43:13:43:20 | ... - ... | Conversion of character type 'int' to 'char' uses numerical value of character. |
9+
| test.cpp:43:18:43:20 | 48 | Conversion of character type 'char' to 'int' uses numerical value of character. |
10+
| test.cpp:44:13:44:14 | l1 | Conversion of character type 'char' to 'int' uses numerical value of character. |
11+
| test.cpp:44:13:44:18 | ... + ... | Conversion of character type 'int' to 'char' uses numerical value of character. |
12+
| test.cpp:45:13:45:14 | l1 | Conversion of character type 'char' to 'int' uses numerical value of character. |
13+
| test.cpp:45:13:45:18 | ... * ... | Conversion of character type 'int' to 'char' uses numerical value of character. |
14+
| test.cpp:51:7:51:8 | l1 | Conversion of character type 'char' to 'bool' uses numerical value of character. |
15+
| test.cpp:51:13:51:14 | l2 | Conversion of character type 'char' to 'bool' uses numerical value of character. |
16+
| test.cpp:53:7:53:8 | l1 | Conversion of character type 'char' to 'bool' uses numerical value of character. |
17+
| test.cpp:55:8:55:9 | l2 | Conversion of character type 'char' to 'bool' uses numerical value of character. |
18+
| test.cpp:73:39:73:41 | 97 | Conversion of character type 'char' to 'int_type' uses numerical value of character. |
19+
| test.cpp:89:8:89:9 | l1 | Conversion of character type 'char' to 'int' uses numerical value of character. |
20+
| test.cpp:89:14:89:16 | 48 | Conversion of character type 'char' to 'int' uses numerical value of character. |
21+
| test.cpp:89:23:89:24 | l1 | Conversion of character type 'char' to 'int' uses numerical value of character. |
22+
| test.cpp:89:29:89:31 | 57 | Conversion of character type 'char' to 'int' uses numerical value of character. |
23+
| test.cpp:102:25:102:26 | l1 | Conversion of character type 'char' to 'int' uses numerical value of character. |
24+
| test.cpp:117:15:117:16 | l2 | Conversion of character type 'int_type' to 'char' uses numerical value of character. |
25+
| test.cpp:123:31:123:32 | 65 | Conversion of character type 'int' to 'char' uses numerical value of character. |
26+
| test.cpp:124:29:124:31 | 65 | Conversion of character type 'char' to 'int' uses numerical value of character. |
27+
| test.cpp:130:6:130:7 | l2 | Conversion of character type 'char' to 'int' uses numerical value of character. |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
rules/RULE-7-0-3/NoCharacterNumericalValue.ql
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
#include <cassert>
2+
#include <cctype>
3+
#include <cstdint>
4+
#include <iostream>
5+
#include <locale>
6+
#include <optional>
7+
#include <string>
8+
9+
void test_character_literal_assignment() {
10+
char l1 = 'a'; // COMPLIANT
11+
char l2 = '\r'; // COMPLIANT
12+
char l3 = '\n'; // COMPLIANT
13+
char l4 = '\0'; // COMPLIANT
14+
}
15+
16+
void test_implicit_conversion_from_int() {
17+
char l1 = 10; // NON_COMPLIANT
18+
char l2 = 65; // NON_COMPLIANT
19+
char l3 = 0; // NON_COMPLIANT
20+
}
21+
22+
void test_implicit_conversion_to_int() {
23+
char l1 = 'a';
24+
std::int8_t l2 = 'a'; // NON_COMPLIANT
25+
std::uint8_t l3 = '\r'; // NON_COMPLIANT
26+
int l4 = 'b'; // NON_COMPLIANT
27+
}
28+
29+
void test_signed_char_assignment() {
30+
signed char l1 = 11; // COMPLIANT
31+
signed char l2 = 65; // COMPLIANT
32+
}
33+
34+
void test_conversion_between_character_types() {
35+
char l1 = L'A'; // COMPLIANT
36+
wchar_t l2 = 'B'; // COMPLIANT
37+
char16_t l3 = 'C'; // COMPLIANT
38+
char32_t l4 = 'D'; // COMPLIANT
39+
}
40+
41+
void test_arithmetic_operations() {
42+
char l1 = 'a';
43+
char l2 = l1 - '0'; // NON_COMPLIANT
44+
char l3 = l1 + 1; // NON_COMPLIANT
45+
char l4 = l1 * 2; // NON_COMPLIANT
46+
}
47+
48+
void test_boolean_conversion() {
49+
char l1 = 'a';
50+
char l2 = '\0';
51+
if (l1 && l2) { // NON_COMPLIANT
52+
}
53+
if (l1) { // NON_COMPLIANT
54+
}
55+
if (!l2) { // NON_COMPLIANT
56+
}
57+
}
58+
59+
void test_same_type_comparison() {
60+
char l1 = 'a';
61+
if (l1 != 'q') { // COMPLIANT
62+
}
63+
if (l1 == 'b') { // COMPLIANT
64+
}
65+
}
66+
67+
void test_char_traits_usage() {
68+
using CT = std::char_traits<char>;
69+
char l1 = 'a';
70+
if (CT::eq(l1, 'q')) { // COMPLIANT
71+
}
72+
auto l2 = CT::to_int_type('a'); // COMPLIANT
73+
auto l3 = static_cast<CT::int_type>('a'); // NON_COMPLIANT
74+
}
75+
76+
void test_optional_comparison() {
77+
std::optional<char> l1;
78+
if (l1 == 'r') { // COMPLIANT
79+
}
80+
}
81+
82+
void test_unevaluated_operand() {
83+
decltype('s' + 't') l1; // COMPLIANT
84+
static_assert(sizeof('x') > 0); // COMPLIANT
85+
}
86+
87+
void test_range_check_non_compliant() {
88+
char l1 = 'a';
89+
if ((l1 >= '0') && (l1 <= '9')) { // NON_COMPLIANT
90+
}
91+
}
92+
93+
void test_range_check_compliant() {
94+
using CT = std::char_traits<char>;
95+
char l1 = 'a';
96+
if (!CT::lt(l1, '0') && !CT::lt('9', l1)) { // COMPLIANT
97+
}
98+
}
99+
100+
void test_isdigit_non_compliant() {
101+
char l1 = 'a';
102+
if (0 == std::isdigit(l1)) { // NON_COMPLIANT
103+
}
104+
}
105+
106+
void test_isdigit_compliant() {
107+
char l1 = 'a';
108+
if (std::isdigit(l1, std::locale{})) { // COMPLIANT
109+
}
110+
}
111+
112+
void test_stream_conversion() {
113+
std::istream &l1 = std::cin;
114+
auto l2 = l1.get();
115+
using CT = std::char_traits<char>;
116+
if (CT::not_eof(l2)) {
117+
char l3 = l2; // NON_COMPLIANT
118+
char l4 = CT::to_char_type(l2); // COMPLIANT
119+
}
120+
}
121+
122+
void test_explicit_cast() {
123+
char l1 = static_cast<char>(65); // NON_COMPLIANT
124+
int l2 = static_cast<int>('A'); // NON_COMPLIANT
125+
}
126+
127+
void test_function_parameter_conversion() {
128+
auto f1 = [](int l1) {};
129+
char l2 = 'x';
130+
f1(l2); // NON_COMPLIANT
131+
}

0 commit comments

Comments
 (0)