Conversation
| // Shifts: calculate() rejects a negative or too large shift and a negative value | ||
| bool error = false; | ||
| result = calculate(op, edge, k, &error); | ||
| if (!rangeIsLhs || error || (op == "<<" && (result >> k) != edge)) |
| // Shifts: calculate() rejects a negative or too large shift and a negative value | ||
| bool error = false; | ||
| result = calculate(op, edge, k, &error); | ||
| if (!rangeIsLhs || error || (op == "<<" && (result >> k) != edge)) |
| // known value and does not depend on a tracked value | ||
| const Values* getStoredValues(const Token* expr) const | ||
| { | ||
| if (expr->exprId() == 0) |
| const ValueFlow::Value& v = utils::as_const(*pm).at(expr->exprId()); | ||
| if (v.isIntValue()) | ||
| return v; | ||
| if (const ValueFlow::Value* v = pm->getValue(expr->exprId(), /*impossible*/ true)) { |
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Range propagation is currently unsound for wrapping arithmetic, casts, and compound updates, with additional precision and performance issues.
Review effort: Balanced
Findings: 1
Open (4)
What changed in this PR
Refactors value-flow program memory to retain multiple constraints, fixing incorrect branch decisions for bounded conditions such as x > 3.
Changes:
- Stores and merges multiple value constraints per expression.
- Propagates ranges through arithmetic, conditions, containers, and execution.
- Adds regression and unit coverage for range-aware analysis.
| File | Description |
|---|---|
lib/programmemory.cpp |
Implements multi-value storage and range execution. |
lib/programmemory.h |
Defines the new list-based API. |
lib/valueflow.cpp |
Improves bound solving and loop handling. |
lib/vfvalue.h |
Adds range-edge helpers. |
lib/vf_common.h |
Adds shared saturation detection. |
lib/vf_analyzers.cpp |
Adapts analyzer state to value lists. |
lib/token.cpp |
Exposes contradiction removal. |
lib/token.h |
Declares contradiction-removal API. |
test/testprogrammemory.cpp |
Tests constraints and range execution. |
test/testvalueflow.cpp |
Tests range-based value flow. |
test/testnullpointer.cpp |
Tests null-pointer analysis with ranges. |
test/testcondition.cpp |
Covers issue 15042’s false positive. |
Makefile |
Adds the new header dependency. |
oss-fuzz/Makefile |
Updates fuzz-build dependencies. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| if (op == "+") { | ||
| result = edge + k; | ||
| } else if (op == "-") { | ||
| increasing = rangeIsLhs; | ||
| result = rangeIsLhs ? edge - k : k - edge; |
| const ValueFlow::Value* sizeValue = pm->getValue(value.tokvalue->exprId()); | ||
| if (sizeValue && sizeValue->isContainerSizeValue()) | ||
| return *sizeValue; | ||
| if (sizeValue && sizeValue->isContainerSizeValue()) { | ||
| sizes.push_back(*sizeValue); | ||
| break; | ||
| } |
| // Shift every value of the variable; bounds and impossible values move along | ||
| for (ValueFlow::Value& v : lhs) { | ||
| if (expr->str() == "++") | ||
| v.intvalue++; | ||
| else | ||
| v.intvalue--; |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
| bool increasing = true; | ||
| MathLib::bigint result = 0; | ||
| if (op == "+") { | ||
| result = edge + k; |
There was a problem hiding this comment.
This is an AI review take it with a grain of salt. Please feel free to reject it by clicking on Resolve button
edge + k (and edge - k / k - edge below) can overflow the analyzer's own MathLib::bigint. isSaturated() only rejects values that are exactly at the limit. I confirmed it with a UBSan build of programmemory.cpp from this branch:
void n(long long x) {
int* p = 0;
if (x > 0x7ffffffffffffff0LL) {
if (x + 100 < 0) p = (int*)&x;
*p = 1;
}
}lib/programmemory.cpp:882:16: runtime error: signed integer overflow: 100 + 9223372036854775793 cannot be represented in type 'long long int'
Maybe return unknown() on overflow here, as the * branch already does via multiplyOverflows(). ceilDiv/floorDiv in valueflow.cpp have a similar corner case, LLONG_MIN / -1, which happens when rangeEdge() adds or subtracts 1 next to the limit.
For the false-positive question, I compared this branch against its merge-base on lib/, cli/, gui/, test/cfg/, samples/ and simplecpp (--enable=style,warning,performance,portability --inconclusive). The output was identical. I also tried a set of nested-condition cases with the p = 0; if (cond) p = &x; *p = 1; pattern, including unsigned wrap-around, narrow types and %/&. I found no new false positives. The only new warnings were correct "Possible null pointer dereference" for x % 4 == 0, (x & 1) == 0 and x - y == 0 under x > 3, where the old code assumed x == 4.


This refactors
ProgramMemoryso it can store a list of values.A condition such as
x > 3was recorded as the possible value 4 with a lower bound, and the executor then used it as ifxwere exactly 4. Nested conditions were decided wrongly, so branches were skipped and values leaked past or were lost before them. This what lead to the FP in 15042.Now it can understand these constraints better. As new values are added, it is resolved similar to
Token::addValue.