-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Fix Rule 14.3 detection for bounded expressions #8819
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1972,6 +1972,180 @@ void CheckConditionImpl::assignmentInCondition(const Token *eq) | |
| Certainty::normal); | ||
| } | ||
|
|
||
| static bool getIntegerTypeRange(const Token* tok, | ||
| const Settings& settings, | ||
| MathLib::bigint& lower, | ||
| MathLib::bigint& upper) | ||
| { | ||
| if (!tok || !tok->valueType() || tok->valueType()->pointer) | ||
| return false; | ||
|
|
||
| std::uint8_t bits = 0; | ||
| switch (tok->valueType()->type) { | ||
| case ValueType::Type::BOOL: | ||
| bits = 1; | ||
| break; | ||
| case ValueType::Type::CHAR: | ||
| bits = settings.platform.char_bit; | ||
| break; | ||
| case ValueType::Type::SHORT: | ||
| bits = settings.platform.short_bit; | ||
| break; | ||
| case ValueType::Type::INT: | ||
| bits = settings.platform.int_bit; | ||
| break; | ||
| case ValueType::Type::LONG: | ||
| bits = settings.platform.long_bit; | ||
| break; | ||
| case ValueType::Type::LONGLONG: | ||
| bits = settings.platform.long_long_bit; | ||
| break; | ||
| default: | ||
| return false; | ||
| } | ||
| if (bits == 0 || bits > 64) | ||
| return false; | ||
|
|
||
| if (bits == 64 && tok->valueType()->sign == ValueType::Sign::UNSIGNED) | ||
| return false; | ||
| const MathLib::bigint max = bits == 64 ? std::numeric_limits<MathLib::bigint>::max() : (MathLib::bigint(1) << bits) - 1; | ||
| if (tok->valueType()->sign == ValueType::Sign::SIGNED) { | ||
| if (bits == 64) { | ||
| lower = std::numeric_limits<MathLib::bigint>::min(); | ||
| upper = std::numeric_limits<MathLib::bigint>::max(); | ||
| } else { | ||
| lower = -(MathLib::bigint(1) << (bits - 1)); | ||
| upper = max / 2; | ||
| } | ||
| } else { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think if the sign is unknown you should probably return false, not treat it the same as an UNSIGNED |
||
| lower = 0; | ||
| upper = max; | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| static bool getIntegerExpressionRange(const Token* tok, | ||
| const Settings& settings, | ||
| MathLib::bigint& lower, | ||
| MathLib::bigint& upper) | ||
| { | ||
| if (!tok) | ||
| return false; | ||
| if (tok->hasKnownIntValue()) { | ||
| lower = upper = tok->getKnownIntValue(); | ||
| return true; | ||
| } | ||
|
|
||
| if (tok->isCast() && tok->astOperand1()) { | ||
| MathLib::bigint typeLower; | ||
| MathLib::bigint typeUpper; | ||
| const bool hasSourceRange = getIntegerExpressionRange(tok->astOperand1(), settings, lower, upper); | ||
| const bool hasTypeRange = getIntegerTypeRange(tok, settings, typeLower, typeUpper); | ||
| if (!hasSourceRange && !hasTypeRange) | ||
| return false; | ||
| if (!hasSourceRange) { | ||
| lower = typeLower; | ||
| upper = typeUpper; | ||
| return true; | ||
| } | ||
| if (!hasTypeRange) | ||
| return true; | ||
| lower = std::max(lower, typeLower); | ||
| upper = std::min(upper, typeUpper); | ||
| return lower <= upper; | ||
| } | ||
|
|
||
| if (Token::simpleMatch(tok, "(") && tok->astOperand1()) | ||
| return getIntegerExpressionRange(tok->astOperand1(), settings, lower, upper); | ||
|
|
||
| if (Token::Match(tok, "+|-")) { | ||
| MathLib::bigint operandLower; | ||
| MathLib::bigint operandUpper; | ||
| MathLib::bigint constantLower; | ||
| MathLib::bigint constantUpper; | ||
| if (!getIntegerExpressionRange(tok->astOperand1(), settings, operandLower, operandUpper) || | ||
| !getIntegerExpressionRange(tok->astOperand2(), settings, constantLower, constantUpper) || | ||
| constantLower != constantUpper) | ||
| return false; | ||
| const MathLib::bigint minimum = std::numeric_limits<MathLib::bigint>::min(); | ||
| const MathLib::bigint maximum = std::numeric_limits<MathLib::bigint>::max(); | ||
| if (tok->str() == "+" && | ||
| ((constantLower > 0 && operandUpper > maximum - constantLower) || | ||
| (constantLower < 0 && operandLower < minimum - constantLower))) | ||
| return false; | ||
| if (tok->str() == "-" && | ||
| ((constantUpper > 0 && operandLower < minimum + constantUpper) || | ||
| (constantUpper < 0 && operandUpper > maximum + constantUpper))) | ||
| return false; | ||
| if (tok->str() == "+") { | ||
| lower = operandLower + constantLower; | ||
| upper = operandUpper + constantUpper; | ||
| } else { | ||
| lower = operandLower - constantUpper; | ||
| upper = operandUpper - constantLower; | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| return getIntegerTypeRange(tok, settings, lower, upper); | ||
| } | ||
|
|
||
| static bool compareIntegerRange(const Token* comparison, | ||
| MathLib::bigint lower, | ||
| MathLib::bigint upper, | ||
| MathLib::bigint value, | ||
| bool& result) | ||
| { | ||
| if (!comparison || !comparison->isComparisonOp()) | ||
| return false; | ||
| if (comparison->str() == "<") { | ||
| if (upper < value) | ||
| result = true; | ||
| else if (lower >= value) | ||
| result = false; | ||
| else | ||
| return false; | ||
| } else if (comparison->str() == "<=") { | ||
| if (upper <= value) | ||
| result = true; | ||
| else if (lower > value) | ||
| result = false; | ||
| else | ||
| return false; | ||
| } else if (comparison->str() == ">") { | ||
| if (lower > value) | ||
| result = true; | ||
| else if (upper <= value) | ||
| result = false; | ||
| else | ||
| return false; | ||
| } else if (comparison->str() == ">=") { | ||
| if (lower >= value) | ||
| result = true; | ||
| else if (upper < value) | ||
| result = false; | ||
| else | ||
| return false; | ||
| } else if (comparison->str() == "==") { | ||
| if (lower == upper) | ||
| result = (lower == value); | ||
| else if (value < lower || value > upper) | ||
| result = false; | ||
| else | ||
| return false; | ||
| } else if (comparison->str() == "!=") { | ||
| if (lower == upper) | ||
| result = (lower != value); | ||
| else if (value < lower || value > upper) | ||
| result = true; | ||
| else | ||
| return false; | ||
| } else { | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| void CheckConditionImpl::checkCompareValueOutOfTypeRange() | ||
| { | ||
| if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("compareValueOutOfTypeRangeError")) | ||
|
|
@@ -1988,6 +2162,26 @@ void CheckConditionImpl::checkCompareValueOutOfTypeRange() | |
| if (!tok->isComparisonOp() || !tok->isBinaryOp()) | ||
| continue; | ||
|
|
||
| for (int i = 0; i < 2; ++i) { | ||
| const Token* const expressionTok = (i == 0) ? tok->astOperand2() : tok->astOperand1(); | ||
| const Token* const valueTok = (i == 0) ? tok->astOperand1() : tok->astOperand2(); | ||
| if (!expressionTok || !valueTok || !valueTok->hasKnownIntValue() || expressionTok->hasKnownIntValue()) | ||
| continue; | ||
| if (expressionTok->str() != "(" && !expressionTok->isCast() && !expressionTok->isArithmeticalOp()) | ||
| continue; | ||
| MathLib::bigint lower; | ||
| MathLib::bigint upper; | ||
| if (!getIntegerExpressionRange(expressionTok, mSettings, lower, upper)) | ||
| continue; | ||
| bool result; | ||
| if (!compareIntegerRange(tok, lower, upper, valueTok->getKnownIntValue(), result) || diag(tok)) | ||
| continue; | ||
| compareValueOutOfTypeRangeError(valueTok, | ||
| expressionTok->valueType() ? expressionTok->valueType()->str() : "", | ||
| valueTok->getKnownIntValue(), | ||
| result); | ||
| } | ||
|
|
||
| for (int i = 0; i < 2; ++i) { | ||
| const Token * const valueTok = (i == 0) ? tok->astOperand1() : tok->astOperand2(); | ||
| const Token * const typeTok = valueTok->astSibling(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6546,6 +6546,45 @@ class TestCondition : public TestFixture { | |
| "[test.cpp:4:13]: (style) Comparing expression of type 'const unsigned int &' against value 4294967295. Condition is always false. [compareValueOutOfTypeRangeError]\n", | ||
| errout_str()); | ||
|
|
||
| check("typedef unsigned int uint32;\n" | ||
| "typedef unsigned long long uint64;\n" | ||
| "typedef long long sint64;\n" | ||
| "void f(uint32 x) {\n" | ||
| " uint64 tmp = ((uint64)x) + 1ULL;\n" | ||
| " if (tmp > 4294967295ULL)\n" | ||
| " tmp = 4294967295ULL;\n" | ||
| " if ((((sint64)((uint32)tmp)) - 1LL) < 0LL) {}\n" | ||
| " if ((((sint64)((uint32)tmp)) - 1LL) > 4294967295LL) {}\n" | ||
| "}\n", settingsUnix64); | ||
| ASSERT_EQUALS("[test.cpp:9:43]: (style) Comparing expression of type 'signed long long' against value 4294967295. Condition is always false. [compareValueOutOfTypeRangeError]\n", | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The text/ID of the warning seems incorrect.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hello! The condition is essentially (sint64), the diagnostic log is correct. But the check is only possible because there is a (uint32) inside. |
||
| errout_str()); | ||
|
|
||
| // cast directly around variable: both the range-based and the declared-type | ||
| // analysis can prove the condition invariant; diag() must prevent duplicates | ||
| check("void f(unsigned char c) {\n" | ||
| " if ((unsigned char)c > 255) {}\n" | ||
| "}\n", settingsUnix64); | ||
| ASSERT_EQUALS("[test.cpp:2:28]: (style) Comparing expression of type 'unsigned char' against value 255. Condition is always false. [compareValueOutOfTypeRangeError]\n", | ||
| errout_str()); | ||
|
|
||
| check("void f(unsigned char c) {\n" | ||
| " if ((unsigned char)c == 256) {}\n" | ||
| "}\n", settingsUnix64); | ||
| ASSERT_EQUALS("[test.cpp:2:29]: (style) Comparing expression of type 'unsigned char' against value 256. Condition is always false. [compareValueOutOfTypeRangeError]\n", | ||
| errout_str()); | ||
|
|
||
| check("void f(unsigned int u) {\n" | ||
| " if ((unsigned int)u > 4294967295ULL) {}\n" | ||
| "}\n", settingsUnix64); | ||
| ASSERT_EQUALS("[test.cpp:2:27]: (style) Comparing expression of type 'unsigned int' against value 4294967295. Condition is always false. [compareValueOutOfTypeRangeError]\n", | ||
| errout_str()); | ||
|
|
||
| check("void f(unsigned short s) {\n" | ||
| " if ((unsigned int)s > 4294967295ULL) {}\n" | ||
| "}\n", settingsUnix64); | ||
| ASSERT_EQUALS("[test.cpp:2:27]: (style) Comparing expression of type 'unsigned int' against value 4294967295. Condition is always false. [compareValueOutOfTypeRangeError]\n", | ||
| errout_str()); | ||
|
|
||
| check("void f() {\n" | ||
| " long long ll = 1024 * 1024 * 1024;\n" | ||
| " if (ll * 8 < INT_MAX) {}\n" | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe this switch could be simplified to something like
I think it would be shorter, duplicate less logic, and it would also support wchar_t
the check for isIntegral() could even be moved up to line 1980 to be very clear that this function only works for integral types
Cheers!
--Aaron