Fix 11087: false negative: usage of reference to member of temporary object not detected (#4217)
* Fix 11087: false negative: usage of reference to member of temporary object not detected * Format * Add another test case * Fix FP with pointer * Format
This commit is contained in:
parent
9cecc8468e
commit
3e09503561
|
@ -3247,6 +3247,31 @@ std::vector<ValueFlow::Value> getLifetimeObjValues(const Token* tok, bool inconc
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool hasUniqueOwnership(const Token* tok)
|
||||||
|
{
|
||||||
|
if (astIsPointer(tok))
|
||||||
|
return false;
|
||||||
|
if (astIsUniqueSmartPointer(tok))
|
||||||
|
return true;
|
||||||
|
if (astIsContainerOwned(tok))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if dereferencing an object that doesn't have unique ownership
|
||||||
|
static bool derefShared(const Token* tok)
|
||||||
|
{
|
||||||
|
if (!tok)
|
||||||
|
return false;
|
||||||
|
if (tok->str() == "." && tok->originalName() != "->") {
|
||||||
|
return false;
|
||||||
|
} else if (!tok->isUnaryOp("*") && tok->str() == "[") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const Token* ptrTok = tok->astOperand1();
|
||||||
|
return !hasUniqueOwnership(ptrTok);
|
||||||
|
}
|
||||||
|
|
||||||
ValueFlow::Value getLifetimeObjValue(const Token *tok, bool inconclusive)
|
ValueFlow::Value getLifetimeObjValue(const Token *tok, bool inconclusive)
|
||||||
{
|
{
|
||||||
std::vector<ValueFlow::Value> values = getLifetimeObjValues(tok, inconclusive);
|
std::vector<ValueFlow::Value> values = getLifetimeObjValues(tok, inconclusive);
|
||||||
|
@ -3320,6 +3345,7 @@ static std::vector<LifetimeToken> getLifetimeTokens(const Token* tok,
|
||||||
const Variable* argvar = argvarTok->variable();
|
const Variable* argvar = argvarTok->variable();
|
||||||
if (!argvar)
|
if (!argvar)
|
||||||
continue;
|
continue;
|
||||||
|
const Token* argTok = nullptr;
|
||||||
if (argvar->isArgument() && (argvar->isReference() || argvar->isRValueReference())) {
|
if (argvar->isArgument() && (argvar->isReference() || argvar->isRValueReference())) {
|
||||||
int n = getArgumentPos(argvar, f);
|
int n = getArgumentPos(argvar, f);
|
||||||
if (n < 0)
|
if (n < 0)
|
||||||
|
@ -3328,9 +3354,17 @@ static std::vector<LifetimeToken> getLifetimeTokens(const Token* tok,
|
||||||
// TODO: Track lifetimes of default parameters
|
// TODO: Track lifetimes of default parameters
|
||||||
if (n >= args.size())
|
if (n >= args.size())
|
||||||
return std::vector<LifetimeToken> {};
|
return std::vector<LifetimeToken> {};
|
||||||
const Token* argTok = args[n];
|
argTok = args[n];
|
||||||
lt.errorPath.emplace_back(returnTok, "Return reference.");
|
lt.errorPath.emplace_back(returnTok, "Return reference.");
|
||||||
lt.errorPath.emplace_back(tok->previous(), "Called function passing '" + argTok->expressionString() + "'.");
|
lt.errorPath.emplace_back(tok->previous(), "Called function passing '" + argTok->expressionString() + "'.");
|
||||||
|
} else if (Token::Match(tok->tokAt(-2), ". %name% (") && !derefShared(tok->tokAt(-2)) &&
|
||||||
|
exprDependsOnThis(argvarTok)) {
|
||||||
|
argTok = tok->tokAt(-2)->astOperand1();
|
||||||
|
lt.errorPath.emplace_back(returnTok, "Return reference that depends on 'this'.");
|
||||||
|
lt.errorPath.emplace_back(tok->previous(),
|
||||||
|
"Calling member function on '" + argTok->expressionString() + "'.");
|
||||||
|
}
|
||||||
|
if (argTok) {
|
||||||
std::vector<LifetimeToken> arglts = LifetimeToken::setInconclusive(
|
std::vector<LifetimeToken> arglts = LifetimeToken::setInconclusive(
|
||||||
getLifetimeTokens(argTok, escape, std::move(lt.errorPath), pred, depth - returns.size()),
|
getLifetimeTokens(argTok, escape, std::move(lt.errorPath), pred, depth - returns.size()),
|
||||||
returns.size() > 1);
|
returns.size() > 1);
|
||||||
|
|
|
@ -1940,6 +1940,61 @@ private:
|
||||||
" g(std::move(v));\n"
|
" g(std::move(v));\n"
|
||||||
"}\n");
|
"}\n");
|
||||||
ASSERT_EQUALS("", errout.str());
|
ASSERT_EQUALS("", errout.str());
|
||||||
|
|
||||||
|
// #11087
|
||||||
|
check("struct S1 {\n"
|
||||||
|
" int& get() { return val; }\n"
|
||||||
|
" int val{42};\n"
|
||||||
|
"};\n"
|
||||||
|
"void f() {\n"
|
||||||
|
" int& v = S1().get();\n"
|
||||||
|
" v += 1;\n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS(
|
||||||
|
"[test.cpp:6] -> [test.cpp:2] -> [test.cpp:6] -> [test.cpp:7]: (error) Using reference to dangling temporary.\n",
|
||||||
|
errout.str());
|
||||||
|
|
||||||
|
check("struct A {\n"
|
||||||
|
" const int& g() const { return i; }\n"
|
||||||
|
" int i;\n"
|
||||||
|
"};\n"
|
||||||
|
"A* a();\n"
|
||||||
|
"int f() {\n"
|
||||||
|
" const int& i = a()->g();\n"
|
||||||
|
" return i;\n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS("", errout.str());
|
||||||
|
|
||||||
|
check("struct A {\n"
|
||||||
|
" const int& g() const { return i; }\n"
|
||||||
|
" int i;\n"
|
||||||
|
"};\n"
|
||||||
|
"std::unique_ptr<A> a();\n"
|
||||||
|
"int f() {\n"
|
||||||
|
" const int& i = a()->g();\n"
|
||||||
|
" return i;\n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS(
|
||||||
|
"[test.cpp:7] -> [test.cpp:2] -> [test.cpp:7] -> [test.cpp:8]: (error) Using reference to dangling temporary.\n",
|
||||||
|
errout.str());
|
||||||
|
|
||||||
|
check("struct S1 {\n"
|
||||||
|
" auto get() -> auto& { return val; }\n"
|
||||||
|
" int val{42};\n"
|
||||||
|
"};\n"
|
||||||
|
"struct S2 {\n"
|
||||||
|
" auto get() -> S1 { return s; }\n"
|
||||||
|
" S1 s;\n"
|
||||||
|
"};\n"
|
||||||
|
"auto main() -> int {\n"
|
||||||
|
" S2 c{};\n"
|
||||||
|
" auto& v = c.get().get();\n"
|
||||||
|
" v += 1;\n"
|
||||||
|
" return c.s.val;\n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS(
|
||||||
|
"[test.cpp:11] -> [test.cpp:2] -> [test.cpp:11] -> [test.cpp:12]: (error) Using reference to dangling temporary.\n",
|
||||||
|
errout.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void testglobalnamespace() {
|
void testglobalnamespace() {
|
||||||
|
|
Loading…
Reference in New Issue