Skip to content

Commit 8502e53

Browse files
jfdevergeJean-François DEVERGE
authored andcommitted
Fix #15000: Propagate integral cast ranges to condition analysis
Impossible bounds on integral casts must not be propagated through arithmetic with unsigned result type, because wrap-around invalidates the bounds. '(uint32_t)x - 1u == 0xFFFFFFFFu' must not be reported as an always-false condition, since 0u - 1u wraps to 4294967295. - Filter impossible integral bounds from the operand values before interval inference in valueFlowInferCondition() when the arithmetic result type is an unsigned integer (+, -, *). - Skip impossible-bounded operand pairs in the binary calculation branch of setTokenValue() under the same result-type predicate. - Known and possible values, impossible point values, comparisons, pointer arithmetic and arithmetic with signed result type are preserved.
1 parent 43425ee commit 8502e53

5 files changed

Lines changed: 218 additions & 7 deletions

File tree

‎lib/valueflow.cpp‎

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1087,6 +1087,24 @@ static void valueFlowImpossibleValues(TokenList& tokenList, const Settings& sett
10871087
upper.bound = ValueFlow::Value::Bound::Lower;
10881088
upper.setImpossible();
10891089
setTokenValue(tok, std::move(upper), settings);
1090+
} else if (tok->isCast() && tok->valueType() && tok->valueType()->isIntegral() && !tok->valueType()->pointer) {
1091+
MathLib::bigint minValue;
1092+
MathLib::bigint maxValue;
1093+
if (!ValueFlow::getMinMaxValues(tok->valueType(), settings.platform, minValue, maxValue))
1094+
continue;
1095+
1096+
if (minValue > std::numeric_limits<MathLib::bigint>::min()) {
1097+
ValueFlow::Value lower{minValue - 1};
1098+
lower.bound = ValueFlow::Value::Bound::Upper;
1099+
lower.setImpossible();
1100+
setTokenValue(tok, std::move(lower), settings);
1101+
}
1102+
if (maxValue < std::numeric_limits<MathLib::bigint>::max()) {
1103+
ValueFlow::Value upper{maxValue + 1};
1104+
upper.bound = ValueFlow::Value::Bound::Lower;
1105+
upper.setImpossible();
1106+
setTokenValue(tok, std::move(upper), settings);
1107+
}
10901108
} else if (astIsUnsigned(tok) && !astIsPointer(tok)) {
10911109
std::vector<MathLib::bigint> minvalue = minUnsignedValue(tok);
10921110
if (minvalue.empty())
@@ -5134,6 +5152,26 @@ static bool isIntegralOrPointer(const Token* tok)
51345152
return false;
51355153
}
51365154

5155+
/**
5156+
* @brief Check if the token is an arithmetic operation whose result type is
5157+
* an unsigned integer, i.e. arithmetic that may wrap around.
5158+
*
5159+
* Impossible bounds cannot be propagated through such arithmetic because
5160+
* wrap-around invalidates the bounds.
5161+
*/
5162+
static bool isUnsignedArithmeticResult(const Token* tok)
5163+
{
5164+
if (!Token::Match(tok, "+|-|*"))
5165+
return false;
5166+
const ValueType* vt = tok->valueType();
5167+
return vt && vt->isIntegral() && vt->pointer == 0 &&
5168+
vt->sign == ValueType::Sign::UNSIGNED &&
5169+
(vt->type == ValueType::Type::INT ||
5170+
vt->type == ValueType::Type::LONG ||
5171+
vt->type == ValueType::Type::LONGLONG ||
5172+
vt->type == ValueType::Type::UNKNOWN_INT);
5173+
}
5174+
51375175
static void valueFlowInferCondition(TokenList& tokenlist, const Settings& settings)
51385176
{
51395177
for (Token* tok = tokenlist.front(); tok; tok = tok->next()) {
@@ -5154,8 +5192,19 @@ static void valueFlowInferCondition(TokenList& tokenlist, const Settings& settin
51545192
}
51555193
}
51565194
} else if (isIntegralOrPointer(tok->astOperand1()) && isIntegralOrPointer(tok->astOperand2())) {
5195+
std::list<ValueFlow::Value> lhsValues = tok->astOperand1()->values();
5196+
std::list<ValueFlow::Value> rhsValues = tok->astOperand2()->values();
5197+
// Impossible bounds cannot be propagated through arithmetic whose
5198+
// result type is unsigned because wrap-around invalidates the bound
5199+
if (isUnsignedArithmeticResult(tok)) {
5200+
const auto isImpossibleIntegralBound = [](const ValueFlow::Value& v) {
5201+
return v.isIntValue() && v.isImpossible() && v.bound != ValueFlow::Value::Bound::Point;
5202+
};
5203+
lhsValues.remove_if(isImpossibleIntegralBound);
5204+
rhsValues.remove_if(isImpossibleIntegralBound);
5205+
}
51575206
std::vector<ValueFlow::Value> result =
5158-
infer(makeIntegralInferModel(), tok->str(), tok->astOperand1()->values(), tok->astOperand2()->values());
5207+
infer(makeIntegralInferModel(), tok->str(), lhsValues, rhsValues);
51595208
for (ValueFlow::Value& value : result) {
51605209
setTokenValue(tok, std::move(value), settings);
51615210
}

‎lib/vf_common.cpp‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ namespace ValueFlow
4646
if (!vt || !vt->isIntegral() || vt->pointer)
4747
return false;
4848

49-
std::uint8_t bits;
49+
std::size_t bits;
5050
switch (vt->type) {
5151
case ValueType::Type::BOOL:
5252
bits = 1;
@@ -66,29 +66,37 @@ namespace ValueFlow
6666
case ValueType::Type::LONGLONG:
6767
bits = platform.long_long_bit;
6868
break;
69+
case ValueType::Type::WCHAR_T:
70+
bits = platform.sizeof_wchar_t * platform.char_bit;
71+
break;
6972
default:
7073
return false;
7174
}
7275

76+
if (bits == 0) {
77+
return false;
78+
}
7379
if (bits == 1) {
7480
minValue = 0;
7581
maxValue = 1;
7682
} else if (bits < 62) {
7783
if (vt->sign == ValueType::Sign::UNSIGNED) {
7884
minValue = 0;
7985
maxValue = (1LL << bits) - 1;
80-
} else {
86+
} else if (vt->sign == ValueType::Sign::SIGNED) {
8187
minValue = -(1LL << (bits - 1));
8288
maxValue = (1LL << (bits - 1)) - 1;
83-
}
89+
} else
90+
return false;
8491
} else if (bits == 64) {
8592
if (vt->sign == ValueType::Sign::UNSIGNED) {
8693
minValue = 0;
87-
maxValue = LLONG_MAX; // todo max unsigned value
88-
} else {
94+
maxValue = LLONG_MAX; // MathLib::bigint cannot represent ULLONG_MAX; conservative max for unsigned 64-bit
95+
} else if (vt->sign == ValueType::Sign::SIGNED) {
8996
minValue = LLONG_MIN;
9097
maxValue = LLONG_MAX;
91-
}
98+
} else
99+
return false;
92100
} else {
93101
return false;
94102
}

‎lib/vf_settokenvalue.cpp‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,18 @@ namespace ValueFlow
488488
return;
489489
}
490490

491+
// Impossible bounds cannot be propagated through arithmetic whose
492+
// result type is unsigned, because wrap-around invalidates the bound
493+
const ValueType* resultType = parent->valueType();
494+
const bool wraps =
495+
resultType && resultType->isIntegral() &&
496+
resultType->sign == ValueType::Sign::UNSIGNED && resultType->pointer == 0 &&
497+
(resultType->type == ValueType::Type::INT ||
498+
resultType->type == ValueType::Type::LONG ||
499+
resultType->type == ValueType::Type::LONGLONG ||
500+
resultType->type == ValueType::Type::UNKNOWN_INT);
501+
const bool skipImpossibleBounds = wraps && Token::Match(parent, "+|-|*");
502+
491503
for (const Value &value1 : parent->astOperand1()->values()) {
492504
if (!isComputableValue(parent, value1))
493505
continue;
@@ -500,6 +512,12 @@ namespace ValueFlow
500512
continue;
501513
if (!isCompatibleValues(value1, value2))
502514
continue;
515+
// Skip impossible bounds on arithmetic with unsigned result type
516+
const bool operandHasImpossibleBound =
517+
(value1.isIntValue() && value1.isImpossible() && value1.bound != Value::Bound::Point) ||
518+
(value2.isIntValue() && value2.isImpossible() && value2.bound != Value::Bound::Point);
519+
if (skipImpossibleBounds && operandHasImpossibleBound)
520+
continue;
503521
Value result(0);
504522
combineValueProperties(value1, value2, result);
505523
if (astIsFloat(parent, false)) {

‎test/testcondition.cpp‎

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6612,6 +6612,99 @@ class TestCondition : public TestFixture {
66126612
"[test.cpp:4:13]: (style) Comparing expression of type 'const unsigned int &' against value 4294967295. Condition is always false. [compareValueOutOfTypeRangeError]\n",
66136613
errout_str());
66146614

6615+
// PR review: no false positive when unsigned arithmetic wraps around
6616+
check("void f(int x) {\n"
6617+
" if ((unsigned int)x - 1u == 0xFFFFFFFFu) {}\n"
6618+
"}\n", settingsUnix64);
6619+
ASSERT_EQUALS("", errout_str());
6620+
6621+
check("void f(int x) {\n"
6622+
" if ((unsigned short)x - 1u == 0xFFFFFFFFu) {}\n"
6623+
"}\n", settingsUnix64);
6624+
ASSERT_EQUALS("", errout_str());
6625+
6626+
// Signed result type after integer promotion must still warn
6627+
check("void f(int x) {\n"
6628+
" if ((unsigned short)x + 1 == 0) {}\n"
6629+
"}\n", settingsUnix64);
6630+
ASSERT_EQUALS("[test.cpp:2:29]: (style) Condition '(unsigned short)x+1==0' is always false [knownConditionTrueFalse]\n",
6631+
errout_str());
6632+
6633+
// Direct cast bounds are preserved
6634+
check("void f(int x) {\n"
6635+
" if ((unsigned int)x > 4294967295ULL) {}\n"
6636+
"}\n", settingsUnix64);
6637+
ASSERT_EQUALS("[test.cpp:2:25]: (style) Comparing expression of type 'unsigned int' against value 4294967295. Condition is always false. [compareValueOutOfTypeRangeError]\n",
6638+
errout_str());
6639+
6640+
// Reproduce the original typedef-heavy Simulink-generated pattern from #15000.
6641+
check("void f(unsigned int x) {\n"
6642+
" unsigned long long tmp = ((unsigned long long)x) + 1ULL;\n"
6643+
" if (tmp > 4294967295ULL)\n"
6644+
" tmp = 4294967295ULL;\n"
6645+
" if ((((long long)((unsigned int)tmp)) - 1LL) < 0LL) {}\n"
6646+
" if ((((long long)((unsigned int)tmp)) - 1LL) > 4294967295LL) {}\n"
6647+
"}\n", settingsUnix64);
6648+
ASSERT_EQUALS("[test.cpp:6:50]: (style) Condition '(((long long)((unsigned int)tmp))-1LL)>4294967295LL' is always false [knownConditionTrueFalse]\n",
6649+
errout_str());
6650+
6651+
check("void f(unsigned long long tmp) {\n"
6652+
" if (tmp > 4294967295ULL)\n"
6653+
" tmp = 4294967295ULL;\n"
6654+
" if (static_cast<long long>(static_cast<unsigned int>(tmp)) - 1LL > 4294967295LL) {}\n"
6655+
"}\n", settingsUnix64);
6656+
ASSERT_EQUALS("[test.cpp:4:70]: (style) Condition 'static_cast<long long>(static_cast<unsigned int>(tmp))-1LL>4294967295LL' is always false [knownConditionTrueFalse]\n",
6657+
errout_str());
6658+
6659+
// cast directly around variable: both the range-based and the declared-type
6660+
// analysis can prove the condition invariant; diag() must prevent duplicates
6661+
check("void f(unsigned char c) {\n"
6662+
" if ((unsigned char)c > 255) {}\n"
6663+
"}\n", settingsUnix64);
6664+
ASSERT_EQUALS("[test.cpp:2:28]: (style) Comparing expression of type 'unsigned char' against value 255. Condition is always false. [compareValueOutOfTypeRangeError]\n",
6665+
errout_str());
6666+
6667+
check("void f(unsigned char c) {\n"
6668+
" if ((unsigned char)c == 256) {}\n"
6669+
"}\n", settingsUnix64);
6670+
ASSERT_EQUALS("[test.cpp:2:29]: (style) Comparing expression of type 'unsigned char' against value 256. Condition is always false. [compareValueOutOfTypeRangeError]\n",
6671+
errout_str());
6672+
6673+
check("void f(unsigned int u) {\n"
6674+
" if ((unsigned int)u > 4294967295ULL) {}\n"
6675+
"}\n", settingsUnix64);
6676+
ASSERT_EQUALS("[test.cpp:2:27]: (style) Comparing expression of type 'unsigned int' against value 4294967295. Condition is always false. [compareValueOutOfTypeRangeError]\n",
6677+
errout_str());
6678+
6679+
check("void f(unsigned short s) {\n"
6680+
" if ((unsigned int)s > 4294967295ULL) {}\n"
6681+
"}\n", settingsUnix64);
6682+
ASSERT_EQUALS("[test.cpp:2:27]: (style) Comparing expression of type 'unsigned int' against value 4294967295. Condition is always false. [compareValueOutOfTypeRangeError]\n",
6683+
errout_str());
6684+
6685+
// wchar_t range is derived through ValueType::getSizeOf()
6686+
check("void f(wchar_t c) {\n"
6687+
" if ((wchar_t)c > 0x7fffffff) {}\n"
6688+
"}\n", settingsUnix64);
6689+
ASSERT_EQUALS("[test.cpp:2:20]: (style) Condition '(wchar_t)c>0x7fffffff' is always false [knownConditionTrueFalse]\n",
6690+
errout_str());
6691+
6692+
check("void f(unsigned int x) {\n"
6693+
" if (-(signed char)x < -129) {}\n"
6694+
"}\n", settingsUnix64);
6695+
ASSERT_EQUALS("[test.cpp:2:25]: (style) Condition '-(char)x<-129' is always false [knownConditionTrueFalse]\n",
6696+
errout_str());
6697+
6698+
check("void f(int x) {\n"
6699+
" if ((x) > 0) {}\n"
6700+
"}\n", settingsUnix64);
6701+
ASSERT_EQUALS("", errout_str());
6702+
6703+
check("void f(unsigned int x) {\n"
6704+
" if ((unsigned int)x > 0) {}\n"
6705+
"}\n", settingsUnix64);
6706+
ASSERT_EQUALS("", errout_str());
6707+
66156708
check("void f() {\n"
66166709
" long long ll = 1024 * 1024 * 1024;\n"
66176710
" if (ll * 8 < INT_MAX) {}\n"

‎test/testvalueflow.cpp‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9604,6 +9604,49 @@ class TestValueFlow : public TestFixture {
96049604
"}\n";
96059605
ASSERT_EQUALS(true, testValueOfXImpossible(code, 3U, "a", -1));
96069606
ASSERT_EQUALS(true, testValueOfXImpossible(code, 3U, -1));
9607+
9608+
const Settings settingsUnix64 = settingsBuilder().platform(Platform::Type::Unix64).build();
9609+
code = "void f(unsigned long long x) {\n"
9610+
" return (unsigned int)x;\n"
9611+
"}\n";
9612+
SimpleTokenizer tokenizer(settingsUnix64, *this);
9613+
ASSERT(tokenizer.tokenize(code));
9614+
const Token* returnTok = Token::findmatch(tokenizer.tokens(), "return (");
9615+
ASSERT(returnTok && returnTok->next());
9616+
const std::list<ValueFlow::Value>& castValues = returnTok->next()->values();
9617+
ASSERT(std::any_of(castValues.cbegin(), castValues.cend(), [](const ValueFlow::Value& value) {
9618+
return value.isImpossible() && value.intvalue == -1;
9619+
}));
9620+
ASSERT(std::any_of(castValues.cbegin(), castValues.cend(), [](const ValueFlow::Value& value) {
9621+
return value.isImpossible() && value.intvalue == 4294967296;
9622+
}));
9623+
9624+
// PR review: impossible bounds on a cast must not be propagated through
9625+
// arithmetic with unsigned result type, since wrap-around invalidates the bound
9626+
code = "void f(int x) {\n"
9627+
" return (unsigned int)x - 1u;\n"
9628+
"}\n";
9629+
SimpleTokenizer tokenizer2(settingsUnix64, *this);
9630+
ASSERT(tokenizer2.tokenize(code));
9631+
const Token* minusTok = Token::findsimplematch(tokenizer2.tokens(), "-");
9632+
ASSERT(minusTok);
9633+
for (const ValueFlow::Value& value : minusTok->values()) {
9634+
ASSERT(!(value.isImpossible() && value.isIntValue() &&
9635+
value.bound != ValueFlow::Value::Bound::Point && value.intvalue == 4294967295));
9636+
}
9637+
9638+
// Known values are still propagated through unsigned arithmetic
9639+
code = "void f(int x) {\n"
9640+
" unsigned int y = 5u;\n"
9641+
" return y - 1u;\n"
9642+
"}\n";
9643+
SimpleTokenizer tokenizer3(settingsUnix64, *this);
9644+
ASSERT(tokenizer3.tokenize(code));
9645+
const Token* minusTok3 = Token::findsimplematch(tokenizer3.tokens(), "-");
9646+
ASSERT(minusTok3);
9647+
ASSERT(std::any_of(minusTok3->values().cbegin(), minusTok3->values().cend(), [](const ValueFlow::Value& value) {
9648+
return value.isKnown() && value.isIntValue() && value.intvalue == 4;
9649+
}));
96079650
}
96089651

96099652
void valueFlowImpossibleIncDec()

0 commit comments

Comments
 (0)