Skip to content

Commit 6375fb4

Browse files
committed
New check: Warn about nonportable code that can be optimised (signed integer overflow)
1 parent 208014b commit 6375fb4

3 files changed

Lines changed: 85 additions & 0 deletions

File tree

lib/checktype.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,55 @@ void CheckType::integerOverflowError(const Token *tok, const ValueFlow::Value &v
222222
value.isInconclusive());
223223
}
224224

225+
//---------------------------------------------------------------------------------
226+
// Checking for code patterns that might be optimised differently by the compilers
227+
//---------------------------------------------------------------------------------
228+
229+
void CheckType::checkIntegerOverflowOptimisations()
230+
{
231+
// Various patterns are defined here:
232+
// https://kristerw.blogspot.com/2016/02/how-undefined-signed-overflow-enables.html
233+
234+
// These optimisations seem to generate "unwanted" output
235+
// x + c < x -> false
236+
// x + c <= x -> false
237+
// x + c > x -> true
238+
// x + c >= x -> true
239+
240+
if (!mSettings->isEnabled(Settings::PORTABILITY))
241+
return;
242+
243+
for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) {
244+
if (!Token::Match(tok, "<|<=|>=|>") || !tok->isBinaryOp())
245+
continue;
246+
247+
if (Token::Match(tok->astOperand1(), "[+-]") &&
248+
tok->astOperand1()->valueType() &&
249+
tok->astOperand1()->valueType()->isIntegral() &&
250+
tok->astOperand1()->valueType()->sign == ValueType::Sign::SIGNED &&
251+
tok->astOperand1()->isBinaryOp() &&
252+
tok->astOperand1()->astOperand2()->isNumber() &&
253+
Token::Match(tok->astOperand1()->astOperand1(), "%var%") &&
254+
tok->astOperand1()->astOperand1()->str() == tok->astOperand2()->str()) {
255+
bool result;
256+
if (tok->astOperand1()->str() == "+")
257+
result = Token::Match(tok, ">|>=");
258+
else
259+
result = Token::Match(tok, "<|<=");
260+
integerOverflowOptimisationError(tok, result ? "true" : "false");
261+
}
262+
}
263+
}
264+
265+
void CheckType::integerOverflowOptimisationError(const Token *tok, const std::string &replace)
266+
{
267+
const std::string expr = tok ? tok->expressionString() : "x+c<x";
268+
const std::string errmsg =
269+
"There is a danger that '" + expr + "' will be optimised into '" + replace + "'. Signed integer overflow is undefined behavior.\n"
270+
"There is a danger that '" + expr + "' will be optimised into '" + replace + "'. Your code could work differently depending on what compiler/flags/version/etc is used. Signed integer overflow is undefined behavior and assuming that it will never happen; the result of '" + expr + "' is always '" + replace + "'.";
271+
reportError(tok, Severity::portability, "integerOverflowOptimization", errmsg, CWE190, false);
272+
}
273+
225274
//---------------------------------------------------------------------------
226275
// Checking for sign conversion when operand can be negative
227276
//---------------------------------------------------------------------------

lib/checktype.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ class CPPCHECKLIB CheckType : public Check {
5454
CheckType checkType(tokenizer, settings, errorLogger);
5555
checkType.checkTooBigBitwiseShift();
5656
checkType.checkIntegerOverflow();
57+
checkType.checkIntegerOverflowOptimisations();
5758
checkType.checkSignConversion();
5859
checkType.checkLongCast();
5960
checkType.checkFloatToIntegerOverflow();
@@ -65,6 +66,9 @@ class CPPCHECKLIB CheckType : public Check {
6566
/** @brief %Check for integer overflow */
6667
void checkIntegerOverflow();
6768

69+
/** @brief Check for overflow code patterns that will be optimized */
70+
void checkIntegerOverflowOptimisations();
71+
6872
/** @brief %Check for dangerous sign conversion */
6973
void checkSignConversion();
7074

@@ -81,6 +85,7 @@ class CPPCHECKLIB CheckType : public Check {
8185
void tooBigBitwiseShiftError(const Token *tok, int lhsbits, const ValueFlow::Value &rhsbits);
8286
void tooBigSignedBitwiseShiftError(const Token *tok, int lhsbits, const ValueFlow::Value &rhsbits);
8387
void integerOverflowError(const Token *tok, const ValueFlow::Value &value);
88+
void integerOverflowOptimisationError(const Token *tok, const std::string &replace);
8489
void signConversionError(const Token *tok, const ValueFlow::Value *negativeValue, const bool constvalue);
8590
void longCastAssignError(const Token *tok);
8691
void longCastReturnError(const Token *tok);
@@ -108,6 +113,7 @@ class CPPCHECKLIB CheckType : public Check {
108113
return "Type checks\n"
109114
"- bitwise shift by too many bits (only enabled when --platform is used)\n"
110115
"- signed integer overflow (only enabled when --platform is used)\n"
116+
"- expression that can be optimised 'out' because signed integer overflow is undefined behavior.\n"
111117
"- dangerous sign conversion, when signed value can be negative\n"
112118
"- possible loss of information when assigning int result to long variable\n"
113119
"- possible loss of information when returning int result as long return value\n"

test/testtype.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class TestType : public TestFixture {
3535
void run() OVERRIDE {
3636
TEST_CASE(checkTooBigShift_Unix32);
3737
TEST_CASE(checkIntegerOverflow);
38+
TEST_CASE(checkIntegerOverflowOptimisations);
3839
TEST_CASE(signConversion);
3940
TEST_CASE(longCastAssign);
4041
TEST_CASE(longCastReturn);
@@ -248,6 +249,35 @@ class TestType : public TestFixture {
248249
ASSERT_EQUALS("", errout.str());
249250
}
250251

252+
void checkIntegerOverflowOptimisations() {
253+
Settings settings;
254+
settings.addEnabled("warning");
255+
256+
check("int f(int x) { return x + 10 > x; }", &settings);
257+
ASSERT_EQUALS("[test.cpp:1]: (portability) There is a danger that 'x+10>x' will be optimised into 'true'. Signed integer overflow is undefined behavior.\n", errout.str());
258+
259+
check("int f(int x) { return x + 10 >= x; }", &settings);
260+
ASSERT_EQUALS("[test.cpp:1]: (portability) There is a danger that 'x+10>=x' will be optimised into 'true'. Signed integer overflow is undefined behavior.\n", errout.str());
261+
262+
check("int f(int x) { return x + 10 < x; }", &settings);
263+
ASSERT_EQUALS("[test.cpp:1]: (portability) There is a danger that 'x+10<x' will be optimised into 'false'. Signed integer overflow is undefined behavior.\n", errout.str());
264+
265+
check("int f(int x) { return x + 10 <= x; }", &settings);
266+
ASSERT_EQUALS("[test.cpp:1]: (portability) There is a danger that 'x+10<=x' will be optimised into 'false'. Signed integer overflow is undefined behavior.\n", errout.str());
267+
268+
check("int f(int x) { return x - 10 > x; }", &settings);
269+
ASSERT_EQUALS("[test.cpp:1]: (portability) There is a danger that 'x-10>x' will be optimised into 'false'. Signed integer overflow is undefined behavior.\n", errout.str());
270+
271+
check("int f(int x) { return x - 10 >= x; }", &settings);
272+
ASSERT_EQUALS("[test.cpp:1]: (portability) There is a danger that 'x-10>=x' will be optimised into 'false'. Signed integer overflow is undefined behavior.\n", errout.str());
273+
274+
check("int f(int x) { return x - 10 < x; }", &settings);
275+
ASSERT_EQUALS("[test.cpp:1]: (portability) There is a danger that 'x-10<x' will be optimised into 'true'. Signed integer overflow is undefined behavior.\n", errout.str());
276+
277+
check("int f(int x) { return x - 10 <= x; }", &settings);
278+
ASSERT_EQUALS("[test.cpp:1]: (portability) There is a danger that 'x-10<=x' will be optimised into 'true'. Signed integer overflow is undefined behavior.\n", errout.str());
279+
}
280+
251281
void signConversion() {
252282
check("x = -4 * (unsigned)y;");
253283
ASSERT_EQUALS("[test.cpp:1]: (warning) Expression '-4' has a negative value. That is converted to an unsigned value and used in an unsigned calculation.\n", errout.str());

0 commit comments

Comments
 (0)