Fix issue 10141: Errors with ref assignment (duplicateValueTenary and knownEmptyContainer) (#3093)
This commit is contained in:
parent
d3d2e16137
commit
0e871c178f
113
lib/astutils.cpp
113
lib/astutils.cpp
|
@ -30,6 +30,7 @@
|
|||
#include "valueflow.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <list>
|
||||
|
@ -751,73 +752,105 @@ static void followVariableExpressionError(const Token *tok1, const Token *tok2,
|
|||
errors->push_back(item);
|
||||
}
|
||||
|
||||
static const Token* followReferences(const Token* tok, ErrorPath* errors, int depth = 20)
|
||||
std::vector<ReferenceToken> followAllReferences(const Token* tok, bool inconclusive, ErrorPath errors, int depth)
|
||||
{
|
||||
struct ReferenceTokenLess {
|
||||
bool operator()(const ReferenceToken& x, const ReferenceToken& y) const
|
||||
{
|
||||
return x.token < y.token;
|
||||
}
|
||||
};
|
||||
if (!tok)
|
||||
return nullptr;
|
||||
return std::vector<ReferenceToken>{};
|
||||
if (depth < 0)
|
||||
return tok;
|
||||
return {{tok, std::move(errors)}};
|
||||
const Variable *var = tok->variable();
|
||||
if (var && var->declarationId() == tok->varId()) {
|
||||
if (var->nameToken() == tok) {
|
||||
return tok;
|
||||
return {{tok, std::move(errors)}};
|
||||
} else if (var->isReference() || var->isRValueReference()) {
|
||||
if (!var->declEndToken())
|
||||
return tok;
|
||||
return {{tok, std::move(errors)}};
|
||||
if (var->isArgument()) {
|
||||
if (errors)
|
||||
errors->emplace_back(var->declEndToken(), "Passed to reference.");
|
||||
return tok;
|
||||
errors.emplace_back(var->declEndToken(), "Passed to reference.");
|
||||
return {{tok, std::move(errors)}};
|
||||
} else if (Token::simpleMatch(var->declEndToken(), "=")) {
|
||||
if (errors)
|
||||
errors->emplace_back(var->declEndToken(), "Assigned to reference.");
|
||||
errors.emplace_back(var->declEndToken(), "Assigned to reference.");
|
||||
const Token *vartok = var->declEndToken()->astOperand2();
|
||||
if (vartok == tok)
|
||||
return tok;
|
||||
return {{tok, std::move(errors)}};
|
||||
if (vartok)
|
||||
return followReferences(vartok, errors, depth - 1);
|
||||
return followAllReferences(vartok, inconclusive, std::move(errors), depth - 1);
|
||||
} else {
|
||||
return tok;
|
||||
return {{tok, std::move(errors)}};
|
||||
}
|
||||
}
|
||||
} else if (Token::simpleMatch(tok, "?") && Token::simpleMatch(tok->astOperand2(), ":")) {
|
||||
std::set<ReferenceToken, ReferenceTokenLess> result;
|
||||
const Token* tok2 = tok->astOperand2();
|
||||
|
||||
std::vector<ReferenceToken> refs;
|
||||
refs = followAllReferences(tok2->astOperand1(), inconclusive, errors, depth - 1);
|
||||
result.insert(refs.begin(), refs.end());
|
||||
refs = followAllReferences(tok2->astOperand2(), inconclusive, errors, depth - 1);
|
||||
result.insert(refs.begin(), refs.end());
|
||||
|
||||
if (!inconclusive && result.size() != 1)
|
||||
return {{tok, std::move(errors)}};
|
||||
|
||||
if (!result.empty())
|
||||
return std::vector<ReferenceToken>(result.begin(), result.end());
|
||||
|
||||
} else if (Token::Match(tok->previous(), "%name% (")) {
|
||||
const Function *f = tok->previous()->function();
|
||||
if (f) {
|
||||
if (!Function::returnsReference(f))
|
||||
return tok;
|
||||
std::set<const Token*> result;
|
||||
ErrorPath errorPath;
|
||||
return {{tok, std::move(errors)}};
|
||||
std::set<ReferenceToken, ReferenceTokenLess> result;
|
||||
for (const Token* returnTok : Function::findReturns(f)) {
|
||||
if (returnTok == tok)
|
||||
continue;
|
||||
const Token* argvarTok = followReferences(returnTok, errors, depth - 1);
|
||||
const Variable* argvar = argvarTok->variable();
|
||||
if (!argvar)
|
||||
return tok;
|
||||
if (argvar->isArgument() && (argvar->isReference() || argvar->isRValueReference())) {
|
||||
int n = getArgumentPos(argvar, f);
|
||||
if (n < 0)
|
||||
return tok;
|
||||
std::vector<const Token*> args = getArguments(tok->previous());
|
||||
if (n >= args.size())
|
||||
return tok;
|
||||
const Token* argTok = args[n];
|
||||
ErrorPath er;
|
||||
er.emplace_back(returnTok, "Return reference.");
|
||||
er.emplace_back(tok->previous(), "Called function passing '" + argTok->expressionString() + "'.");
|
||||
const Token* tok2 = followReferences(argTok, &er, depth - 1);
|
||||
errorPath = er;
|
||||
result.insert(tok2);
|
||||
std::vector<ReferenceToken> argvarRt = followAllReferences(returnTok, inconclusive, errors, depth - 1);
|
||||
for(const ReferenceToken& rt:followAllReferences(returnTok, inconclusive, errors, depth - 1)) {
|
||||
const Variable* argvar = rt.token->variable();
|
||||
if (!argvar)
|
||||
return {{tok, std::move(errors)}};
|
||||
if (argvar->isArgument() && (argvar->isReference() || argvar->isRValueReference())) {
|
||||
int n = getArgumentPos(argvar, f);
|
||||
if (n < 0)
|
||||
return {{tok, std::move(errors)}};
|
||||
std::vector<const Token*> args = getArguments(tok->previous());
|
||||
if (n >= args.size())
|
||||
return {{tok, std::move(errors)}};
|
||||
const Token* argTok = args[n];
|
||||
ErrorPath er = errors;
|
||||
er.emplace_back(returnTok, "Return reference.");
|
||||
er.emplace_back(tok->previous(), "Called function passing '" + argTok->expressionString() + "'.");
|
||||
std::vector<ReferenceToken> refs = followAllReferences(argTok, inconclusive, std::move(er), depth - 1);
|
||||
result.insert(refs.begin(), refs.end());
|
||||
if (!inconclusive && result.size() > 1)
|
||||
return {{tok, std::move(errors)}};
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result.size() == 1) {
|
||||
if (errors)
|
||||
errors->insert(errors->end(), errorPath.begin(), errorPath.end());
|
||||
return *result.begin();
|
||||
}
|
||||
if (!result.empty())
|
||||
return std::vector<ReferenceToken>(result.begin(), result.end());
|
||||
}
|
||||
}
|
||||
return tok;
|
||||
return {{tok, std::move(errors)}};
|
||||
}
|
||||
|
||||
const Token* followReferences(const Token* tok, ErrorPath* errors)
|
||||
{
|
||||
if (!tok)
|
||||
return nullptr;
|
||||
std::vector<ReferenceToken> refs = followAllReferences(tok, false);
|
||||
if (refs.size() == 1) {
|
||||
if (errors)
|
||||
*errors = refs.front().errors;
|
||||
return refs.front().token;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static bool isSameLifetime(const Token * const tok1, const Token * const tok2)
|
||||
|
|
|
@ -140,6 +140,14 @@ bool precedes(const Token * tok1, const Token * tok2);
|
|||
|
||||
bool exprDependsOnThis(const Token* expr, nonneg int depth = 0);
|
||||
|
||||
struct ReferenceToken {
|
||||
const Token* token;
|
||||
ErrorPath errors;
|
||||
};
|
||||
|
||||
std::vector<ReferenceToken> followAllReferences(const Token* tok, bool inconclusive = true, ErrorPath errors = ErrorPath{}, int depth = 20);
|
||||
const Token* followReferences(const Token* tok, ErrorPath* errors = nullptr);
|
||||
|
||||
bool isSameExpression(bool cpp, bool macro, const Token *tok1, const Token *tok2, const Library& library, bool pure, bool followVar, ErrorPath* errors=nullptr);
|
||||
|
||||
bool isEqualKnownValue(const Token * const tok1, const Token * const tok2);
|
||||
|
|
|
@ -1915,25 +1915,37 @@ struct ValueFlowAnalyzer : Analyzer {
|
|||
}
|
||||
}
|
||||
|
||||
virtual Action analyze(const Token* tok, Direction d) const OVERRIDE {
|
||||
if (invalid())
|
||||
return Action::Invalid;
|
||||
Action analyzeMatch(const Token* tok, Direction d) const {
|
||||
const Token* parent = tok->astParent();
|
||||
if (astIsPointer(tok) && (Token::Match(parent, "*|[") || (parent && parent->originalName() == "->")) && getIndirect(tok) <= 0)
|
||||
return Action::Read;
|
||||
|
||||
Action w = isWritable(tok, d);
|
||||
if (w != Action::None)
|
||||
return w;
|
||||
|
||||
// Check for modifications by function calls
|
||||
return isModified(tok);
|
||||
}
|
||||
|
||||
Action analyzeToken(const Token* ref, const Token* tok, Direction d, bool inconclusiveRef) const
|
||||
{
|
||||
if (!ref)
|
||||
return Action::None;
|
||||
// If its an inconclusiveRef then ref != tok
|
||||
assert(!inconclusiveRef || ref != tok);
|
||||
bool inconclusive = false;
|
||||
if (match(tok)) {
|
||||
const Token* parent = tok->astParent();
|
||||
if (astIsPointer(tok) && (Token::Match(parent, "*|[") || (parent && parent->originalName() == "->")) && getIndirect(tok) <= 0)
|
||||
return Action::Read | Action::Match;
|
||||
|
||||
// Action read = Action::Read;
|
||||
Action w = isWritable(tok, d);
|
||||
if (w != Action::None)
|
||||
return w | Action::Match;
|
||||
|
||||
// Check for modifications by function calls
|
||||
return isModified(tok) | Action::Match;
|
||||
} else if (tok->isUnaryOp("*")) {
|
||||
if (match(ref)) {
|
||||
if (inconclusiveRef) {
|
||||
Action a = isModified(tok);
|
||||
if (a.isModified() || a.isInconclusive())
|
||||
return Action::Inconclusive;
|
||||
} else {
|
||||
return analyzeMatch(tok, d) | Action::Match;
|
||||
}
|
||||
} else if (ref->isUnaryOp("*")) {
|
||||
const Token* lifeTok = nullptr;
|
||||
for (const ValueFlow::Value& v:tok->astOperand1()->values()) {
|
||||
for (const ValueFlow::Value& v:ref->astOperand1()->values()) {
|
||||
if (!v.isLocalLifetimeValue())
|
||||
continue;
|
||||
if (lifeTok)
|
||||
|
@ -1946,17 +1958,37 @@ struct ValueFlowAnalyzer : Analyzer {
|
|||
a = Action::Invalid;
|
||||
if (Token::Match(tok->astParent(), "%assign%") && astIsLHS(tok))
|
||||
a |= Action::Invalid;
|
||||
if (inconclusiveRef && a.isModified())
|
||||
return Action::Inconclusive;
|
||||
return a;
|
||||
}
|
||||
return Action::None;
|
||||
|
||||
} else if (isAlias(tok, inconclusive)) {
|
||||
} else if (isAlias(ref, inconclusive)) {
|
||||
inconclusive |= inconclusiveRef;
|
||||
Action a = isAliasModified(tok);
|
||||
if (inconclusive && a.isModified())
|
||||
return Action::Inconclusive;
|
||||
else
|
||||
return a;
|
||||
} else if (Token::Match(tok, "%name% (") && !Token::simpleMatch(tok->linkAt(1), ") {")) {
|
||||
}
|
||||
return Action::None;
|
||||
}
|
||||
|
||||
virtual Action analyze(const Token* tok, Direction d) const OVERRIDE {
|
||||
if (invalid())
|
||||
return Action::Invalid;
|
||||
// Follow references
|
||||
std::vector<ReferenceToken> refs = followAllReferences(tok);
|
||||
const bool inconclusiveRefs = refs.size() != 1;
|
||||
for(const ReferenceToken& ref:refs) {
|
||||
Action a = analyzeToken(ref.token, tok, d, inconclusiveRefs);
|
||||
if (a != Action::None)
|
||||
return a;
|
||||
}
|
||||
|
||||
// TODO: Check if function is pure
|
||||
if (Token::Match(tok, "%name% (") && !Token::simpleMatch(tok->linkAt(1), ") {")) {
|
||||
// bailout: global non-const variables
|
||||
if (isGlobal()) {
|
||||
return Action::Invalid;
|
||||
|
@ -4216,7 +4248,14 @@ struct ConditionHandler {
|
|||
}
|
||||
}
|
||||
|
||||
Token* startTok = tok->astParent() ? tok->astParent() : tok->previous();
|
||||
Token* startTok = nullptr;
|
||||
if (astIsRHS(tok))
|
||||
startTok = tok->astParent();
|
||||
else if (astIsLHS(tok))
|
||||
startTok = previousBeforeAstLeftmostLeaf(tok->astParent());
|
||||
if (!startTok)
|
||||
startTok = tok->previous();
|
||||
|
||||
reverse(startTok, nullptr, cond.vartok, values, tokenlist, settings);
|
||||
});
|
||||
}
|
||||
|
@ -5789,7 +5828,7 @@ struct ContainerVariableAnalyzer : VariableAnalyzer {
|
|||
return Action::Invalid;
|
||||
if (isLikelyStreamRead(isCPP(), tok->astParent()))
|
||||
return Action::Invalid;
|
||||
if (isContainerSizeChanged(tok))
|
||||
if (astIsContainer(tok) && isContainerSizeChanged(tok))
|
||||
return Action::Invalid;
|
||||
return read;
|
||||
}
|
||||
|
|
|
@ -4472,6 +4472,18 @@ private:
|
|||
return "";
|
||||
}
|
||||
|
||||
static std::string isInconclusiveContainerSizeValue(const std::list<ValueFlow::Value>& values, MathLib::bigint i) {
|
||||
if (values.size() != 1)
|
||||
return "values.size():" + std::to_string(values.size());
|
||||
if (!values.front().isContainerSizeValue())
|
||||
return "ContainerSizeValue";
|
||||
if (!values.front().isInconclusive())
|
||||
return "Inconclusive";
|
||||
if (values.front().intvalue != i)
|
||||
return "intvalue:" + std::to_string(values.front().intvalue);
|
||||
return "";
|
||||
}
|
||||
|
||||
static std::string isKnownContainerSizeValue(const std::list<ValueFlow::Value> &values, MathLib::bigint i) {
|
||||
if (values.size() != 1)
|
||||
return "values.size():" + std::to_string(values.size());
|
||||
|
@ -5026,17 +5038,29 @@ private:
|
|||
"}\n";
|
||||
ASSERT_EQUALS(true, testValueOfXKnown(code, 4U, 0));
|
||||
|
||||
code = "bool f(std::string s) {\n"
|
||||
" if (!s.empty()) {\n"
|
||||
" bool x = s == \"0\";\n"
|
||||
" return x;\n"
|
||||
code = "bool f(std::string s) {\n"
|
||||
" if (!s.empty()) {\n"
|
||||
" bool x = s == \"0\";\n"
|
||||
" return x;\n"
|
||||
" }\n"
|
||||
" return false;\n"
|
||||
"}\n";
|
||||
ASSERT_EQUALS(false, testValueOfXKnown(code, 4U, 0));
|
||||
ASSERT_EQUALS(false, testValueOfXKnown(code, 4U, 1));
|
||||
ASSERT_EQUALS(false, testValueOfXImpossible(code, 4U, 0));
|
||||
|
||||
code = "bool f() {\n"
|
||||
" std::list<int> x1;\n"
|
||||
" std::list<int> x2;\n"
|
||||
" for (int i = 0; i < 10; ++i) {\n"
|
||||
" std::list<int>& x = (i < 5) ? x1 : x2;\n"
|
||||
" x.push_back(i);\n"
|
||||
" }\n"
|
||||
" return false;\n"
|
||||
" return x1.empty() || x2.empty();\n"
|
||||
"}\n";
|
||||
ASSERT_EQUALS(false, testValueOfXKnown(code, 4U, 0));
|
||||
ASSERT_EQUALS(false, testValueOfXKnown(code, 4U, 1));
|
||||
ASSERT_EQUALS(false, testValueOfXImpossible(code, 4U, 0));
|
||||
|
||||
ASSERT_EQUALS("", isInconclusiveContainerSizeValue(tokenValues(code, "x1 . empty", ValueFlow::Value::ValueType::CONTAINER_SIZE), 0));
|
||||
ASSERT_EQUALS("", isInconclusiveContainerSizeValue(tokenValues(code, "x2 . empty", ValueFlow::Value::ValueType::CONTAINER_SIZE), 0));
|
||||
|
||||
code = "std::vector<int> g();\n"
|
||||
"int f(bool b) {\n"
|
||||
" std::set<int> a;\n"
|
||||
|
|
Loading…
Reference in New Issue