Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions lib/analyzer.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,13 @@ struct Analyzer {
struct Assume {
enum Flags : std::uint8_t {
None = 0,
Quiet = (1 << 0),
Absolute = (1 << 1),
ContainerEmpty = (1 << 2),
Quiet = (1u << 0),
Absolute = (1u << 1),
ContainerEmpty = (1u << 2),
// The branch this condition guards is not traversed yet (a separate path walks it), so
// the assume must not record the program state at the branch boundaries - they would be
// premature. When unset, the branch has been traversed and control is leaving it.
Pending = (1u << 3),
};
};

Expand Down
2 changes: 2 additions & 0 deletions lib/astutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2230,6 +2230,8 @@ bool isEscapeFunction(const Token* ftok, const Library& library)
{
if (!Token::Match(ftok, "%name% ("))
return false;
if (Token::Match(ftok, "exit|abort"))
return true;
Comment on lines +2233 to +2234

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is also terminate() but it feels like should already be handled via the noreturn handling below (i.e. library configuration).

const Function* function = ftok->function();
if (function) {
if (function->isEscapeFunction())
Expand Down
225 changes: 121 additions & 104 deletions lib/forwardanalyzer.cpp

Large diffs are not rendered by default.

209 changes: 144 additions & 65 deletions lib/programmemory.cpp

Large diffs are not rendered by default.

38 changes: 34 additions & 4 deletions lib/programmemory.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <map>
#include <memory>
#include <string>
#include <tuple>
#include <unordered_map>
#include <utility>
#include <vector>
Expand Down Expand Up @@ -161,20 +162,40 @@
};

struct ProgramMemoryState {
struct ChangedKeyHash {
std::size_t operator()(const std::tuple<const Token*, const Token*, const Token*>& t) const
{
const std::hash<const Token*> h;
std::size_t seed = h(std::get<0>(t));

Check warning

Code scanning / Cppcheck Premium

Use meaningful symbolic constants to represent literal values. Warning

Use meaningful symbolic constants to represent literal values.
seed ^= h(std::get<1>(t)) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
seed ^= h(std::get<2>(t)) + 0x9e3779b9 + (seed << 6) + (seed >> 2);

Check warning

Code scanning / Cppcheck Premium

Use meaningful symbolic constants to represent literal values. Warning

Use meaningful symbolic constants to represent literal values.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
return seed;
}
};
using ChangedCache =
std::unordered_map<std::tuple<const Token*, const Token*, const Token*>, const Token*, ChangedKeyHash>;
// The token modifying expr between start and end, or nullptr.
using FindChangedFn = std::function<const Token*(const Token* expr, const Token* start, const Token* end)>;

ProgramMemory state;
std::map<nonneg int, const Token*> origins;
const Settings& settings;
// Memoized findExpressionChanged() pre-filter; structural, so never invalidated.
std::shared_ptr<ChangedCache> changedCache;

explicit ProgramMemoryState(const Settings& s);

void replace(ProgramMemory pm, const Token* origin = nullptr);

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

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

void removeModifiedVars(const Token* tok);

// A findExpressionChanged() closure memoized in changedCache
FindChangedFn getCachedFindExpressionChanged(bool skipDeadCode) const;

ProgramMemory get(const Token* tok, const Token* ctx, const ProgramMemory::Map& vars) const;
};

Expand All @@ -184,21 +205,30 @@
ProgramMemory& programMemory,
MathLib::bigint* result,
bool* error,
const Settings& settings);
const Settings& settings,
const ProgramMemory::Map& vars = {});

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

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

/**
* Get program memory by looking backwards from given token.
Expand Down
4 changes: 4 additions & 0 deletions lib/settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@
vfOptions.maxIfCount = 100;
vfOptions.doConditionExpressionAnalysis = false;
vfOptions.maxForwardBranches = 4;
vfOptions.maxForwardConditionForkDepth = 0;
vfOptions.maxIterations = 1;
}
else if (level == CheckLevel::normal) {
Expand All @@ -344,6 +345,7 @@
vfOptions.maxIfCount = 100;
vfOptions.doConditionExpressionAnalysis = false;
vfOptions.maxForwardBranches = 4;
vfOptions.maxForwardConditionForkDepth = 1;
}
else if (level == CheckLevel::exhaustive) {
// Checking can take a little while. ~ 10 times slower than normal analysis is OK.
Expand All @@ -352,6 +354,8 @@
vfOptions.maxSubFunctionArgs = 256;
vfOptions.doConditionExpressionAnalysis = true;
vfOptions.maxForwardBranches = -1;
vfOptions.maxForwardConditionForkDepth = 4;
vfOptions.maxForwardConditionForks = 256;

Check warning

Code scanning / Cppcheck Premium

Use meaningful symbolic constants to represent literal values. Warning

Use meaningful symbolic constants to represent literal values.
}
}

Expand Down
9 changes: 9 additions & 0 deletions lib/settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,15 @@
/** @brief Maximum performed forward branches */
int maxForwardBranches = -1;

/** @brief Maximum depth of nested condition-fork continuations in the forward analyzer.
Bounds the exponential fan-out of carrying condition state forward at exhaustive level (where
maxForwardBranches is unlimited); past it the forward analysis continues on a single linear path
without skipping any branch. 0 disables forking (linear); a negative value means unlimited. */
int maxForwardConditionForkDepth = 4;

/** @brief Maximum total condition-fork continuations in one forward traversal. */
int maxForwardConditionForks = 256;

Check warning

Code scanning / Cppcheck Premium

Use meaningful symbolic constants to represent literal values. Warning

Use meaningful symbolic constants to represent literal values.

/** @brief Maximum performed alignof recursion */
int maxAlignOfRecursion = 100;

Expand Down
61 changes: 39 additions & 22 deletions lib/vf_analyzers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -678,16 +678,20 @@ struct ValueFlowAnalyzer : Analyzer {
if (const ValueFlow::Value* v = tok->getKnownValue(ValueFlow::Value::ValueType::INT))
return {v->intvalue};
std::vector<MathLib::bigint> result;
ProgramMemory pm = getProgramMemoryFunc();
// Pass the tracked values so a cached program-memory value that depends on one (e.g. 'h(p)'
// after 'p' was reassigned) is re-evaluated rather than served stale. The memory is built
// from the same state, so compute it once and hand it to the builder.
const ProgramState vars = getProgramState();
ProgramMemory pm = getProgramMemoryFunc(vars);
if (Token::Match(tok, "&&|%oror%")) {
if (conditionIsTrue(tok, pm, getSettings()))
if (conditionIsTrue(tok, pm, getSettings(), vars))
result.push_back(1);
if (conditionIsFalse(tok, std::move(pm), getSettings()))
if (conditionIsFalse(tok, std::move(pm), getSettings(), vars))
result.push_back(0);
} else {
MathLib::bigint out = 0;
bool error = false;
execute(tok, pm, &out, &error, getSettings());
execute(tok, pm, &out, &error, getSettings(), vars);
if (!error)
result.push_back(out);
}
Expand All @@ -696,16 +700,16 @@ struct ValueFlowAnalyzer : Analyzer {

std::vector<MathLib::bigint> evaluateInt(const Token* tok) const
{
return evaluateInt(tok, [&] {
return ProgramMemory{getProgramState()};
return evaluateInt(tok, [](const ProgramState& vars) {
return ProgramMemory{vars};
});
}

std::vector<MathLib::bigint> evaluate(Evaluate e, const Token* tok, const Token* ctx = nullptr) const override
{
if (e == Evaluate::Integral) {
return evaluateInt(tok, [&] {
return pms.get(tok, ctx, getProgramState());
return evaluateInt(tok, [&](const ProgramState& vars) {
return pms.get(tok, ctx, vars);
});
}
if (e == Evaluate::ContainerEmpty) {
Expand All @@ -723,30 +727,43 @@ struct ValueFlowAnalyzer : Analyzer {
return {};
}

void assume(const Token* tok, bool state, unsigned int flags) override {
// Update program state
pms.removeModifiedVars(tok);
pms.addState(tok, getProgramState());
pms.assume(tok, state, flags & Assume::ContainerEmpty);

void assume(const Token* tok, bool state, unsigned int flags) override
{
bool isCondBlock = false;
const Token* parent = tok->astParent();
if (parent) {
isCondBlock = Token::Match(parent->previous(), "if|while (");
}

const Token* endBlock = nullptr;
if (isCondBlock) {
const Token* startBlock = parent->link()->next();
if (Token::simpleMatch(startBlock, ";") && Token::simpleMatch(parent->tokAt(-2), "} while ("))
startBlock = parent->linkAt(-2);
const Token* endBlock = startBlock->link();
if (state) {
pms.removeModifiedVars(endBlock);
pms.addState(endBlock->previous(), getProgramState());
} else {
if (Token::simpleMatch(endBlock, "} else {"))
pms.addState(endBlock->linkAt(2)->previous(), getProgramState());
}
endBlock = startBlock->link();
}

// Without Pending the 'then' block has been traversed and control is leaving it, so anchor
// the assumed state at the block end instead of the condition. That keeps assumptions on
// variables modified inside the block (e.g. an 'if' narrowing a value computed there) from
// being discarded as "modified" once control leaves the block.
const bool scopeEnd = !(flags & Assume::Pending) && state && endBlock;
const Token* anchor = scopeEnd ? endBlock : tok;
const Token* origin = scopeEnd ? endBlock : nullptr;

// Update program state
pms.removeModifiedVars(anchor);
pms.addState(anchor, getProgramState());
pms.assume(tok, state, flags & Assume::ContainerEmpty, origin);

// The false path (the true path uses scopeEnd above): record the assumed state where control
// continues - the end of the else block, or the closing brace when there is no else - so it
// reaches the enclosing scope.
if (isCondBlock && !(flags & Assume::Pending) && !state) {
if (Token::simpleMatch(endBlock, "} else {"))
pms.addState(endBlock->linkAt(2)->previous(), getProgramState());
else
pms.addState(endBlock, getProgramState());
}

if (!(flags & Assume::Quiet)) {
Expand Down
4 changes: 3 additions & 1 deletion test/testautovariables.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4848,7 +4848,9 @@ class TestAutoVariables : public TestFixture {
" return *iPtr;\n"
" return 0;\n"
"}");
ASSERT_EQUALS("[test.cpp:5:16] -> [test.cpp:4:13] -> [test.cpp:8:17]: (error) Using pointer to local variable 'x' that is out of scope. [invalidLifetime]\n", errout_str());
ASSERT_EQUALS(
"[test.cpp:5:16] -> [test.cpp:7:10] -> [test.cpp:4:13] -> [test.cpp:8:17]: (error) Using pointer to local variable 'x' that is out of scope. [invalidLifetime]\n",
errout_str());

// #11753
check("int main(int argc, const char *argv[]) {\n"
Expand Down
24 changes: 24 additions & 0 deletions test/testcondition.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4911,6 +4911,30 @@ class TestCondition : public TestFixture {
ASSERT_EQUALS("[test.cpp:3:10]: (style) Condition 'b()' is always false [knownConditionTrueFalse]\n"
"[test.cpp:4:9]: (style) Condition '!b()' is always true [knownConditionTrueFalse]\n",
errout_str());

check("int g();\n" // a value modified inside a nested branch must be lowered to possible
"void f(int outer, int inner) {\n"
" int bits = 0;\n"
" if (outer) {\n"
" if (inner == 1)\n"
" bits = g();\n"
" }\n"
" if (bits > 0) {}\n"
"}\n");
ASSERT_EQUALS("", errout_str());

check("int g();\n" // the modifying branch has an escaping sibling - still must be lowered
"void f(int t, int u) {\n"
" int v = 0;\n"
" if (t) {\n"
" if (u == 2)\n"
" v = g();\n"
" else\n"
" return;\n"
" }\n"
" if (v > 0) {}\n"
"}\n");
ASSERT_EQUALS("", errout_str());
}

void alwaysTrueSymbolic()
Expand Down
5 changes: 5 additions & 0 deletions test/testnullpointer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2450,6 +2450,9 @@ class TestNullPointer : public TestFixture {

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

// Likewise here, even though 'i' is null when the first 'h(i)' was true: the second 'h(i)' is an
// independent call that may validate 'i', so '*i' is not necessarily a null dereference.
check("bool h(int*);\n"
"void f(int* x) {\n"
" int* i = x;\n"
Expand Down
5 changes: 3 additions & 2 deletions test/testother.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11491,8 +11491,9 @@ class TestOther : public TestFixture {
" x = a + b;\n"
" return x;\n"
"}\n");
ASSERT_EQUALS("[test.cpp:2:11] -> [test.cpp:4:9]: (style) Variable 'x' is assigned an expression that holds the same value. [redundantAssignment]\n",
errout_str());
ASSERT_EQUALS(
"[test.cpp:2:11] -> [test.cpp:3:9] -> [test.cpp:4:9]: (style) Variable 'x' is assigned an expression that holds the same value. [redundantAssignment]\n",
errout_str());
}

void varFuncNullUB() { // #4482
Expand Down
4 changes: 3 additions & 1 deletion test/teststl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,9 @@ class TestStl : public TestFixture {
" if(b) ++x;\n"
" return s[x];\n"
"}");
ASSERT_EQUALS("[test.cpp:5:13]: error: Out of bounds access in 's[x]', if 's' size is 6 and 'x' is 6 [containerOutOfBounds]\n", errout_str());
ASSERT_EQUALS(
"[test.cpp:5:13]: error: Out of bounds access in 's[x]', if 's' size is 6 and 'x' is 7 [containerOutOfBounds]\n",
errout_str());

checkNormal("void f() {\n"
" static const int N = 4;\n"
Expand Down
Loading
Loading