Fix FP when copying pointer to string (#1479)

This commit is contained in:
Paul Fultz II 2018-11-13 23:59:25 -06:00 committed by Daniel Marjamäki
parent ac242b69d6
commit 54453c5802
10 changed files with 278 additions and 250 deletions

View File

@ -98,7 +98,7 @@ static bool match(const Token *tok, const std::string &rhs)
{
if (tok->str() == rhs)
return true;
if (tok->isName() && !tok->varId() && tok->values().size() == 1U && tok->values().front().isKnown() && MathLib::toString(tok->values().front().intvalue) == rhs)
if (tok->isName() && !tok->varId() && tok->hasKnownIntValue() && MathLib::toString(tok->values().front().intvalue) == rhs)
return true;
return false;
}

View File

@ -593,6 +593,9 @@ void CheckAutoVariables::checkVarLifetimeScope(const Token * start, const Token
for (const ValueFlow::Value& val:tok->values()) {
if (!val.isLifetimeValue())
continue;
// Skip temporaries for now
if (val.tokvalue == tok)
continue;
if (Token::Match(tok->astParent(), "return|throw")) {
if (isInScope(val.tokvalue, scope)) {
errorReturnDanglingLifetime(tok, &val);

View File

@ -285,8 +285,8 @@ void CheckCondition::checkBadBitmaskCheck()
(parent->str() == "(" && Token::Match(parent->astOperand1(), "if|while")) ||
(parent->str() == "return" && parent->astOperand1() == tok && inBooleanFunction(tok));
const bool isTrue = (tok->astOperand1()->values().size() == 1 && tok->astOperand1()->values().front().intvalue != 0 && tok->astOperand1()->values().front().isKnown()) ||
(tok->astOperand2()->values().size() == 1 && tok->astOperand2()->values().front().intvalue != 0 && tok->astOperand2()->values().front().isKnown());
const bool isTrue = (tok->astOperand1()->hasKnownIntValue() && tok->astOperand1()->values().front().intvalue != 0) ||
(tok->astOperand2()->hasKnownIntValue() && tok->astOperand2()->values().front().intvalue != 0);
if (isBoolean && isTrue)
badBitmaskCheckError(tok);
@ -441,8 +441,8 @@ void CheckCondition::multiCondition()
if (cond1 &&
tok2->astOperand2() &&
!cond1->hasKnownValue() &&
!tok2->astOperand2()->hasKnownValue() &&
!cond1->hasKnownIntValue() &&
!tok2->astOperand2()->hasKnownIntValue() &&
isOverlappingCond(cond1, tok2->astOperand2(), true))
multiConditionError(tok2, cond1->linenr());
}
@ -601,7 +601,7 @@ void CheckCondition::multiCondition2()
if (firstCondition->str() == "&&") {
tokens1.push(firstCondition->astOperand1());
tokens1.push(firstCondition->astOperand2());
} else if (!firstCondition->hasKnownValue()) {
} else if (!firstCondition->hasKnownIntValue()) {
if (!isReturnVar && isOppositeCond(false, mTokenizer->isCPP(), firstCondition, cond2, mSettings->library, true, true, &errorPath)) {
if (!isAliased(vars))
oppositeInnerConditionError(firstCondition, cond2, errorPath);
@ -621,7 +621,7 @@ void CheckCondition::multiCondition2()
if (secondCondition->str() == "||" || secondCondition->str() == "&&") {
tokens2.push(secondCondition->astOperand1());
tokens2.push(secondCondition->astOperand2());
} else if ((!cond1->hasKnownValue() || !secondCondition->hasKnownValue()) &&
} else if ((!cond1->hasKnownIntValue() || !secondCondition->hasKnownIntValue()) &&
isSameExpression(mTokenizer->isCPP(), true, cond1, secondCondition, mSettings->library, true, true, &errorPath)) {
if (!isAliased(vars))
identicalConditionAfterEarlyExitError(cond1, secondCondition, errorPath);

View File

@ -492,8 +492,7 @@ static bool alwaysTrue(const Token *tok)
{
if (!tok)
return false;
if (tok->values().size() == 1U &&
tok->values().front().isKnown() &&
if (tok->hasKnownIntValue() &&
tok->values().front().intvalue != 0)
return true;
if (tok->str() == "||")

View File

@ -225,7 +225,7 @@ static void conditionAlwaysTrueOrFalse(const Token *tok, const std::map<unsigned
}
else if (tok->isComparisonOp()) {
if (tok->values().size() == 1U && tok->values().front().isKnown()) {
if (tok->hasKnownIntValue()) {
if (tok->values().front().intvalue)
*alwaysTrue = true;
else

View File

@ -1231,7 +1231,7 @@ void SymbolDatabase::createSymbolDatabaseEnums()
ValueFlow::valueFlowConstantFoldAST(rhs, mSettings);
// get constant folded value:
if (rhs && rhs->values().size() == 1U && rhs->values().front().isKnown()) {
if (rhs && rhs->hasKnownIntValue()) {
enumerator.value = rhs->values().front().intvalue;
enumerator.value_known = true;
value = enumerator.value + 1;
@ -1334,7 +1334,7 @@ void SymbolDatabase::createSymbolDatabaseUnknownArrayDimensions()
ValueFlow::valueFlowConstantFoldAST(rhs, mSettings);
// get constant folded value:
if (rhs && rhs->values().size() == 1U && rhs->values().front().isKnown()) {
if (rhs && rhs->hasKnownIntValue()) {
dimension.num = rhs->values().front().intvalue;
dimension.known = true;
}

View File

@ -1640,8 +1640,10 @@ const Token *Token::getValueTokenDeadPointer() const
bool Token::addValue(const ValueFlow::Value &value)
{
if (value.isKnown() && mValues) {
// Clear all other values since value is known
mValues->clear();
// Clear all other values of the same type since value is known
mValues->remove_if([&](const ValueFlow::Value & x) {
return x.valueType == value.valueType;
});
}
if (mValues) {
@ -1660,7 +1662,7 @@ bool Token::addValue(const ValueFlow::Value &value)
// different types => continue
if (it->valueType != value.valueType)
continue;
if (value.isTokValue() && (it->tokvalue != value.tokvalue) && (it->tokvalue->str() != value.tokvalue->str()))
if ((value.isTokValue() || value.isLifetimeValue()) && (it->tokvalue != value.tokvalue) && (it->tokvalue->str() != value.tokvalue->str()))
continue;
// same value, but old value is inconclusive so replace it
@ -1680,7 +1682,10 @@ bool Token::addValue(const ValueFlow::Value &value)
ValueFlow::Value v(value);
if (v.varId == 0)
v.varId = mVarId;
mValues->push_back(v);
if(v.isKnown() && v.isIntValue())
mValues->push_front(v);
else
mValues->push_back(v);
}
} else {
ValueFlow::Value v(value);

View File

@ -25,7 +25,9 @@
#include "mathlib.h"
#include "valueflow.h"
#include <algorithm>
#include <cstddef>
#include <functional>
#include <list>
#include <ostream>
#include <string>
@ -838,11 +840,11 @@ public:
}
bool hasKnownIntValue() const {
return mValues && mValues->size() == 1U && mValues->front().isKnown() && mValues->front().isIntValue();
return hasKnownValue() && std::any_of(mValues->begin(), mValues->end(), std::mem_fn(&ValueFlow::Value::isIntValue));
}
bool hasKnownValue() const {
return mValues && mValues->size() == 1U && mValues->front().isKnown();
return mValues && std::any_of(mValues->begin(), mValues->end(), std::mem_fn(&ValueFlow::Value::isKnown));
}
const ValueFlow::Value * getValue(const MathLib::bigint val) const {

View File

@ -2468,6 +2468,240 @@ static bool valueFlowForward(Token * const startToken,
return true;
}
static bool isNotLifetimeValue(const ValueFlow::Value& val)
{
return !val.isLifetimeValue();
}
static void valueFlowForwardLifetime(Token * tok, TokenList *tokenlist, ErrorLogger *errorLogger, const Settings *settings)
{
const Token * assignTok = tok->astParent();
// Assignment
if (!assignTok || (assignTok->str() != "=") || assignTok->astParent())
return;
// Lhs should be a variable
if (!assignTok->astOperand1() || !assignTok->astOperand1()->varId())
return;
const Variable *var = assignTok->astOperand1()->variable();
if (!var || (!var->isLocal() && !var->isGlobal() && !var->isArgument()))
return;
const Token * const endOfVarScope = var->typeStartToken()->scope()->bodyEnd;
// Rhs values..
if (!assignTok->astOperand2() || assignTok->astOperand2()->values().empty())
return;
if(astIsPointer(assignTok->astOperand2()) && !var->isPointer() && !(var->valueType() && var->valueType()->isIntegral()))
return;
std::list<ValueFlow::Value> values = assignTok->astOperand2()->values();
// Static variable initialisation?
if (var->isStatic() && var->nameToken() == assignTok->astOperand1())
changeKnownToPossible(values);
// Skip RHS
const Token * nextExpression = nextAfterAstRightmostLeaf(assignTok);
// Only forward lifetime values
values.remove_if(&isNotLifetimeValue);
valueFlowForward(const_cast<Token *>(nextExpression), endOfVarScope, var, var->declarationId(), values, false, false, tokenlist, errorLogger, settings);
}
static const Variable * getLifetimeVariable(const Token * tok, ErrorPath& errorPath)
{
const Variable * var = tok->variable();
if (!var)
return nullptr;
if (var->isReference() || var->isRValueReference()) {
for (const ValueFlow::Value& v:tok->values()) {
if (!v.isLifetimeValue())
continue;
if (v.tokvalue == tok)
continue;
errorPath.insert(errorPath.end(), v.errorPath.begin(), v.errorPath.end());
const Variable * var2 = getLifetimeVariable(v.tokvalue, errorPath);
if (var2)
return var2;
}
return nullptr;
}
return var;
}
struct Lambda {
explicit Lambda(const Token * tok)
: capture(nullptr), arguments(nullptr), returnTok(nullptr), bodyTok(nullptr) {
if (!Token::simpleMatch(tok, "[") || !tok->link())
return;
capture = tok;
if (Token::simpleMatch(capture->link(), "] (")) {
arguments = capture->link()->next();
}
const Token * afterArguments = arguments ? arguments->link()->next() : capture->link()->next();
if (afterArguments && afterArguments->originalName() == "->") {
returnTok = afterArguments->next();
bodyTok = Token::findsimplematch(returnTok, "{");
} else if (Token::simpleMatch(afterArguments, "{")) {
bodyTok = afterArguments;
}
}
const Token * capture;
const Token * arguments;
const Token * returnTok;
const Token * bodyTok;
bool isLambda() const {
return capture && bodyTok;
}
};
static void valueFlowLifetime(TokenList *tokenlist, SymbolDatabase*, ErrorLogger *errorLogger, const Settings *settings)
{
for (Token *tok = tokenlist->front(); tok; tok = tok->next()) {
Lambda lam(tok);
// Lamdas
if (lam.isLambda()) {
const Scope * bodyScope = lam.bodyTok->scope();
std::set<const Scope *> scopes;
// TODO: Handle explicit capture
bool captureByRef = Token::Match(lam.capture, "[ & ]");
bool captureByValue = Token::Match(lam.capture, "[ = ]");
for (const Token * tok2 = lam.bodyTok; tok2 != lam.bodyTok->link(); tok2 = tok2->next()) {
ErrorPath errorPath;
if (captureByRef) {
const Variable * var = getLifetimeVariable(tok2, errorPath);
if (!var)
continue;
const Scope * scope = var->scope();
if (scopes.count(scope) > 0)
continue;
if (scope->isNestedIn(bodyScope))
continue;
scopes.insert(scope);
errorPath.emplace_back(tok2, "Lambda captures variable by reference here.");
ValueFlow::Value value;
value.valueType = ValueFlow::Value::LIFETIME;
value.tokvalue = var->nameToken();
value.errorPath = errorPath;
value.lifetimeKind = ValueFlow::Value::Lambda;
setTokenValue(tok, value, tokenlist->getSettings());
valueFlowForwardLifetime(tok, tokenlist, errorLogger, settings);
} else if (captureByValue) {
for (const ValueFlow::Value& v:tok2->values()) {
if (!v.isLifetimeValue() && !v.tokvalue)
continue;
const Token * tok3 = v.tokvalue;
errorPath = v.errorPath;
const Variable * var = getLifetimeVariable(tok3, errorPath);
if (!var)
continue;
const Scope * scope = var->scope();
if (scopes.count(scope) > 0)
continue;
if (scope->isNestedIn(bodyScope))
continue;
scopes.insert(scope);
errorPath.emplace_back(tok2, "Lambda captures variable by value here.");
ValueFlow::Value value;
value.valueType = ValueFlow::Value::LIFETIME;
value.tokvalue = var->nameToken();
value.errorPath = errorPath;
value.lifetimeKind = ValueFlow::Value::Lambda;
setTokenValue(tok, value, tokenlist->getSettings());
valueFlowForwardLifetime(tok, tokenlist, errorLogger, settings);
}
}
}
}
// address of
else if (tok->isUnaryOp("&")) {
ErrorPath errorPath;
// child should be some buffer or variable
const Token *vartok = tok->astOperand1();
while (vartok) {
if (vartok->str() == "[")
vartok = vartok->astOperand1();
else if (vartok->str() == "." || vartok->str() == "::")
vartok = vartok->astOperand2();
else
break;
}
if (!vartok)
continue;
const Variable * var = getLifetimeVariable(vartok, errorPath);
if (!var)
continue;
if (var->isPointer() && Token::Match(vartok->astParent(), "[|*"))
continue;
errorPath.emplace_back(tok, "Address of variable taken here.");
ValueFlow::Value value;
value.valueType = ValueFlow::Value::LIFETIME;
value.tokvalue = var->nameToken();
value.errorPath = errorPath;
setTokenValue(tok, value, tokenlist->getSettings());
valueFlowForwardLifetime(tok, tokenlist, errorLogger, settings);
}
// container lifetimes
else if (tok->variable() && Token::Match(tok, "%var% . begin|cbegin|rbegin|crbegin|end|cend|rend|crend|data|c_str (")) {
ErrorPath errorPath;
const Library::Container * container = settings->library.detectContainer(tok->variable()->typeStartToken());
if (!container)
continue;
const Variable * var = tok->variable();
bool isIterator = !Token::Match(tok->tokAt(2), "data|c_str");
if (isIterator)
errorPath.emplace_back(tok, "Iterator to container is created here.");
else
errorPath.emplace_back(tok, "Pointer to container is created here.");
ValueFlow::Value value;
value.valueType = ValueFlow::Value::LIFETIME;
value.tokvalue = var->nameToken();
value.errorPath = errorPath;
value.lifetimeKind = isIterator ? ValueFlow::Value::Iterator : ValueFlow::Value::Object;
setTokenValue(tok->tokAt(3), value, tokenlist->getSettings());
valueFlowForwardLifetime(tok->tokAt(3), tokenlist, errorLogger, settings);
}
// Check variables
else if (tok->variable()) {
ErrorPath errorPath;
const Variable * var = getLifetimeVariable(tok, errorPath);
if (!var)
continue;
if (var->isArray() && tok->astParent() && (astIsPointer(tok->astParent()) || Token::Match(tok->astParent(), "%assign%|return"))) {
errorPath.emplace_back(tok, "Array decayed to pointer here.");
ValueFlow::Value value;
value.valueType = ValueFlow::Value::LIFETIME;
value.tokvalue = var->nameToken();
value.errorPath = errorPath;
setTokenValue(tok, value, tokenlist->getSettings());
valueFlowForwardLifetime(tok, tokenlist, errorLogger, settings);
}
}
}
}
static bool isStdMoveOrStdForwarded(Token * tok, ValueFlow::Value::MoveKind * moveKind, Token ** varTok = nullptr)
{
if (tok->str() != "std")
@ -2629,6 +2863,12 @@ static void valueFlowAfterAssign(TokenList *tokenlist, SymbolDatabase* symboldat
continue;
std::list<ValueFlow::Value> values = tok->astOperand2()->values();
if(std::any_of(values.begin(), values.end(), std::mem_fn(&ValueFlow::Value::isLifetimeValue))) {
valueFlowForwardLifetime(tok, tokenlist, errorLogger, settings);
values.remove_if(std::mem_fn(&ValueFlow::Value::isLifetimeValue));
}
if(!var->isPointer())
values.remove_if(std::mem_fn(&ValueFlow::Value::isTokValue));
for (std::list<ValueFlow::Value>::iterator it = values.begin(); it != values.end(); ++it) {
const std::string info = "Assignment '" + tok->expressionString() + "', assigned value is " + it->infoString();
it->errorPath.emplace_back(tok->astOperand2(), info);
@ -2913,237 +3153,6 @@ static void valueFlowAfterCondition(TokenList *tokenlist, SymbolDatabase* symbol
}
}
static bool isNotLifetimeValue(const ValueFlow::Value& val)
{
return !val.isLifetimeValue();
}
static void valueFlowForwardLifetime(Token * tok, TokenList *tokenlist, ErrorLogger *errorLogger, const Settings *settings)
{
const Token * assignTok = tok->astParent();
// Assignment
if (!assignTok || (assignTok->str() != "=") || assignTok->astParent())
return;
// Lhs should be a variable
if (!assignTok->astOperand1() || !assignTok->astOperand1()->varId())
return;
const Variable *var = assignTok->astOperand1()->variable();
if (!var || (!var->isLocal() && !var->isGlobal() && !var->isArgument()))
return;
const Token * const endOfVarScope = var->typeStartToken()->scope()->bodyEnd;
// Rhs values..
if (!assignTok->astOperand2() || assignTok->astOperand2()->values().empty())
return;
std::list<ValueFlow::Value> values = assignTok->astOperand2()->values();
// Static variable initialisation?
if (var->isStatic() && var->nameToken() == assignTok->astOperand1())
changeKnownToPossible(values);
// Skip RHS
const Token * nextExpression = nextAfterAstRightmostLeaf(assignTok);
// Only forward lifetime values
values.remove_if(&isNotLifetimeValue);
valueFlowForward(const_cast<Token *>(nextExpression), endOfVarScope, var, var->declarationId(), values, false, false, tokenlist, errorLogger, settings);
}
static const Variable * getLifetimeVariable(const Token * tok, ErrorPath& errorPath)
{
const Variable * var = tok->variable();
if (!var)
return nullptr;
if (var->isReference() || var->isRValueReference()) {
for (const ValueFlow::Value& v:tok->values()) {
if (!v.isLifetimeValue() && !v.tokvalue)
continue;
if (v.tokvalue == tok)
continue;
errorPath.insert(errorPath.end(), v.errorPath.begin(), v.errorPath.end());
const Variable * var2 = getLifetimeVariable(v.tokvalue, errorPath);
if (var2)
return var2;
}
return nullptr;
}
return var;
}
struct Lambda {
explicit Lambda(const Token * tok)
: capture(nullptr), arguments(nullptr), returnTok(nullptr), bodyTok(nullptr) {
if (!Token::simpleMatch(tok, "[") || !tok->link())
return;
capture = tok;
if (Token::simpleMatch(capture->link(), "] (")) {
arguments = capture->link()->next();
}
const Token * afterArguments = arguments ? arguments->link()->next() : capture->link()->next();
if (afterArguments && afterArguments->originalName() == "->") {
returnTok = afterArguments->next();
bodyTok = Token::findsimplematch(returnTok, "{");
} else if (Token::simpleMatch(afterArguments, "{")) {
bodyTok = afterArguments;
}
}
const Token * capture;
const Token * arguments;
const Token * returnTok;
const Token * bodyTok;
bool isLambda() const {
return capture && bodyTok;
}
};
static void valueFlowLifetime(TokenList *tokenlist, SymbolDatabase*, ErrorLogger *errorLogger, const Settings *settings)
{
for (Token *tok = tokenlist->front(); tok; tok = tok->next()) {
Lambda lam(tok);
// Lamdas
if (lam.isLambda()) {
const Scope * bodyScope = lam.bodyTok->scope();
std::set<const Scope *> scopes;
// TODO: Handle explicit capture
bool captureByRef = Token::Match(lam.capture, "[ & ]");
bool captureByValue = Token::Match(lam.capture, "[ = ]");
for (const Token * tok2 = lam.bodyTok; tok2 != lam.bodyTok->link(); tok2 = tok2->next()) {
ErrorPath errorPath;
if (captureByRef) {
const Variable * var = getLifetimeVariable(tok2, errorPath);
if (!var)
continue;
const Scope * scope = var->scope();
if (scopes.count(scope) > 0)
continue;
if (scope->isNestedIn(bodyScope))
continue;
scopes.insert(scope);
errorPath.emplace_back(tok2, "Lambda captures variable by reference here.");
ValueFlow::Value value;
value.valueType = ValueFlow::Value::LIFETIME;
value.tokvalue = var->nameToken();
value.errorPath = errorPath;
value.lifetimeKind = ValueFlow::Value::Lambda;
setTokenValue(tok, value, tokenlist->getSettings());
valueFlowForwardLifetime(tok, tokenlist, errorLogger, settings);
} else if (captureByValue) {
for (const ValueFlow::Value& v:tok2->values()) {
if (!v.isLifetimeValue() && !v.tokvalue)
continue;
const Token * tok3 = v.tokvalue;
errorPath = v.errorPath;
const Variable * var = getLifetimeVariable(tok3, errorPath);
if (!var)
continue;
const Scope * scope = var->scope();
if (scopes.count(scope) > 0)
continue;
if (scope->isNestedIn(bodyScope))
continue;
scopes.insert(scope);
errorPath.emplace_back(tok2, "Lambda captures variable by value here.");
ValueFlow::Value value;
value.valueType = ValueFlow::Value::LIFETIME;
value.tokvalue = var->nameToken();
value.errorPath = errorPath;
value.lifetimeKind = ValueFlow::Value::Lambda;
setTokenValue(tok, value, tokenlist->getSettings());
valueFlowForwardLifetime(tok, tokenlist, errorLogger, settings);
}
}
}
}
// address of
else if (tok->isUnaryOp("&")) {
ErrorPath errorPath;
// child should be some buffer or variable
const Token *vartok = tok->astOperand1();
while (vartok) {
if (vartok->str() == "[")
vartok = vartok->astOperand1();
else if (vartok->str() == "." || vartok->str() == "::")
vartok = vartok->astOperand2();
else
break;
}
if (!vartok)
continue;
const Variable * var = getLifetimeVariable(vartok, errorPath);
if (!var)
continue;
if (var->isPointer() && Token::Match(vartok->astParent(), "[|*"))
continue;
errorPath.emplace_back(tok, "Address of variable taken here.");
ValueFlow::Value value;
value.valueType = ValueFlow::Value::LIFETIME;
value.tokvalue = var->nameToken();
value.errorPath = errorPath;
setTokenValue(tok, value, tokenlist->getSettings());
valueFlowForwardLifetime(tok, tokenlist, errorLogger, settings);
}
// container lifetimes
else if (tok->variable() && Token::Match(tok, "%var% . begin|cbegin|rbegin|crbegin|end|cend|rend|crend|data|c_str (")) {
ErrorPath errorPath;
const Library::Container * container = settings->library.detectContainer(tok->variable()->typeStartToken());
if (!container)
continue;
const Variable * var = tok->variable();
bool isIterator = !Token::Match(tok->tokAt(2), "data|c_str");
if (isIterator)
errorPath.emplace_back(tok, "Iterator to container is created here.");
else
errorPath.emplace_back(tok, "Pointer to container is created here.");
ValueFlow::Value value;
value.valueType = ValueFlow::Value::LIFETIME;
value.tokvalue = var->nameToken();
value.errorPath = errorPath;
value.lifetimeKind = isIterator ? ValueFlow::Value::Iterator : ValueFlow::Value::Object;
setTokenValue(tok->tokAt(3), value, tokenlist->getSettings());
valueFlowForwardLifetime(tok->tokAt(3), tokenlist, errorLogger, settings);
}
// Check variables
else if (tok->variable()) {
ErrorPath errorPath;
const Variable * var = getLifetimeVariable(tok, errorPath);
if (!var)
continue;
if (var->isArray() && tok->astParent() && (astIsPointer(tok->astParent()) || Token::Match(tok->astParent(), "%assign%|return"))) {
errorPath.emplace_back(tok, "Array decayed to pointer here.");
ValueFlow::Value value;
value.valueType = ValueFlow::Value::LIFETIME;
value.tokvalue = var->nameToken();
value.errorPath = errorPath;
setTokenValue(tok, value, tokenlist->getSettings());
valueFlowForwardLifetime(tok, tokenlist, errorLogger, settings);
}
}
}
}
static void execute(const Token *expr,
ProgramMemory * const programMemory,
MathLib::bigint *result,

View File

@ -714,7 +714,7 @@ private:
" char *p = str;\n"
" return p;\n"
"}");
ASSERT_EQUALS("[test.cpp:4] -> [test.cpp:4] -> [test.cpp:3] -> [test.cpp:5]: (error) Returning pointer to local variable 'str' that will be invalid when returning.\n", errout.str());
ASSERT_EQUALS("[test.cpp:4] -> [test.cpp:3] -> [test.cpp:5]: (error) Returning pointer to local variable 'str' that will be invalid when returning.\n", errout.str());
check("class Fred {\n"
" char *foo();\n"
@ -1431,6 +1431,16 @@ private:
"}\n");
ASSERT_EQUALS("", errout.str());
check("void f(bool b) {\n"
" std::string s;\n"
" if(b) {\n"
" char buf[3];\n"
" s = buf;\n"
" }\n"
" std::cout << s;\n"
"}\n");
ASSERT_EQUALS("", errout.str());
check("int &a[];\n"
"void b(){int *c = a};\n");
ASSERT_EQUALS("", errout.str());