New check: Warn about nonportable code that can be optimised (signed integer overflow)
This commit is contained in:
parent
208014bae9
commit
6375fb4f08
|
@ -222,6 +222,55 @@ void CheckType::integerOverflowError(const Token *tok, const ValueFlow::Value &v
|
||||||
value.isInconclusive());
|
value.isInconclusive());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------------
|
||||||
|
// Checking for code patterns that might be optimised differently by the compilers
|
||||||
|
//---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void CheckType::checkIntegerOverflowOptimisations()
|
||||||
|
{
|
||||||
|
// Various patterns are defined here:
|
||||||
|
// https://kristerw.blogspot.com/2016/02/how-undefined-signed-overflow-enables.html
|
||||||
|
|
||||||
|
// These optimisations seem to generate "unwanted" output
|
||||||
|
// x + c < x -> false
|
||||||
|
// x + c <= x -> false
|
||||||
|
// x + c > x -> true
|
||||||
|
// x + c >= x -> true
|
||||||
|
|
||||||
|
if (!mSettings->isEnabled(Settings::PORTABILITY))
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) {
|
||||||
|
if (!Token::Match(tok, "<|<=|>=|>") || !tok->isBinaryOp())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (Token::Match(tok->astOperand1(), "[+-]") &&
|
||||||
|
tok->astOperand1()->valueType() &&
|
||||||
|
tok->astOperand1()->valueType()->isIntegral() &&
|
||||||
|
tok->astOperand1()->valueType()->sign == ValueType::Sign::SIGNED &&
|
||||||
|
tok->astOperand1()->isBinaryOp() &&
|
||||||
|
tok->astOperand1()->astOperand2()->isNumber() &&
|
||||||
|
Token::Match(tok->astOperand1()->astOperand1(), "%var%") &&
|
||||||
|
tok->astOperand1()->astOperand1()->str() == tok->astOperand2()->str()) {
|
||||||
|
bool result;
|
||||||
|
if (tok->astOperand1()->str() == "+")
|
||||||
|
result = Token::Match(tok, ">|>=");
|
||||||
|
else
|
||||||
|
result = Token::Match(tok, "<|<=");
|
||||||
|
integerOverflowOptimisationError(tok, result ? "true" : "false");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheckType::integerOverflowOptimisationError(const Token *tok, const std::string &replace)
|
||||||
|
{
|
||||||
|
const std::string expr = tok ? tok->expressionString() : "x+c<x";
|
||||||
|
const std::string errmsg =
|
||||||
|
"There is a danger that '" + expr + "' will be optimised into '" + replace + "'. Signed integer overflow is undefined behavior.\n"
|
||||||
|
"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 + "'.";
|
||||||
|
reportError(tok, Severity::portability, "integerOverflowOptimization", errmsg, CWE190, false);
|
||||||
|
}
|
||||||
|
|
||||||
//---------------------------------------------------------------------------
|
//---------------------------------------------------------------------------
|
||||||
// Checking for sign conversion when operand can be negative
|
// Checking for sign conversion when operand can be negative
|
||||||
//---------------------------------------------------------------------------
|
//---------------------------------------------------------------------------
|
||||||
|
|
|
@ -54,6 +54,7 @@ public:
|
||||||
CheckType checkType(tokenizer, settings, errorLogger);
|
CheckType checkType(tokenizer, settings, errorLogger);
|
||||||
checkType.checkTooBigBitwiseShift();
|
checkType.checkTooBigBitwiseShift();
|
||||||
checkType.checkIntegerOverflow();
|
checkType.checkIntegerOverflow();
|
||||||
|
checkType.checkIntegerOverflowOptimisations();
|
||||||
checkType.checkSignConversion();
|
checkType.checkSignConversion();
|
||||||
checkType.checkLongCast();
|
checkType.checkLongCast();
|
||||||
checkType.checkFloatToIntegerOverflow();
|
checkType.checkFloatToIntegerOverflow();
|
||||||
|
@ -65,6 +66,9 @@ public:
|
||||||
/** @brief %Check for integer overflow */
|
/** @brief %Check for integer overflow */
|
||||||
void checkIntegerOverflow();
|
void checkIntegerOverflow();
|
||||||
|
|
||||||
|
/** @brief Check for overflow code patterns that will be optimized */
|
||||||
|
void checkIntegerOverflowOptimisations();
|
||||||
|
|
||||||
/** @brief %Check for dangerous sign conversion */
|
/** @brief %Check for dangerous sign conversion */
|
||||||
void checkSignConversion();
|
void checkSignConversion();
|
||||||
|
|
||||||
|
@ -81,6 +85,7 @@ private:
|
||||||
void tooBigBitwiseShiftError(const Token *tok, int lhsbits, const ValueFlow::Value &rhsbits);
|
void tooBigBitwiseShiftError(const Token *tok, int lhsbits, const ValueFlow::Value &rhsbits);
|
||||||
void tooBigSignedBitwiseShiftError(const Token *tok, int lhsbits, const ValueFlow::Value &rhsbits);
|
void tooBigSignedBitwiseShiftError(const Token *tok, int lhsbits, const ValueFlow::Value &rhsbits);
|
||||||
void integerOverflowError(const Token *tok, const ValueFlow::Value &value);
|
void integerOverflowError(const Token *tok, const ValueFlow::Value &value);
|
||||||
|
void integerOverflowOptimisationError(const Token *tok, const std::string &replace);
|
||||||
void signConversionError(const Token *tok, const ValueFlow::Value *negativeValue, const bool constvalue);
|
void signConversionError(const Token *tok, const ValueFlow::Value *negativeValue, const bool constvalue);
|
||||||
void longCastAssignError(const Token *tok);
|
void longCastAssignError(const Token *tok);
|
||||||
void longCastReturnError(const Token *tok);
|
void longCastReturnError(const Token *tok);
|
||||||
|
@ -108,6 +113,7 @@ private:
|
||||||
return "Type checks\n"
|
return "Type checks\n"
|
||||||
"- bitwise shift by too many bits (only enabled when --platform is used)\n"
|
"- bitwise shift by too many bits (only enabled when --platform is used)\n"
|
||||||
"- signed integer overflow (only enabled when --platform is used)\n"
|
"- signed integer overflow (only enabled when --platform is used)\n"
|
||||||
|
"- expression that can be optimised 'out' because signed integer overflow is undefined behavior.\n"
|
||||||
"- dangerous sign conversion, when signed value can be negative\n"
|
"- dangerous sign conversion, when signed value can be negative\n"
|
||||||
"- possible loss of information when assigning int result to long variable\n"
|
"- possible loss of information when assigning int result to long variable\n"
|
||||||
"- possible loss of information when returning int result as long return value\n"
|
"- possible loss of information when returning int result as long return value\n"
|
||||||
|
|
|
@ -35,6 +35,7 @@ private:
|
||||||
void run() OVERRIDE {
|
void run() OVERRIDE {
|
||||||
TEST_CASE(checkTooBigShift_Unix32);
|
TEST_CASE(checkTooBigShift_Unix32);
|
||||||
TEST_CASE(checkIntegerOverflow);
|
TEST_CASE(checkIntegerOverflow);
|
||||||
|
TEST_CASE(checkIntegerOverflowOptimisations);
|
||||||
TEST_CASE(signConversion);
|
TEST_CASE(signConversion);
|
||||||
TEST_CASE(longCastAssign);
|
TEST_CASE(longCastAssign);
|
||||||
TEST_CASE(longCastReturn);
|
TEST_CASE(longCastReturn);
|
||||||
|
@ -248,6 +249,35 @@ private:
|
||||||
ASSERT_EQUALS("", errout.str());
|
ASSERT_EQUALS("", errout.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void checkIntegerOverflowOptimisations() {
|
||||||
|
Settings settings;
|
||||||
|
settings.addEnabled("warning");
|
||||||
|
|
||||||
|
check("int f(int x) { return x + 10 > x; }", &settings);
|
||||||
|
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());
|
||||||
|
|
||||||
|
check("int f(int x) { return x + 10 >= x; }", &settings);
|
||||||
|
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());
|
||||||
|
|
||||||
|
check("int f(int x) { return x + 10 < x; }", &settings);
|
||||||
|
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());
|
||||||
|
|
||||||
|
check("int f(int x) { return x + 10 <= x; }", &settings);
|
||||||
|
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());
|
||||||
|
|
||||||
|
check("int f(int x) { return x - 10 > x; }", &settings);
|
||||||
|
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());
|
||||||
|
|
||||||
|
check("int f(int x) { return x - 10 >= x; }", &settings);
|
||||||
|
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());
|
||||||
|
|
||||||
|
check("int f(int x) { return x - 10 < x; }", &settings);
|
||||||
|
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());
|
||||||
|
|
||||||
|
check("int f(int x) { return x - 10 <= x; }", &settings);
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
void signConversion() {
|
void signConversion() {
|
||||||
check("x = -4 * (unsigned)y;");
|
check("x = -4 * (unsigned)y;");
|
||||||
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());
|
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());
|
||||||
|
|
Loading…
Reference in New Issue