Skip to content

Commit a0d20a2

Browse files
author
Your Name
committed
Update to use stopOnCondition
1 parent 49f7cd2 commit a0d20a2

3 files changed

Lines changed: 106 additions & 5 deletions

File tree

lib/forwardanalyzer.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -782,7 +782,7 @@ namespace {
782782
} else if (thenBranch.check) {
783783
return Break();
784784
} else {
785-
if (analyzer->isConditional() && stopUpdates())
785+
if (stopOnCondition(condTok) && stopUpdates())
786786
return Break(Analyzer::Terminate::Conditional);
787787
analyzer->assume(condTok, false);
788788
}

lib/vf_analyzers.cpp

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1198,18 +1198,44 @@ struct SingleValueFlowAnalyzer : ValueFlowAnalyzer {
11981198

11991199
bool stopOnCondition(const Token* condTok) const override
12001200
{
1201-
if (value.isNonValue())
1202-
return false;
12031201
if (value.isImpossible())
12041202
return false;
1205-
if (isConditional() && !value.isKnown())
1203+
// Lifetime values must keep flowing through conditions to detect dangling dereferences on every path.
1204+
if (value.isLifetimeValue())
1205+
return false;
1206+
// A value carrying the explicit 'conditional' flag (e.g. an uninitialized value, or a value lowered
1207+
// to possible after a branch that modifies the variable) can depend on conditions that don't mention
1208+
// the variable itself, so stop at any subsequent condition to stay conservative.
1209+
if (value.conditional && !value.isKnown())
12061210
return true;
1207-
if (value.isSymbolicValue())
1211+
if (value.isNonValue())
12081212
return false;
1213+
if (value.isSymbolicValue())
1214+
return isConditional() && !value.isKnown();
1215+
// The value may still be conditional via the originating 'condition' token (e.g. a possible null
1216+
// pointer after 'if (p && ...)'). Such a value may keep flowing past a later condition, but only
1217+
// when that condition actually refers to the tracked value: then cppcheck can reason about how the
1218+
// condition constrains it. If the value is not mentioned, a correlation that cppcheck cannot follow
1219+
// during forward analysis (e.g. 'bool ok = (p != nullptr); if (!ok) return;') could make a later
1220+
// dereference safe, so stop conservatively to avoid false positives.
1221+
if (value.condition && !value.isKnown() && !conditionReferencesValue(condTok))
1222+
return true;
12091223
ConditionState cs = analyzeCondition(condTok);
12101224
return cs.isUnknownDependent();
12111225
}
12121226

1227+
// Does the condition mention the tracked value, either directly or through a symbolic alias?
1228+
bool conditionReferencesValue(const Token* condTok) const
1229+
{
1230+
return findAstNode(condTok, [&](const Token* tok) {
1231+
if (match(tok))
1232+
return true;
1233+
return std::any_of(tok->values().cbegin(), tok->values().cend(), [&](const ValueFlow::Value& v) {
1234+
return v.isSymbolicValue() && !v.isImpossible() && v.tokvalue && match(v.tokvalue);
1235+
});
1236+
}) != nullptr;
1237+
}
1238+
12131239
bool updateScope(const Token* endBlock, bool /*modified*/) const override {
12141240
const Scope* scope = endBlock->scope();
12151241
if (!scope)

test/testnullpointer.cpp

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ class TestNullPointer : public TestFixture {
143143
TEST_CASE(nullpointer103);
144144
TEST_CASE(nullpointer104); // #13881
145145
TEST_CASE(nullpointer105); // #13861
146+
TEST_CASE(nullpointer106); // #13682
147+
TEST_CASE(nullpointer107); // #13682 (no false positive past unrelated conditions)
146148
TEST_CASE(nullpointer_addressOf); // address of
147149
TEST_CASE(nullpointerSwitch); // #2626
148150
TEST_CASE(nullpointer_cast); // #4692
@@ -2966,6 +2968,79 @@ class TestNullPointer : public TestFixture {
29662968
ASSERT_EQUALS("", errout_str());
29672969
}
29682970

2971+
void nullpointer106() // #13682
2972+
{
2973+
// An unrelated condition between the null check and the dereference must not stop the analysis
2974+
check("struct S {\n"
2975+
" bool b;\n"
2976+
" bool f() const;\n"
2977+
"};\n"
2978+
"void f(const S* p, const S* o) {\n"
2979+
" const S* p1 = p;\n"
2980+
" if (p1 && p1->f())\n"
2981+
" return;\n"
2982+
" if (p == o)\n"
2983+
" return;\n"
2984+
" if (p1->b) {}\n"
2985+
"}\n");
2986+
ASSERT_EQUALS(
2987+
"[test.cpp:7:9] -> [test.cpp:11:9]: (warning) Either the condition 'p1' is redundant or there is possible null pointer dereference: p1. [nullPointerRedundantCheck]\n",
2988+
errout_str());
2989+
}
2990+
2991+
void nullpointer107() // #13682 - don't flow past a condition that doesn't reference the pointer
2992+
{
2993+
// The pointer's null-ness is cached in a boolean; the guard 'if (!ok)' makes the deref safe but
2994+
// cppcheck cannot follow that correlation during forward analysis -> must not warn (no false positive)
2995+
check("struct S { void g(); bool f() const; };\n"
2996+
"void f(S* p) {\n"
2997+
" bool ok = (p != nullptr);\n"
2998+
" if (p && p->f())\n"
2999+
" return;\n"
3000+
" if (!ok)\n"
3001+
" return;\n"
3002+
" p->g();\n"
3003+
"}\n");
3004+
ASSERT_EQUALS("", errout_str());
3005+
3006+
// A guard on an unrelated boolean (that the caller may use to imply validity) is not something
3007+
// cppcheck can follow -> stay conservative and do not warn
3008+
check("struct S { void g(); bool f() const; };\n"
3009+
"void f(S* p, bool valid) {\n"
3010+
" S* p1 = p;\n"
3011+
" if (p1 && p1->f())\n"
3012+
" return;\n"
3013+
" if (!valid)\n"
3014+
" return;\n"
3015+
" p1->g();\n"
3016+
"}\n");
3017+
ASSERT_EQUALS("", errout_str());
3018+
3019+
// A guard on a different pointer must not be assumed to constrain this one
3020+
check("struct S { void g(); bool f() const; };\n"
3021+
"void f(S* p, S* q) {\n"
3022+
" S* p1 = p;\n"
3023+
" if (p1 && p1->f())\n"
3024+
" return;\n"
3025+
" if (!q)\n"
3026+
" return;\n"
3027+
" p1->g();\n"
3028+
"}\n");
3029+
ASSERT_EQUALS("", errout_str());
3030+
3031+
// A direct null guard on the pointer (or its alias) is still handled and must not warn
3032+
check("struct S { void g(); bool f() const; };\n"
3033+
"void f(S* p) {\n"
3034+
" S* p1 = p;\n"
3035+
" if (p1 && p1->f())\n"
3036+
" return;\n"
3037+
" if (!p)\n"
3038+
" return;\n"
3039+
" p1->g();\n"
3040+
"}\n");
3041+
ASSERT_EQUALS("", errout_str());
3042+
}
3043+
29693044
void nullpointer_addressOf() { // address of
29703045
check("void f() {\n"
29713046
" struct X *x = 0;\n"

0 commit comments

Comments
 (0)