Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 10 additions & 0 deletions lib/astutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1871,6 +1871,16 @@ std::vector<const Token *> getArguments(const Token *ftok)
return astFlatten(startTok, ",");
}

int getArgumentPos(const Variable* var, const Function* f)
{
auto arg_it = std::find_if(f->argumentList.begin(), f->argumentList.end(), [&](const Variable& v) {
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.

I wonder if it would be a good idea to capture var by value not by reference.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Why? It seems simpler and faster to capture by ref as it has lexical scoping and avoids creating copies.

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.

the motivation would be if we get some const safety. if it doesn't have a performance penalty.. that would be good. for a trivial example I made the assembler output from gcc is identical.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The variables are already declared const.

Copy link
Copy Markdown
Collaborator

@danmar danmar Jan 4, 2021

Choose a reason for hiding this comment

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

I assume that in the lambda you could write var=nullptr; and that will happily compile. well.. maybe that compiles happily with a = also?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yea, it can be modified with var=nullptr. I can make the pointer const.

return v.nameToken() == var->nameToken();
});
if (arg_it == f->argumentList.end())
return -1;
return std::distance(f->argumentList.begin(), arg_it);
}

const Token *findLambdaStartToken(const Token *last)
{
if (!last || last->str() != "}")
Expand Down
3 changes: 3 additions & 0 deletions lib/astutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "errortypes.h"
#include "utils.h"

class Function;
class Library;
class Scope;
class Settings;
Expand Down Expand Up @@ -234,6 +235,8 @@ int numberOfArguments(const Token *start);
*/
std::vector<const Token *> getArguments(const Token *ftok);

int getArgumentPos(const Variable* var, const Function* f);

const Token *findLambdaStartToken(const Token *last);

/**
Expand Down
271 changes: 184 additions & 87 deletions lib/checkstl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,18 @@

#include "checkstl.h"

#include "astutils.h"
#include "check.h"
#include "checknullpointer.h"
#include "errortypes.h"
#include "library.h"
#include "mathlib.h"
#include "pathanalysis.h"
#include "settings.h"
#include "standards.h"
#include "symboldatabase.h"
#include "token.h"
#include "utils.h"
#include "astutils.h"
#include "pathanalysis.h"
#include "valueflow.h"

#include <algorithm>
Expand All @@ -38,6 +39,8 @@
#include <map>
#include <set>
#include <sstream>
#include <unordered_map>
#include <unordered_set>
#include <utility>

// Register this check class (by creating a static instance of it)
Expand Down Expand Up @@ -843,6 +846,105 @@ static bool isInvalidMethod(const Token * tok)
return false;
}

struct InvalidContainerAnalyzer {
struct Info {
struct Reference {
const Token* tok;
ErrorPath errorPath;
};
std::unordered_map<int, Reference> expressions;
ErrorPath errorPath;
void add(const std::vector<Reference>& refs)
{
for (const Reference& r : refs) {
add(r);
}
}
void add(const Reference& r)
{
if (!r.tok)
return;
expressions.insert(std::make_pair(r.tok->exprId(), r));
}

std::vector<Reference> invalidTokens() const
{
std::vector<Reference> result;
std::transform(expressions.begin(), expressions.end(), std::back_inserter(result), SelectMapValues{});
return result;
}
};
std::unordered_map<const Function*, Info> invalidMethods;

std::vector<Info::Reference> invalidatesContainer(const Token* tok) const
{
std::vector<Info::Reference> result;
if (Token::Match(tok, "%name% (")) {
const Function* f = tok->function();
if (!f)
return result;
ErrorPathItem epi = std::make_pair(tok, "Calling function " + tok->str());
const bool dependsOnThis = exprDependsOnThis(tok->next());
auto it = invalidMethods.find(f);
if (it != invalidMethods.end()) {
std::vector<Info::Reference> refs = it->second.invalidTokens();
std::copy_if(refs.begin(), refs.end(), std::back_inserter(result), [&](const Info::Reference& r) {
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.

I wonder if it would be a good idea to capture dependsOnThis by value. Does it actually capture everything in the context or are compilers smart enough and captures only dependsOnThis.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It only captures the variables used, but compilers can optimize it further and just capture the stack pointer instead of each variable used. Usually, its inlined so there is no copy(or copy to pointer) or function call(see this example).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

It's good practice to limit the amount of variable captured.

const Variable* var = r.tok->variable();
if (!var)
return false;
if (dependsOnThis && !var->isLocal() && !var->isGlobal() && !var->isStatic())
return true;
if (!var->isArgument())
return false;
if (!var->isReference())
return false;
return true;
});
std::vector<const Token*> args = getArguments(tok);
for (Info::Reference& r : result) {
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.

Is result nonempty here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No.

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.

so I guess you will fix something here before we merge it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

What needs to be fixed?

Copy link
Copy Markdown
Collaborator

@danmar danmar Jan 7, 2021

Choose a reason for hiding this comment

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

I wonder .. if result is always empty. Is the loop redundant? If the loop is redundant then remove it.. but if it is not then please tell me how result could be nonempty..

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Its not always empty. The copy_if fills the result.

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.

ok thanks for that clarification I somehow missed it.

r.errorPath.push_front(epi);
const Variable* var = r.tok->variable();
if (!var)
continue;
if (var->isArgument()) {
int n = getArgumentPos(var, f);
const Token* tok2 = nullptr;
if (n >= 0 && n < args.size())
tok2 = args[n];
r.tok = tok2;
}
}
}
} else if (astIsContainer(tok)) {
if (isInvalidMethod(tok)) {
ErrorPath ep;
ep.emplace_front(tok,
"After calling '" + tok->strAt(2) +
"', iterators or references to the container's data may be invalid .");
result.push_back(Info::Reference{tok, ep});
}
}
return result;
}

void analyze(const SymbolDatabase* symboldatabase)
{
for (const Scope* scope : symboldatabase->functionScopes) {
const Function* f = scope->function;
if (!f)
continue;
for (const Token* tok = scope->bodyStart; tok != scope->bodyEnd; tok = tok->next()) {
if (Token::Match(tok, "if|while|for|goto|return"))
break;
std::vector<Info::Reference> c = invalidatesContainer(tok);
if (c.empty())
continue;
invalidMethods[f].add(c);
}
}
}
};

static bool isVariableDecl(const Token* tok)
{
if (!tok)
Expand All @@ -861,91 +963,91 @@ void CheckStl::invalidContainer()
{
const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
const Library& library = mSettings->library;
InvalidContainerAnalyzer analyzer;
analyzer.analyze(symbolDatabase);
for (const Scope * scope : symbolDatabase->functionScopes) {
for (const Token* tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) {
if (!Token::Match(tok, "%var%"))
continue;
if (tok->varId() == 0)
continue;
if (!astIsContainer(tok))
continue;
if (!isInvalidMethod(tok))
continue;
std::set<nonneg int> skipVarIds;
// Skip if the variable is assigned to
const Token* assignExpr = tok;
while (assignExpr->astParent()) {
bool isRHS = astIsRHS(assignExpr);
assignExpr = assignExpr->astParent();
if (Token::Match(assignExpr, "%assign%")) {
if (!isRHS)
assignExpr = nullptr;
break;
}
}
if (Token::Match(assignExpr, "%assign%") && Token::Match(assignExpr->astOperand1(), "%var%"))
skipVarIds.insert(assignExpr->astOperand1()->varId());
const Token * endToken = nextAfterAstRightmostLeaf(tok->next()->astParent());
if (!endToken)
endToken = tok->next();
const ValueFlow::Value* v = nullptr;
ErrorPath errorPath;
PathAnalysis::Info info = PathAnalysis{endToken, library} .forwardFind([&](const PathAnalysis::Info& info) {
if (!info.tok->variable())
return false;
if (info.tok->varId() == 0)
return false;
if (skipVarIds.count(info.tok->varId()) > 0)
return false;
// if (Token::simpleMatch(info.tok->next(), "."))
// return false;
if (Token::Match(info.tok->astParent(), "%assign%") && astIsLHS(info.tok))
skipVarIds.insert(info.tok->varId());
if (info.tok->variable()->isReference() &&
!isVariableDecl(info.tok) &&
reaches(info.tok->variable()->nameToken(), tok, library, nullptr)) {

ErrorPath ep;
bool addressOf = false;
const Variable* var = getLifetimeVariable(info.tok, ep, &addressOf);
// Check the reference is created before the change
if (var && var->declarationId() == tok->varId() && !addressOf) {
// An argument always reaches
if (var->isArgument() || (!var->isReference() && !var->isRValueReference() &&
!isVariableDecl(tok) && reaches(var->nameToken(), tok, library, &ep))) {
errorPath = ep;
return true;
}
for (const InvalidContainerAnalyzer::Info::Reference& r : analyzer.invalidatesContainer(tok)) {
if (!astIsContainer(r.tok))
continue;
std::set<nonneg int> skipVarIds;
// Skip if the variable is assigned to
const Token* assignExpr = tok;
while (assignExpr->astParent()) {
bool isRHS = astIsRHS(assignExpr);
assignExpr = assignExpr->astParent();
if (Token::Match(assignExpr, "%assign%")) {
if (!isRHS)
assignExpr = nullptr;
break;
}
}
for (const ValueFlow::Value& val:info.tok->values()) {
if (!val.isLocalLifetimeValue())
continue;
if (val.lifetimeKind == ValueFlow::Value::LifetimeKind::Address)
continue;
if (val.lifetimeKind == ValueFlow::Value::LifetimeKind::SubObject)
continue;
if (!val.tokvalue->variable())
continue;
if (val.tokvalue->varId() != tok->varId())
continue;
ErrorPath ep;
// Check the iterator is created before the change
if (val.tokvalue != tok && reaches(val.tokvalue, tok, library, &ep)) {
v = &val;
errorPath = ep;
return true;
}
if (Token::Match(assignExpr, "%assign%") && Token::Match(assignExpr->astOperand1(), "%var%"))
skipVarIds.insert(assignExpr->astOperand1()->varId());
const Token* endToken = nextAfterAstRightmostLeaf(tok->next()->astParent());
if (!endToken)
endToken = tok->next();
const ValueFlow::Value* v = nullptr;
ErrorPath errorPath;
PathAnalysis::Info info =
PathAnalysis{endToken, library}.forwardFind([&](const PathAnalysis::Info& info) {
if (!info.tok->variable())
return false;
if (info.tok->varId() == 0)
return false;
if (skipVarIds.count(info.tok->varId()) > 0)
return false;
// if (Token::simpleMatch(info.tok->next(), "."))
// return false;
if (Token::Match(info.tok->astParent(), "%assign%") && astIsLHS(info.tok))
skipVarIds.insert(info.tok->varId());
if (info.tok->variable()->isReference() && !isVariableDecl(info.tok) &&
reaches(info.tok->variable()->nameToken(), tok, library, nullptr)) {

ErrorPath ep;
bool addressOf = false;
const Variable* var = getLifetimeVariable(info.tok, ep, &addressOf);
// Check the reference is created before the change
if (var && var->declarationId() == r.tok->varId() && !addressOf) {
// An argument always reaches
if (var->isArgument() ||
(!var->isReference() && !var->isRValueReference() && !isVariableDecl(tok) &&
reaches(var->nameToken(), tok, library, &ep))) {
errorPath = ep;
return true;
}
}
}
for (const ValueFlow::Value& val : info.tok->values()) {
if (!val.isLocalLifetimeValue())
continue;
if (val.lifetimeKind == ValueFlow::Value::LifetimeKind::Address)
continue;
if (val.lifetimeKind == ValueFlow::Value::LifetimeKind::SubObject)
continue;
if (!val.tokvalue->variable())
continue;
if (val.tokvalue->varId() != r.tok->varId())
continue;
ErrorPath ep;
// Check the iterator is created before the change
if (val.tokvalue != tok && reaches(val.tokvalue, tok, library, &ep)) {
v = &val;
errorPath = ep;
return true;
}
}
return false;
});
if (!info.tok)
continue;
errorPath.insert(errorPath.end(), info.errorPath.begin(), info.errorPath.end());
errorPath.insert(errorPath.end(), r.errorPath.begin(), r.errorPath.end());
if (v) {
invalidContainerError(info.tok, r.tok, v, errorPath);
} else {
invalidContainerReferenceError(info.tok, r.tok, errorPath);
}
return false;
});
if (!info.tok)
continue;
errorPath.insert(errorPath.end(), info.errorPath.begin(), info.errorPath.end());
if (v) {
invalidContainerError(info.tok, tok, v, errorPath);
} else {
invalidContainerReferenceError(info.tok, tok, errorPath);
}
}
}
Expand Down Expand Up @@ -1011,8 +1113,6 @@ void CheckStl::invalidContainerLoopError(const Token *tok, const Token * loopTok
void CheckStl::invalidContainerError(const Token *tok, const Token * contTok, const ValueFlow::Value *val, ErrorPath errorPath)
{
const bool inconclusive = val ? val->isInconclusive() : false;
std::string method = contTok ? contTok->strAt(2) : "erase";
errorPath.emplace_back(contTok, "After calling '" + method + "', iterators or references to the container's data may be invalid .");
if (val)
errorPath.insert(errorPath.begin(), val->errorPath.begin(), val->errorPath.end());
std::string msg = "Using " + lifetimeMessage(tok, val, errorPath);
Expand All @@ -1022,10 +1122,7 @@ void CheckStl::invalidContainerError(const Token *tok, const Token * contTok, co

void CheckStl::invalidContainerReferenceError(const Token* tok, const Token* contTok, ErrorPath errorPath)
{
std::string method = contTok ? contTok->strAt(2) : "erase";
std::string name = contTok ? contTok->expressionString() : "x";
errorPath.emplace_back(
contTok, "After calling '" + method + "', iterators or references to the container's data may be invalid .");
std::string msg = "Reference to " + name;
errorPath.emplace_back(tok, "");
reportError(errorPath, Severity::error, "invalidContainerReference", msg + " that may be invalid.", CWE664, false);
Expand Down
16 changes: 16 additions & 0 deletions lib/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,22 @@
#include <string>
#include <vector>

struct SelectMapKeys {
template <class Pair>
typename Pair::first_type operator()(const Pair& p) const
{
return p.first;
}
};

struct SelectMapValues {
template <class Pair>
typename Pair::second_type operator()(const Pair& p) const
{
return p.second;
}
};

inline bool endsWith(const std::string &str, char c)
{
return str[str.size()-1U] == c;
Expand Down
Loading