Skip to content

Commit a216f27

Browse files
author
Your Name
committed
Update to handle the vars in execute
1 parent c111d2e commit a216f27

6 files changed

Lines changed: 133 additions & 34 deletions

File tree

lib/analyzer.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,10 @@ struct Analyzer {
158158
ContainerEmpty = (1 << 2),
159159
// Do not record the program state at the branch boundaries. Used when assuming a
160160
// condition before the branch is traversed, where those states would be premature.
161+
// When this is not set the branch has already been traversed and control is continuing
162+
// past it, so the assumed state is anchored at the block's end (see the analyzer's
163+
// assume()): this keeps assumptions on variables modified inside the block from being
164+
// discarded as "modified" once control leaves it.
161165
NoState = (1 << 3),
162166
};
163167
};

lib/programmemory.cpp

Lines changed: 49 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -262,26 +262,26 @@ ProgramMemory::Map::iterator ProgramMemory::find(nonneg int exprid)
262262
return mValues->find(ExprIdToken::create(exprid));
263263
}
264264

265-
static ValueFlow::Value execute(const Token* expr, ProgramMemory& pm, const Settings& settings);
265+
static ValueFlow::Value execute(const Token* expr, ProgramMemory& pm, const Settings& settings, const ProgramMemory::Map& vars = {});
266266

267-
static bool evaluateCondition(MathLib::bigint r, const Token* condition, ProgramMemory& pm, const Settings& settings)
267+
static bool evaluateCondition(MathLib::bigint r, const Token* condition, ProgramMemory& pm, const Settings& settings, const ProgramMemory::Map& vars = {})
268268
{
269269
if (!condition)
270270
return false;
271271
MathLib::bigint result = 0;
272272
bool error = false;
273-
execute(condition, pm, &result, &error, settings);
273+
execute(condition, pm, &result, &error, settings, vars);
274274
return !error && result == r;
275275
}
276276

277-
bool conditionIsFalse(const Token* condition, ProgramMemory pm, const Settings& settings)
277+
bool conditionIsFalse(const Token* condition, ProgramMemory pm, const Settings& settings, const ProgramMemory::Map& vars)
278278
{
279-
return evaluateCondition(0, condition, pm, settings);
279+
return evaluateCondition(0, condition, pm, settings, vars);
280280
}
281281

282-
bool conditionIsTrue(const Token* condition, ProgramMemory pm, const Settings& settings)
282+
bool conditionIsTrue(const Token* condition, ProgramMemory pm, const Settings& settings, const ProgramMemory::Map& vars)
283283
{
284-
return evaluateCondition(1, condition, pm, settings);
284+
return evaluateCondition(1, condition, pm, settings, vars);
285285
}
286286

287287
static bool frontIs(const std::vector<MathLib::bigint>& v, bool i)
@@ -443,7 +443,7 @@ static void fillProgramMemoryFromAssignments(ProgramMemory& pm, const Token* tok
443443
if (!pm.hasValue(vartok->exprId())) {
444444
const Token* valuetok = tok2->astOperand2();
445445
ProgramMemory local = state;
446-
pm.setValue(vartok, execute(valuetok, local, settings));
446+
pm.setValue(vartok, execute(valuetok, local, settings, vars));
447447
}
448448
}
449449
} else if (Token::simpleMatch(tok2, ")") && tok2->link() &&
@@ -547,19 +547,21 @@ void ProgramMemoryState::addState(const Token* tok, const ProgramMemory::Map& va
547547
replace(std::move(local), tok);
548548
}
549549

550-
void ProgramMemoryState::assume(const Token* tok, bool b, bool isEmpty)
550+
void ProgramMemoryState::assume(const Token* tok, bool b, bool isEmpty, const Token* origin)
551551
{
552552
ProgramMemory pm = state;
553553
if (isEmpty)
554554
pm.setContainerSizeValue(tok, 0, b);
555555
else
556556
programMemoryParseCondition(pm, tok, nullptr, settings, b);
557-
const Token* origin = tok;
558-
const Token* top = tok->astTop();
559-
if (Token::Match(top->previous(), "for|while|if (") && !Token::simpleMatch(tok->astParent(), "?")) {
560-
origin = top->link()->next();
561-
if (!b && origin->link()) {
562-
origin = origin->link();
557+
if (!origin) {
558+
origin = tok;
559+
const Token* top = tok->astTop();
560+
if (Token::Match(top->previous(), "for|while|if (") && !Token::simpleMatch(tok->astParent(), "?")) {
561+
origin = top->link()->next();
562+
if (!b && origin->link()) {
563+
origin = origin->link();
564+
}
563565
}
564566
}
565567
replace(std::move(pm), origin);
@@ -1316,6 +1318,10 @@ namespace {
13161318
struct Executor {
13171319
ProgramMemory* pm;
13181320
const Settings& settings;
1321+
// The values that are being tracked by the forward/reverse analysis. These take precedence
1322+
// over what is stored in the program memory: a tracked value is the authoritative current
1323+
// value of its expression, so any cached value that depends on it must be re-evaluated.
1324+
const ProgramMemory::Map* vars = nullptr;
13191325
int fdepth = 4;
13201326
int depth = 10;
13211327

@@ -1324,6 +1330,25 @@ namespace {
13241330
assert(pm != nullptr);
13251331
}
13261332

1333+
// Is the tracked value for this expression available?
1334+
const ValueFlow::Value* getTrackedValue(const Token* expr) const {
1335+
if (!vars || expr->exprId() == 0)
1336+
return nullptr;
1337+
const auto it = vars->find(ExprIdToken::create(expr->exprId()));
1338+
return it == vars->end() ? nullptr : &it->second;
1339+
}
1340+
1341+
// Does the expression read a tracked value? If so, any value cached in the program memory
1342+
// for it may be stale (the tracked value may have changed since it was cached), so it must
1343+
// be re-evaluated rather than served from the cache.
1344+
bool dependsOnTrackedValue(const Token* expr) const {
1345+
if (!vars || vars->empty())
1346+
return false;
1347+
return findAstNode(expr, [&](const Token* tok) {
1348+
return getTrackedValue(tok) != nullptr;
1349+
}) != nullptr;
1350+
}
1351+
13271352
static ValueFlow::Value unknown() {
13281353
return ValueFlow::Value::unknown();
13291354
}
@@ -1622,7 +1647,10 @@ namespace {
16221647
}
16231648
return execute(expr->astOperand1());
16241649
}
1625-
if (expr->exprId() > 0 && pm->hasValue(expr->exprId())) {
1650+
// A tracked value is the authoritative current value of its expression.
1651+
if (const ValueFlow::Value* tracked = getTrackedValue(expr))
1652+
return *tracked;
1653+
if (expr->exprId() > 0 && pm->hasValue(expr->exprId()) && !dependsOnTrackedValue(expr)) {
16261654
ValueFlow::Value result = utils::as_const(*pm).at(expr->exprId());
16271655
if (result.isImpossible() && result.isIntValue() && result.intvalue == 0 && isUsedAsBool(expr, settings)) {
16281656
result.intvalue = !result.intvalue;
@@ -1815,9 +1843,10 @@ namespace {
18151843
};
18161844
} // namespace
18171845

1818-
static ValueFlow::Value execute(const Token* expr, ProgramMemory& pm, const Settings& settings)
1846+
static ValueFlow::Value execute(const Token* expr, ProgramMemory& pm, const Settings& settings, const ProgramMemory::Map& vars)
18191847
{
18201848
Executor ex{&pm, settings};
1849+
ex.vars = &vars;
18211850
return ex.execute(expr);
18221851
}
18231852

@@ -1907,9 +1936,10 @@ void execute(const Token* expr,
19071936
ProgramMemory& programMemory,
19081937
MathLib::bigint* result,
19091938
bool* error,
1910-
const Settings& settings)
1939+
const Settings& settings,
1940+
const ProgramMemory::Map& vars)
19111941
{
1912-
ValueFlow::Value v = execute(expr, programMemory, settings);
1942+
ValueFlow::Value v = execute(expr, programMemory, settings, vars);
19131943
if (!v.isIntValue() || v.isImpossible()) {
19141944
if (error)
19151945
*error = true;

lib/programmemory.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ struct ProgramMemoryState {
171171

172172
void addState(const Token* tok, const ProgramMemory::Map& vars);
173173

174-
void assume(const Token* tok, bool b, bool isEmpty = false);
174+
void assume(const Token* tok, bool b, bool isEmpty = false, const Token* origin = nullptr);
175175

176176
void removeModifiedVars(const Token* tok);
177177

@@ -184,21 +184,24 @@ void execute(const Token* expr,
184184
ProgramMemory& programMemory,
185185
MathLib::bigint* result,
186186
bool* error,
187-
const Settings& settings);
187+
const Settings& settings,
188+
const ProgramMemory::Map& vars = {});
188189

189190
/**
190191
* Is condition always false when variable has given value?
191192
* \param condition top ast token in condition
192193
* \param pm program memory
194+
* \param vars optional tracked values that take precedence over the program memory
193195
*/
194-
bool conditionIsFalse(const Token* condition, ProgramMemory pm, const Settings& settings);
196+
bool conditionIsFalse(const Token* condition, ProgramMemory pm, const Settings& settings, const ProgramMemory::Map& vars = {});
195197

196198
/**
197199
* Is condition always true when variable has given value?
198200
* \param condition top ast token in condition
199201
* \param pm program memory
202+
* \param vars optional tracked values that take precedence over the program memory
200203
*/
201-
bool conditionIsTrue(const Token* condition, ProgramMemory pm, const Settings& settings);
204+
bool conditionIsTrue(const Token* condition, ProgramMemory pm, const Settings& settings, const ProgramMemory::Map& vars = {});
202205

203206
/**
204207
* Get program memory by looking backwards from given token.

lib/vf_analyzers.cpp

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -679,15 +679,19 @@ struct ValueFlowAnalyzer : Analyzer {
679679
return {v->intvalue};
680680
std::vector<MathLib::bigint> result;
681681
ProgramMemory pm = getProgramMemoryFunc();
682+
// The tracked values are authoritative: pass them so a value cached in the program memory
683+
// that depends on a tracked value (e.g. 'h(p)' after 'p' was reassigned) is re-evaluated
684+
// rather than served stale.
685+
const ProgramState vars = getProgramState();
682686
if (Token::Match(tok, "&&|%oror%")) {
683-
if (conditionIsTrue(tok, pm, getSettings()))
687+
if (conditionIsTrue(tok, pm, getSettings(), vars))
684688
result.push_back(1);
685-
if (conditionIsFalse(tok, std::move(pm), getSettings()))
689+
if (conditionIsFalse(tok, std::move(pm), getSettings(), vars))
686690
result.push_back(0);
687691
} else {
688692
MathLib::bigint out = 0;
689693
bool error = false;
690-
execute(tok, pm, &out, &error, getSettings());
694+
execute(tok, pm, &out, &error, getSettings(), vars);
691695
if (!error)
692696
result.push_back(out);
693697
}
@@ -724,22 +728,36 @@ struct ValueFlowAnalyzer : Analyzer {
724728
}
725729

726730
void assume(const Token* tok, bool state, unsigned int flags) override {
727-
// Update program state
728-
pms.removeModifiedVars(tok);
729-
pms.addState(tok, getProgramState());
730-
pms.assume(tok, state, flags & Assume::ContainerEmpty);
731-
732731
bool isCondBlock = false;
733732
const Token* parent = tok->astParent();
734733
if (parent) {
735734
isCondBlock = Token::Match(parent->previous(), "if|while (");
736735
}
737736

738-
if (isCondBlock && !(flags & Assume::NoState)) {
739-
const Token* startBlock = parent->link()->next();
737+
const Token* startBlock = nullptr;
738+
const Token* endBlock = nullptr;
739+
if (isCondBlock) {
740+
startBlock = parent->link()->next();
740741
if (Token::simpleMatch(startBlock, ";") && Token::simpleMatch(parent->tokAt(-2), "} while ("))
741742
startBlock = parent->linkAt(-2);
742-
const Token* endBlock = startBlock->link();
743+
endBlock = startBlock->link();
744+
}
745+
746+
// NoState is set only for the pre-traversal assume; without it the 'then' block has already
747+
// been traversed and control is leaving it, so anchor the assumed state at the end of the
748+
// block rather than at the condition. Assumptions about variables modified inside the block
749+
// (e.g. an 'if' that narrows a value computed in the block) then survive past it, instead of
750+
// being discarded because the variable was "modified" since the condition was evaluated.
751+
const bool scopeEnd = !(flags & Assume::NoState) && state && endBlock;
752+
const Token* anchor = scopeEnd ? endBlock : tok;
753+
const Token* origin = scopeEnd ? endBlock : nullptr;
754+
755+
// Update program state
756+
pms.removeModifiedVars(anchor);
757+
pms.addState(anchor, getProgramState());
758+
pms.assume(tok, state, flags & Assume::ContainerEmpty, origin);
759+
760+
if (isCondBlock && !(flags & Assume::NoState) && !scopeEnd) {
743761
if (state) {
744762
pms.removeModifiedVars(endBlock);
745763
pms.addState(endBlock->previous(), getProgramState());

test/testnullpointer.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2450,6 +2450,9 @@ class TestNullPointer : public TestFixture {
24502450

24512451
void nullpointer77()
24522452
{
2453+
// No warning: 'i' is passed to the unknown function 'h' in the same condition that guards the
2454+
// dereference. 'h' may validate the pointer (e.g. return false for null), so '*i' can be safe
2455+
// - this is the common "if (check(p) && p->...)" pattern, so we must not assume 'i' is null.
24532456
check("bool h(int*);\n"
24542457
"void f(int* i) {\n"
24552458
" int* i = nullptr;\n"
@@ -2465,6 +2468,8 @@ class TestNullPointer : public TestFixture {
24652468
"}\n");
24662469
ASSERT_EQUALS("", errout_str());
24672470

2471+
// Likewise here, even though 'i' is null when the first 'h(i)' was true: the second 'h(i)' is an
2472+
// independent call that may validate 'i', so '*i' is not necessarily a null dereference.
24682473
check("bool h(int*);\n"
24692474
"void f(int* x) {\n"
24702475
" int* i = x;\n"

test/testuninitvar.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5614,6 +5614,45 @@ class TestUninitVar : public TestFixture {
56145614
"}");
56155615
ASSERT_EQUALS("[test.cpp:18:9] -> [test.cpp:12:13] -> [test.cpp:8:15]: (warning) Uninitialized variable: s->flag [uninitvar]\n", errout_str());
56165616

5617+
// A value narrowed by a nested condition (dimensions < 1 here) must survive the enclosing
5618+
// 'if' so a later correlated condition can be resolved: dimensions == 1 is then false, the
5619+
// function does not return, and the uninitialized read is reached.
5620+
valueFlowUninit("unsigned int g();\n"
5621+
"void f(bool a) {\n"
5622+
" unsigned int dimensions = 0;\n"
5623+
" bool mightBeLarger;\n"
5624+
" if (a) {\n"
5625+
" dimensions = g();\n"
5626+
" if (dimensions >= 1)\n"
5627+
" mightBeLarger = false;\n"
5628+
" } else {\n"
5629+
" mightBeLarger = false;\n"
5630+
" }\n"
5631+
" if (dimensions == 1)\n"
5632+
" return;\n"
5633+
" if (!mightBeLarger) {}\n"
5634+
"}");
5635+
ASSERT_EQUALS("[test.cpp:5:9] -> [test.cpp:7:24] -> [test.cpp:14:10]: (warning) Uninitialized variable: mightBeLarger [uninitvar]\n", errout_str());
5636+
5637+
// Same shape, but the later condition (dimensions == 0) is implied by the narrowing, so the
5638+
// early return fires on the uninitialized path and there must be no warning.
5639+
valueFlowUninit("unsigned int g();\n"
5640+
"void f(bool a) {\n"
5641+
" unsigned int dimensions = 0;\n"
5642+
" bool mightBeLarger;\n"
5643+
" if (a) {\n"
5644+
" dimensions = g();\n"
5645+
" if (dimensions >= 1)\n"
5646+
" mightBeLarger = false;\n"
5647+
" } else {\n"
5648+
" mightBeLarger = false;\n"
5649+
" }\n"
5650+
" if (dimensions == 0)\n"
5651+
" return;\n"
5652+
" if (!mightBeLarger) {}\n"
5653+
"}");
5654+
ASSERT_EQUALS("", errout_str());
5655+
56175656
// Ticket #2207 - False negative
56185657
valueFlowUninit("void foo() {\n"
56195658
" int a;\n"

0 commit comments

Comments
 (0)