Skip to content

Commit a2d7ee3

Browse files
committed
RULE-7-0-5 - NoSignednessChangeFromPromotion
Detects integral promotions and arithmetic conversions that change the signedness or type category of operands, preventing unexpected behavior from implicit type conversions. [a]
1 parent 74946cf commit a2d7ee3

File tree

4 files changed

+282
-0
lines changed

4 files changed

+282
-0
lines changed
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/**
2+
* @id cpp/misra/no-signedness-change-from-promotion
3+
* @name RULE-7-0-5: Integral promotion and the usual arithmetic conversions shall not change the signedness or the type
4+
* @description Integral promotion and usual arithmetic conversions that change operand signedness
5+
* or type category may cause unexpected behavior or undefined behavior when operations
6+
* overflow.
7+
* @kind problem
8+
* @precision very-high
9+
* @problem.severity error
10+
* @tags external/misra/id/rule-7-0-5
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
18+
import codingstandards.cpp.misra.BuiltInTypeRules
19+
20+
/**
21+
* A `Conversion` that is relevant for the rule.
22+
*/
23+
abstract class RelevantConversion extends Conversion {
24+
NumericType fromType;
25+
NumericType toType;
26+
27+
RelevantConversion() {
28+
fromType = this.getExpr().getType().getUnspecifiedType() and
29+
toType = this.getType().getUnspecifiedType() and
30+
this.isImplicit()
31+
}
32+
33+
Type getFromType() { result = fromType }
34+
35+
Type getToType() { result = toType }
36+
}
37+
38+
class UsualArithmeticConversion extends RelevantConversion {
39+
UsualArithmeticConversion() {
40+
(
41+
exists(BinaryOperation op | op.getAnOperand().getFullyConverted() = this) or
42+
exists(UnaryOperation uao | uao.getOperand().getFullyConverted() = this) or
43+
exists(AssignArithmeticOperation ao | ao.getAnOperand().getFullyConverted() = this)
44+
)
45+
}
46+
}
47+
48+
class IntegerPromotion extends RelevantConversion {
49+
IntegerPromotion() {
50+
// Only consider cases where the integer promotion is the last conversion applied
51+
exists(Expr e | e.getFullyConverted() = this) and
52+
// Integer promotion occurs where the from type is smaller than int
53+
fromType.getRealSize() < any(IntType i).(NumericType).getRealSize() and
54+
// To type is bigger than or equal to int
55+
toType.getRealSize() >= any(IntType i).(NumericType).getRealSize() and
56+
fromType.getTypeCategory() = Integral() and
57+
toType.getTypeCategory() = Integral()
58+
}
59+
}
60+
61+
from Expr e, RelevantConversion c, NumericType fromType, NumericType toType, string changeType
62+
where
63+
not isExcluded(e, ConversionsPackage::noSignednessChangeFromPromotionQuery()) and
64+
c = e.getConversion() and
65+
fromType = c.getFromType() and
66+
toType = c.getToType() and
67+
(
68+
fromType.getSignedness() != toType.getSignedness() and changeType = "signedness"
69+
or
70+
fromType.getTypeCategory() != toType.getTypeCategory() and changeType = "type category"
71+
) and
72+
// Ignore crement operations
73+
not exists(CrementOperation cop | cop.getAnOperand() = e) and
74+
// Exception 1: allow safe constant conversions
75+
not (
76+
e.getValue().toInt() >= 0 and
77+
fromType.(IntegralType).isSigned() and
78+
toType.(IntegralType).isUnsigned()
79+
) and
80+
// Exception 2: allow safe conversions from integral to floating-point types
81+
not (
82+
e.isConstant() and
83+
fromType.getTypeCategory() = Integral() and
84+
toType.getTypeCategory() = FloatingPoint()
85+
)
86+
select e,
87+
"Conversion from '" + fromType.getName() + "' to '" + toType.getName() + "' changes " + changeType
88+
+ "."
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
| test.cpp:24:5:24:6 | l1 | Conversion from 'unsigned char' to 'int' changes signedness. |
2+
| test.cpp:24:10:24:11 | l2 | Conversion from 'unsigned char' to 'int' changes signedness. |
3+
| test.cpp:25:5:25:6 | l1 | Conversion from 'unsigned char' to 'int' changes signedness. |
4+
| test.cpp:25:10:25:11 | l2 | Conversion from 'unsigned char' to 'int' changes signedness. |
5+
| test.cpp:26:5:26:6 | l1 | Conversion from 'unsigned char' to 'int' changes signedness. |
6+
| test.cpp:26:10:26:11 | l2 | Conversion from 'unsigned char' to 'int' changes signedness. |
7+
| test.cpp:27:5:27:6 | l1 | Conversion from 'unsigned char' to 'int' changes signedness. |
8+
| test.cpp:27:10:27:11 | l2 | Conversion from 'unsigned char' to 'int' changes signedness. |
9+
| test.cpp:28:5:28:6 | l1 | Conversion from 'unsigned char' to 'int' changes signedness. |
10+
| test.cpp:28:10:28:11 | l2 | Conversion from 'unsigned char' to 'int' changes signedness. |
11+
| test.cpp:29:5:29:6 | l1 | Conversion from 'unsigned char' to 'int' changes signedness. |
12+
| test.cpp:29:10:29:11 | l2 | Conversion from 'unsigned char' to 'int' changes signedness. |
13+
| test.cpp:30:5:30:6 | l1 | Conversion from 'unsigned char' to 'int' changes signedness. |
14+
| test.cpp:30:10:30:11 | l2 | Conversion from 'unsigned char' to 'int' changes signedness. |
15+
| test.cpp:31:5:31:6 | l1 | Conversion from 'unsigned char' to 'int' changes signedness. |
16+
| test.cpp:31:10:31:11 | l2 | Conversion from 'unsigned char' to 'int' changes signedness. |
17+
| test.cpp:45:11:45:12 | l2 | Conversion from 'unsigned char' to 'int' changes signedness. |
18+
| test.cpp:46:11:46:12 | l2 | Conversion from 'unsigned char' to 'int' changes signedness. |
19+
| test.cpp:47:11:47:12 | l2 | Conversion from 'unsigned char' to 'int' changes signedness. |
20+
| test.cpp:48:11:48:12 | l2 | Conversion from 'unsigned char' to 'int' changes signedness. |
21+
| test.cpp:49:11:49:12 | l2 | Conversion from 'unsigned char' to 'int' changes signedness. |
22+
| test.cpp:50:11:50:12 | l2 | Conversion from 'unsigned char' to 'int' changes signedness. |
23+
| test.cpp:51:11:51:12 | l2 | Conversion from 'unsigned char' to 'int' changes signedness. |
24+
| test.cpp:52:11:52:12 | l2 | Conversion from 'unsigned char' to 'int' changes signedness. |
25+
| test.cpp:64:5:64:6 | l1 | Conversion from 'signed int' to 'unsigned int' changes signedness. |
26+
| test.cpp:65:5:65:6 | l1 | Conversion from 'signed int' to 'unsigned int' changes signedness. |
27+
| test.cpp:66:5:66:6 | l1 | Conversion from 'signed int' to 'unsigned int' changes signedness. |
28+
| test.cpp:67:5:67:6 | l1 | Conversion from 'signed int' to 'unsigned int' changes signedness. |
29+
| test.cpp:68:5:68:6 | l1 | Conversion from 'signed int' to 'unsigned int' changes signedness. |
30+
| test.cpp:69:5:69:6 | l1 | Conversion from 'signed int' to 'unsigned int' changes signedness. |
31+
| test.cpp:71:5:71:6 | l3 | Conversion from 'unsigned char' to 'int' changes signedness. |
32+
| test.cpp:71:10:71:11 | l4 | Conversion from 'unsigned short' to 'int' changes signedness. |
33+
| test.cpp:72:5:72:6 | l3 | Conversion from 'unsigned char' to 'int' changes signedness. |
34+
| test.cpp:72:10:72:11 | l4 | Conversion from 'unsigned short' to 'int' changes signedness. |
35+
| test.cpp:82:10:82:11 | l2 | Conversion from 'unsigned char' to 'int' changes signedness. |
36+
| test.cpp:82:15:82:16 | l4 | Conversion from 'unsigned short' to 'int' changes signedness. |
37+
| test.cpp:89:5:89:6 | l1 | Conversion from 'unsigned char' to 'int' changes signedness. |
38+
| test.cpp:90:5:90:6 | l1 | Conversion from 'unsigned char' to 'int' changes signedness. |
39+
| test.cpp:100:6:100:7 | l1 | Conversion from 'unsigned char' to 'int' changes signedness. |
40+
| test.cpp:102:6:102:7 | l1 | Conversion from 'unsigned char' to 'int' changes signedness. |
41+
| test.cpp:104:6:104:7 | l1 | Conversion from 'unsigned char' to 'int' changes signedness. |
42+
| test.cpp:143:11:143:12 | l2 | Conversion from 'unsigned int' to 'float' changes signedness. |
43+
| test.cpp:143:11:143:12 | l2 | Conversion from 'unsigned int' to 'float' changes type category. |
44+
| test.cpp:144:11:144:12 | l2 | Conversion from 'unsigned int' to 'float' changes signedness. |
45+
| test.cpp:144:11:144:12 | l2 | Conversion from 'unsigned int' to 'float' changes type category. |
46+
| test.cpp:145:11:145:12 | l2 | Conversion from 'unsigned int' to 'float' changes signedness. |
47+
| test.cpp:145:11:145:12 | l2 | Conversion from 'unsigned int' to 'float' changes type category. |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
rules/RULE-7-0-5/NoSignednessChangeFromPromotion.ql
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
#include <cstdint>
2+
3+
// Global variables for testing
4+
std::uint8_t g1 = 5;
5+
std::uint8_t g2 = 10;
6+
std::uint16_t g3 = 100;
7+
std::uint32_t g4 = 1000;
8+
std::int8_t g5 = -5;
9+
std::int32_t g6 = -1000;
10+
float g7 = 3.14f;
11+
12+
constexpr std::int32_t f1(std::int32_t i) {
13+
return i * i;
14+
}
15+
16+
void test_binary_arithmetic_operations() {
17+
std::uint8_t l1 = 5;
18+
std::uint8_t l2 = 10;
19+
std::uint16_t l3 = 100;
20+
std::uint32_t l4 = 1000;
21+
std::int8_t l5 = -5;
22+
std::int32_t l6 = -1000;
23+
24+
l1 + l2; // NON_COMPLIANT - u8 + u8 -> signed int
25+
l1 * l2; // NON_COMPLIANT - u8 * u8 -> signed int
26+
l1 - l2; // NON_COMPLIANT - u8 - u8 -> signed int
27+
l1 / l2; // NON_COMPLIANT - u8 / u8 -> signed int
28+
l1 % l2; // NON_COMPLIANT - u8 % u8 -> signed int
29+
l1 & l2; // NON_COMPLIANT - u8 & u8 -> signed int
30+
l1 | l2; // NON_COMPLIANT - u8 | u8 -> signed int
31+
l1 ^ l2; // NON_COMPLIANT - u8 ^ u8 -> signed int
32+
33+
static_cast<std::uint32_t>(l1) + l2; // COMPLIANT - l2 -> unsigned int
34+
l1 + static_cast<std::uint32_t>(l2); // COMPLIANT - l1 -> unsigned int
35+
36+
l6 * l5; // COMPLIANT - l5 -> signed int
37+
l4 / l1; // COMPLIANT - l1 -> unsigned int
38+
}
39+
40+
void test_assignment_operations() {
41+
std::uint8_t l1 = 5;
42+
std::uint8_t l2 = 10;
43+
std::uint32_t l3 = 1000;
44+
45+
l1 += l2; // NON_COMPLIANT - same as l1 + l2
46+
l1 -= l2; // NON_COMPLIANT - same as l1 - l2
47+
l1 *= l2; // NON_COMPLIANT - same as l1 * l2
48+
l1 /= l2; // NON_COMPLIANT - same as l1 / l2
49+
l1 %= l2; // NON_COMPLIANT - same as l1 % l2
50+
l1 &= l2; // NON_COMPLIANT - same as l1 & l2
51+
l1 |= l2; // NON_COMPLIANT - same as l1 | l2
52+
l1 ^= l2; // NON_COMPLIANT - same as l1 ^ l2
53+
54+
l1 += static_cast<std::uint32_t>(l2); // COMPLIANT - l1 -> unsigned int
55+
l1 += l3; // COMPLIANT - l1 -> unsigned int
56+
}
57+
58+
void test_comparison_operations() {
59+
std::int32_t l1 = -1000;
60+
std::uint32_t l2 = 1000;
61+
std::uint8_t l3 = 5;
62+
std::uint16_t l4 = 100;
63+
64+
l1 > l2; // NON_COMPLIANT - l1 -> unsigned int
65+
l1 < l2; // NON_COMPLIANT - l1 -> unsigned int
66+
l1 >= l2; // NON_COMPLIANT - l1 -> unsigned int
67+
l1 <= l2; // NON_COMPLIANT - l1 -> unsigned int
68+
l1 == l2; // NON_COMPLIANT - l1 -> unsigned int
69+
l1 != l2; // NON_COMPLIANT - l1 -> unsigned int
70+
71+
l3 > l4; // NON_COMPLIANT - l3 and l4 -> signed int
72+
l3 < l4; // NON_COMPLIANT - l3 and l4 -> signed int
73+
}
74+
75+
void test_conditional_operator() {
76+
bool l1 = true;
77+
std::uint8_t l2 = 5;
78+
std::uint8_t l3 = 10;
79+
std::uint16_t l4 = 100;
80+
81+
l1 ? l2 : l3; // COMPLIANT - no conversion
82+
l1 ? l2 : l4; // NON_COMPLIANT - l2 and l4 -> signed int
83+
}
84+
85+
void test_shift_operations() {
86+
std::uint8_t l1 = 5;
87+
std::uint32_t l2 = 1000;
88+
89+
l1 << 2; // NON_COMPLIANT - l1 -> signed int
90+
l1 >> 1; // NON_COMPLIANT - l1 -> signed int
91+
l2 << 2; // COMPLIANT
92+
l2 >> 1; // COMPLIANT
93+
}
94+
95+
void test_unary_operations() {
96+
std::uint8_t l1 = 5;
97+
std::uint32_t l2 = 1000;
98+
std::int8_t l3 = -5;
99+
100+
~l1; // NON_COMPLIANT - l1 -> signed int
101+
~l2; // COMPLIANT
102+
-l1; // NON_COMPLIANT - l1 -> signed int
103+
-l3; // COMPLIANT - l3 is signed
104+
+l1; // NON_COMPLIANT - l1 -> signed int
105+
}
106+
107+
void test_increment_decrement() {
108+
std::uint8_t l1 = 5;
109+
std::uint16_t l2 = 100;
110+
111+
l1++; // COMPLIANT - rule does not apply
112+
++l1; // COMPLIANT - rule does not apply
113+
l1--; // COMPLIANT - rule does not apply
114+
--l1; // COMPLIANT - rule does not apply
115+
l2++; // COMPLIANT - rule does not apply
116+
++l2; // COMPLIANT - rule does not apply
117+
}
118+
119+
void test_array_subscript() {
120+
int l1[10];
121+
std::uint8_t l2 = 5;
122+
123+
l1[l2]; // COMPLIANT - rule does not apply
124+
}
125+
126+
void test_exception_compile_time_constants() {
127+
std::uint32_t l1 = 1000;
128+
float l2 = 3.14f;
129+
std::int32_t l3 = 5;
130+
131+
l1 - 1; // COMPLIANT - exception #1
132+
l1 + 42; // COMPLIANT - exception #1
133+
l2 += 1; // COMPLIANT - exception #2
134+
l2 += 0x10001; // COMPLIANT - exception #2
135+
l3 + f1(10); // COMPLIANT - exception #1
136+
l2 + f1(10); // COMPLIANT - exception #2
137+
}
138+
139+
void test_floating_point_conversions() {
140+
float l1;
141+
std::uint32_t l2;
142+
143+
l1 += l2; // NON_COMPLIANT - l2 -> floating
144+
l1 *= l2; // NON_COMPLIANT - l2 -> floating
145+
l1 /= l2; // NON_COMPLIANT - l2 -> floating
146+
}

0 commit comments

Comments
 (0)