Add support for string_view (#3480)
This commit is contained in:
parent
71809044bd
commit
8668d445c7
|
@ -81,6 +81,7 @@
|
||||||
<container id="boostVector" startPattern="boost :: vector|small_vector <" inherits="stdVector"/>
|
<container id="boostVector" startPattern="boost :: vector|small_vector <" inherits="stdVector"/>
|
||||||
<container id="boostStableVector" startPattern="boost :: stable_vector|static_vector <" inherits="stdVectorDeque"/>
|
<container id="boostStableVector" startPattern="boost :: stable_vector|static_vector <" inherits="stdVectorDeque"/>
|
||||||
<container id="boostDeque" startPattern="boost :: deque <" inherits="stdDeque"/>
|
<container id="boostDeque" startPattern="boost :: deque <" inherits="stdDeque"/>
|
||||||
|
<container id="boostStringView" startPattern="boost :: string_view" inherits="stdStringView"/>
|
||||||
<!-- ########## Boost smart pointers ########## -->
|
<!-- ########## Boost smart pointers ########## -->
|
||||||
<!-- https://www.boost.org/doc/libs/1_70_0/libs/smart_ptr/doc/html/smart_ptr.html -->
|
<!-- https://www.boost.org/doc/libs/1_70_0/libs/smart_ptr/doc/html/smart_ptr.html -->
|
||||||
<smart-pointer class-name="boost::scoped_ptr">
|
<smart-pointer class-name="boost::scoped_ptr">
|
||||||
|
|
|
@ -416,6 +416,9 @@
|
||||||
<optional>
|
<optional>
|
||||||
<attribute name="hasInitializerListConstructor"><ref name="DATA-BOOL"/></attribute>
|
<attribute name="hasInitializerListConstructor"><ref name="DATA-BOOL"/></attribute>
|
||||||
</optional>
|
</optional>
|
||||||
|
<optional>
|
||||||
|
<attribute name="view"><ref name="DATA-BOOL"/></attribute>
|
||||||
|
</optional>
|
||||||
<optional>
|
<optional>
|
||||||
<attribute name="itEndPattern"><text/></attribute>
|
<attribute name="itEndPattern"><text/></attribute>
|
||||||
</optional>
|
</optional>
|
||||||
|
|
12
cfg/std.cfg
12
cfg/std.cfg
|
@ -8305,6 +8305,18 @@ initializer list (7) string& replace (const_iterator i1, const_iterator i2, init
|
||||||
<type templateParameter="0"/>
|
<type templateParameter="0"/>
|
||||||
</container>
|
</container>
|
||||||
<container id="stdString" startPattern="std :: string|wstring|u16string|u32string" endPattern="" inherits="stdAllString"/>
|
<container id="stdString" startPattern="std :: string|wstring|u16string|u32string" endPattern="" inherits="stdAllString"/>
|
||||||
|
<container id="stdAllStringView" inherits="stdAllString" view="true">
|
||||||
|
<size>
|
||||||
|
<function name="remove_prefix" action="change"/>
|
||||||
|
<function name="remove_suffix" action="change"/>
|
||||||
|
</size>
|
||||||
|
</container>
|
||||||
|
<container id="stdBasicStringView" startPattern="std :: basic_string_view <" inherits="stdAllStringView">
|
||||||
|
<type templateParameter="0"/>
|
||||||
|
</container>
|
||||||
|
<container id="stdStringView" startPattern="std :: string_view|wstring_view|u16string_view|u32string_view" endPattern="" inherits="stdAllStringView"/>
|
||||||
|
<container id="stdExperimentalStringView" startPattern="std :: experimental :: string_view|wstring_view|u16string_view|u32string_view" endPattern="" inherits="stdAllStringView"/>
|
||||||
|
<container id="stdExperimentalBasicStringView" startPattern="std :: experimental :: basic_string_view <" inherits="stdBasicStringView" />
|
||||||
<smart-pointer class-name="std::auto_ptr">
|
<smart-pointer class-name="std::auto_ptr">
|
||||||
<unique/>
|
<unique/>
|
||||||
</smart-pointer>
|
</smart-pointer>
|
||||||
|
|
|
@ -249,9 +249,18 @@ bool astIsIterator(const Token *tok)
|
||||||
return tok && tok->valueType() && tok->valueType()->type == ValueType::Type::ITERATOR;
|
return tok && tok->valueType() && tok->valueType()->type == ValueType::Type::ITERATOR;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool astIsContainer(const Token *tok)
|
bool astIsContainer(const Token* tok) {
|
||||||
|
return getLibraryContainer(tok) != nullptr && !astIsIterator(tok);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool astIsContainerView(const Token* tok)
|
||||||
{
|
{
|
||||||
return getLibraryContainer(tok) != nullptr && tok->valueType()->type != ValueType::Type::ITERATOR;
|
const Library::Container* container = getLibraryContainer(tok);
|
||||||
|
return container && !astIsIterator(tok) && container->view;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool astIsContainerOwned(const Token* tok) {
|
||||||
|
return astIsContainer(tok) && !astIsContainerView(tok);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string astCanonicalType(const Token *expr)
|
std::string astCanonicalType(const Token *expr)
|
||||||
|
|
|
@ -85,6 +85,9 @@ bool astIsIterator(const Token *tok);
|
||||||
|
|
||||||
bool astIsContainer(const Token *tok);
|
bool astIsContainer(const Token *tok);
|
||||||
|
|
||||||
|
bool astIsContainerView(const Token* tok);
|
||||||
|
bool astIsContainerOwned(const Token* tok);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get canonical type of expression. const/static/etc are not included and neither *&.
|
* Get canonical type of expression. const/static/etc are not included and neither *&.
|
||||||
* For example:
|
* For example:
|
||||||
|
|
|
@ -558,14 +558,13 @@ void CheckAutoVariables::checkVarLifetimeScope(const Token * start, const Token
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
const bool escape = Token::Match(tok->astParent(), "return|throw");
|
||||||
for (const ValueFlow::Value& val:tok->values()) {
|
for (const ValueFlow::Value& val:tok->values()) {
|
||||||
if (!val.isLocalLifetimeValue() && !val.isSubFunctionLifetimeValue())
|
if (!val.isLocalLifetimeValue() && !val.isSubFunctionLifetimeValue())
|
||||||
continue;
|
continue;
|
||||||
if (!printInconclusive && val.isInconclusive())
|
if (!printInconclusive && val.isInconclusive())
|
||||||
continue;
|
continue;
|
||||||
const bool escape = Token::Match(tok->astParent(), "return|throw");
|
|
||||||
for (const LifetimeToken& lt :
|
for (const LifetimeToken& lt :
|
||||||
getLifetimeTokens(getParentLifetime(val.tokvalue), escape || isAssignedToNonLocal(tok))) {
|
getLifetimeTokens(getParentLifetime(val.tokvalue), escape || isAssignedToNonLocal(tok))) {
|
||||||
const Token * tokvalue = lt.token;
|
const Token * tokvalue = lt.token;
|
||||||
|
@ -575,7 +574,8 @@ void CheckAutoVariables::checkVarLifetimeScope(const Token * start, const Token
|
||||||
continue;
|
continue;
|
||||||
if (!isLifetimeBorrowed(tok, mSettings))
|
if (!isLifetimeBorrowed(tok, mSettings))
|
||||||
continue;
|
continue;
|
||||||
if (tokvalue->exprId() == tok->exprId() && !(tok->variable() && tok->variable()->isArray()))
|
if (tokvalue->exprId() == tok->exprId() && !(tok->variable() && tok->variable()->isArray()) &&
|
||||||
|
!astIsContainerView(tok->astParent()))
|
||||||
continue;
|
continue;
|
||||||
if ((tokvalue->variable() && !isEscapedReference(tokvalue->variable()) &&
|
if ((tokvalue->variable() && !isEscapedReference(tokvalue->variable()) &&
|
||||||
isInScope(tokvalue->variable()->nameToken(), scope)) ||
|
isInScope(tokvalue->variable()->nameToken(), scope)) ||
|
||||||
|
|
|
@ -439,6 +439,9 @@ Library::Error Library::load(const tinyxml2::XMLDocument &doc)
|
||||||
const char* const hasInitializerListConstructor = node->Attribute("hasInitializerListConstructor");
|
const char* const hasInitializerListConstructor = node->Attribute("hasInitializerListConstructor");
|
||||||
if (hasInitializerListConstructor)
|
if (hasInitializerListConstructor)
|
||||||
container.hasInitializerListConstructor = std::string(hasInitializerListConstructor) == "true";
|
container.hasInitializerListConstructor = std::string(hasInitializerListConstructor) == "true";
|
||||||
|
const char* const view = node->Attribute("view");
|
||||||
|
if (view)
|
||||||
|
container.view = std::string(view) == "true";
|
||||||
|
|
||||||
for (const tinyxml2::XMLElement *containerNode = node->FirstChildElement(); containerNode; containerNode = containerNode->NextSiblingElement()) {
|
for (const tinyxml2::XMLElement *containerNode = node->FirstChildElement(); containerNode; containerNode = containerNode->NextSiblingElement()) {
|
||||||
const std::string containerNodeName = containerNode->Name();
|
const std::string containerNodeName = containerNode->Name();
|
||||||
|
|
|
@ -204,8 +204,8 @@ public:
|
||||||
|
|
||||||
class Container {
|
class Container {
|
||||||
public:
|
public:
|
||||||
Container() :
|
Container()
|
||||||
type_templateArgNo(-1),
|
: type_templateArgNo(-1),
|
||||||
size_templateArgNo(-1),
|
size_templateArgNo(-1),
|
||||||
arrayLike_indexOp(false),
|
arrayLike_indexOp(false),
|
||||||
stdStringLike(false),
|
stdStringLike(false),
|
||||||
|
@ -213,14 +213,33 @@ public:
|
||||||
opLessAllowed(true),
|
opLessAllowed(true),
|
||||||
hasInitializerListConstructor(false),
|
hasInitializerListConstructor(false),
|
||||||
unstableErase(false),
|
unstableErase(false),
|
||||||
unstableInsert(false) {}
|
unstableInsert(false),
|
||||||
|
view(false)
|
||||||
|
{}
|
||||||
|
|
||||||
enum class Action {
|
enum class Action {
|
||||||
RESIZE, CLEAR, PUSH, POP, FIND, INSERT, ERASE, CHANGE_CONTENT, CHANGE, CHANGE_INTERNAL,
|
RESIZE,
|
||||||
|
CLEAR,
|
||||||
|
PUSH,
|
||||||
|
POP,
|
||||||
|
FIND,
|
||||||
|
INSERT,
|
||||||
|
ERASE,
|
||||||
|
CHANGE_CONTENT,
|
||||||
|
CHANGE,
|
||||||
|
CHANGE_INTERNAL,
|
||||||
NO_ACTION
|
NO_ACTION
|
||||||
};
|
};
|
||||||
enum class Yield {
|
enum class Yield {
|
||||||
AT_INDEX, ITEM, BUFFER, BUFFER_NT, START_ITERATOR, END_ITERATOR, ITERATOR, SIZE, EMPTY,
|
AT_INDEX,
|
||||||
|
ITEM,
|
||||||
|
BUFFER,
|
||||||
|
BUFFER_NT,
|
||||||
|
START_ITERATOR,
|
||||||
|
END_ITERATOR,
|
||||||
|
ITERATOR,
|
||||||
|
SIZE,
|
||||||
|
EMPTY,
|
||||||
NO_YIELD
|
NO_YIELD
|
||||||
};
|
};
|
||||||
struct Function {
|
struct Function {
|
||||||
|
@ -238,6 +257,7 @@ public:
|
||||||
bool hasInitializerListConstructor;
|
bool hasInitializerListConstructor;
|
||||||
bool unstableErase;
|
bool unstableErase;
|
||||||
bool unstableInsert;
|
bool unstableInsert;
|
||||||
|
bool view;
|
||||||
|
|
||||||
Action getAction(const std::string& function) const {
|
Action getAction(const std::string& function) const {
|
||||||
const std::map<std::string, Function>::const_iterator i = functions.find(function);
|
const std::map<std::string, Function>::const_iterator i = functions.find(function);
|
||||||
|
|
|
@ -3031,22 +3031,32 @@ static bool isNotLifetimeValue(const ValueFlow::Value& val)
|
||||||
return !val.isLifetimeValue();
|
return !val.isLifetimeValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool isLifetimeOwned(const ValueType* vtParent)
|
||||||
|
{
|
||||||
|
if (vtParent->container)
|
||||||
|
return !vtParent->container->view;
|
||||||
|
return vtParent->type == ValueType::CONTAINER;
|
||||||
|
}
|
||||||
|
|
||||||
static bool isLifetimeOwned(const ValueType *vt, const ValueType *vtParent)
|
static bool isLifetimeOwned(const ValueType *vt, const ValueType *vtParent)
|
||||||
{
|
{
|
||||||
if (!vtParent)
|
if (!vtParent)
|
||||||
return false;
|
return false;
|
||||||
if (!vt) {
|
if (!vt) {
|
||||||
if (vtParent->type == ValueType::CONTAINER)
|
if (isLifetimeOwned(vtParent))
|
||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// If converted from iterator to pointer then the iterator is most likely a pointer
|
||||||
|
if (vtParent->pointer == 1 && vt->pointer == 0 && vt->type == ValueType::ITERATOR)
|
||||||
|
return false;
|
||||||
if (vt->type != ValueType::UNKNOWN_TYPE && vtParent->type != ValueType::UNKNOWN_TYPE) {
|
if (vt->type != ValueType::UNKNOWN_TYPE && vtParent->type != ValueType::UNKNOWN_TYPE) {
|
||||||
if (vt->pointer != vtParent->pointer)
|
if (vt->pointer != vtParent->pointer)
|
||||||
return true;
|
return true;
|
||||||
if (vt->type != vtParent->type) {
|
if (vt->type != vtParent->type) {
|
||||||
if (vtParent->type == ValueType::RECORD)
|
if (vtParent->type == ValueType::RECORD)
|
||||||
return true;
|
return true;
|
||||||
if (vtParent->type == ValueType::CONTAINER)
|
if (isLifetimeOwned(vtParent))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3062,6 +3072,8 @@ static bool isLifetimeBorrowed(const ValueType *vt, const ValueType *vtParent)
|
||||||
return false;
|
return false;
|
||||||
if (vt->pointer > 0 && vt->pointer == vtParent->pointer)
|
if (vt->pointer > 0 && vt->pointer == vtParent->pointer)
|
||||||
return true;
|
return true;
|
||||||
|
if (vtParent->container && vtParent->container->view)
|
||||||
|
return true;
|
||||||
if (vt->type != ValueType::UNKNOWN_TYPE && vtParent->type != ValueType::UNKNOWN_TYPE && vtParent->container == vt->container) {
|
if (vt->type != ValueType::UNKNOWN_TYPE && vtParent->type != ValueType::UNKNOWN_TYPE && vtParent->container == vt->container) {
|
||||||
if (vtParent->pointer > vt->pointer)
|
if (vtParent->pointer > vt->pointer)
|
||||||
return true;
|
return true;
|
||||||
|
@ -3146,6 +3158,42 @@ static bool isDifferentType(const Token* src, const Token* dst)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::vector<ValueType> getParentValueTypes(const Token* tok, const Settings* settings = nullptr)
|
||||||
|
{
|
||||||
|
if (!tok)
|
||||||
|
return {};
|
||||||
|
if (!tok->astParent())
|
||||||
|
return {};
|
||||||
|
if (Token::Match(tok->astParent(), "(|{|,")) {
|
||||||
|
int argn = -1;
|
||||||
|
const Token* ftok = getTokenArgumentFunction(tok, argn);
|
||||||
|
if (ftok && ftok->function()) {
|
||||||
|
std::vector<ValueType> result;
|
||||||
|
std::vector<const Variable*> argsVars = getArgumentVars(ftok, argn);
|
||||||
|
for (const Variable* var : getArgumentVars(ftok, argn)) {
|
||||||
|
if (!var)
|
||||||
|
continue;
|
||||||
|
if (!var->valueType())
|
||||||
|
continue;
|
||||||
|
result.push_back(*var->valueType());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (settings && Token::Match(tok->astParent()->tokAt(-2), ". push_back|push_front|insert|push (") &&
|
||||||
|
astIsContainer(tok->astParent()->tokAt(-2)->astOperand1())) {
|
||||||
|
const Token* contTok = tok->astParent()->tokAt(-2)->astOperand1();
|
||||||
|
const ValueType* vtCont = contTok->valueType();
|
||||||
|
if (!vtCont->containerTypeToken)
|
||||||
|
return {};
|
||||||
|
ValueType vtParent = ValueType::parseDecl(vtCont->containerTypeToken, settings);
|
||||||
|
return {std::move(vtParent)};
|
||||||
|
}
|
||||||
|
if (tok->astParent()->valueType())
|
||||||
|
return {*tok->astParent()->valueType()};
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
bool isLifetimeBorrowed(const Token *tok, const Settings *settings)
|
bool isLifetimeBorrowed(const Token *tok, const Settings *settings)
|
||||||
{
|
{
|
||||||
if (!tok)
|
if (!tok)
|
||||||
|
@ -3605,6 +3653,13 @@ static void valueFlowLifetimeFunction(Token *tok, TokenList *tokenlist, ErrorLog
|
||||||
}
|
}
|
||||||
if (update)
|
if (update)
|
||||||
valueFlowForwardLifetime(tok->next(), tokenlist, errorLogger, settings);
|
valueFlowForwardLifetime(tok->next(), tokenlist, errorLogger, settings);
|
||||||
|
} else if (tok->valueType()) {
|
||||||
|
// TODO: Propagate lifetimes with library functions
|
||||||
|
if (settings->library.getFunction(tok->previous()))
|
||||||
|
return;
|
||||||
|
// Assume constructing the valueType
|
||||||
|
valueFlowLifetimeConstructor(tok, tokenlist, errorLogger, settings);
|
||||||
|
valueFlowForwardLifetime(tok->next(), tokenlist, errorLogger, settings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3673,7 +3728,13 @@ static void valueFlowLifetimeConstructor(Token* tok, TokenList* tokenlist, Error
|
||||||
Token* parent = tok->astParent();
|
Token* parent = tok->astParent();
|
||||||
while (Token::simpleMatch(parent, ","))
|
while (Token::simpleMatch(parent, ","))
|
||||||
parent = parent->astParent();
|
parent = parent->astParent();
|
||||||
if (Token::simpleMatch(parent, "{") && hasInitList(parent->astParent())) {
|
if (Token::Match(tok, "{|(") && astIsContainerView(tok) && !tok->function()) {
|
||||||
|
std::vector<const Token*> args = getArguments(tok);
|
||||||
|
if (args.size() == 1 && astIsContainerOwned(args.front())) {
|
||||||
|
LifetimeStore{args.front(), "Passed to container view.", ValueFlow::Value::LifetimeKind::SubObject}.byRef(
|
||||||
|
tok, tokenlist, errorLogger, settings);
|
||||||
|
}
|
||||||
|
} else if (Token::simpleMatch(parent, "{") && hasInitList(parent->astParent())) {
|
||||||
valueFlowLifetimeConstructor(tok, Token::typeOf(parent->previous()), tokenlist, errorLogger, settings);
|
valueFlowLifetimeConstructor(tok, Token::typeOf(parent->previous()), tokenlist, errorLogger, settings);
|
||||||
} else if (Token::simpleMatch(tok, "{") && hasInitList(parent)) {
|
} else if (Token::simpleMatch(tok, "{") && hasInitList(parent)) {
|
||||||
std::vector<const Token *> args = getArguments(tok);
|
std::vector<const Token *> args = getArguments(tok);
|
||||||
|
@ -3764,6 +3825,16 @@ static bool isDecayedPointer(const Token *tok)
|
||||||
return astIsPointer(tok->astParent());
|
return astIsPointer(tok->astParent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool isConvertedToView(const Token* tok, const Settings* settings)
|
||||||
|
{
|
||||||
|
std::vector<ValueType> vtParents = getParentValueTypes(tok, settings);
|
||||||
|
return std::any_of(vtParents.begin(), vtParents.end(), [&](const ValueType& vt) {
|
||||||
|
if (!vt.container)
|
||||||
|
return false;
|
||||||
|
return vt.container->view;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
static void valueFlowLifetime(TokenList *tokenlist, SymbolDatabase*, ErrorLogger *errorLogger, const Settings *settings)
|
static void valueFlowLifetime(TokenList *tokenlist, SymbolDatabase*, ErrorLogger *errorLogger, const Settings *settings)
|
||||||
{
|
{
|
||||||
for (Token *tok = tokenlist->front(); tok; tok = tok->next()) {
|
for (Token *tok = tokenlist->front(); tok; tok = tok->next()) {
|
||||||
|
@ -3861,6 +3932,13 @@ static void valueFlowLifetime(TokenList *tokenlist, SymbolDatabase*, ErrorLogger
|
||||||
valueFlowForwardLifetime(tok, tokenlist, errorLogger, settings);
|
valueFlowForwardLifetime(tok, tokenlist, errorLogger, settings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Converting to container view
|
||||||
|
else if (astIsContainerOwned(tok) && isConvertedToView(tok, settings)) {
|
||||||
|
LifetimeStore ls =
|
||||||
|
LifetimeStore{tok, "Converted to container view", ValueFlow::Value::LifetimeKind::SubObject};
|
||||||
|
ls.byRef(tok, tokenlist, errorLogger, settings);
|
||||||
|
valueFlowForwardLifetime(tok, tokenlist, errorLogger, settings);
|
||||||
|
}
|
||||||
// container lifetimes
|
// container lifetimes
|
||||||
else if (astIsContainer(tok)) {
|
else if (astIsContainer(tok)) {
|
||||||
Token * parent = astParentSkipParens(tok);
|
Token * parent = astParentSkipParens(tok);
|
||||||
|
@ -3902,6 +3980,16 @@ static void valueFlowLifetime(TokenList *tokenlist, SymbolDatabase*, ErrorLogger
|
||||||
continue;
|
continue;
|
||||||
toks.push_back(v.tokvalue);
|
toks.push_back(v.tokvalue);
|
||||||
}
|
}
|
||||||
|
} else if (astIsContainerView(tok)) {
|
||||||
|
for (const ValueFlow::Value& v : tok->values()) {
|
||||||
|
if (!v.isLifetimeValue())
|
||||||
|
continue;
|
||||||
|
if (!v.tokvalue)
|
||||||
|
continue;
|
||||||
|
if (!astIsContainerOwned(v.tokvalue))
|
||||||
|
continue;
|
||||||
|
toks.push_back(v.tokvalue);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
toks = {tok};
|
toks = {tok};
|
||||||
}
|
}
|
||||||
|
|
|
@ -580,6 +580,8 @@ A lot of C++ libraries, among those the STL itself, provide containers with very
|
||||||
|
|
||||||
The `hasInitializerListConstructor` attribute can be set when the container has a constructor taking an initializer list.
|
The `hasInitializerListConstructor` attribute can be set when the container has a constructor taking an initializer list.
|
||||||
|
|
||||||
|
The `view` attribute can be set when the container is a view, which means it borrows the lifetime of another container.
|
||||||
|
|
||||||
Inside the `<container>` tag, functions can be defined inside of the tags `<size>`, `<access>` and `<other>` (on your choice). Each of them can specify an action like "resize" and/or the result it yields, for example "end-iterator".
|
Inside the `<container>` tag, functions can be defined inside of the tags `<size>`, `<access>` and `<other>` (on your choice). Each of them can specify an action like "resize" and/or the result it yields, for example "end-iterator".
|
||||||
|
|
||||||
The following example provides a definition for std::vector, based on the definition of "stdContainer" (not shown):
|
The following example provides a definition for std::vector, based on the definition of "stdContainer" (not shown):
|
||||||
|
|
|
@ -1 +1,3 @@
|
||||||
release notes for cppcheck-2.7
|
release notes for cppcheck-2.7
|
||||||
|
|
||||||
|
Add support for container views. The `view` attribute has been added to the `<container>` library tag to specify the class is a view. The lifetime analysis has been updated to use this new attribute to find dangling lifetime containers.
|
||||||
|
|
|
@ -141,6 +141,7 @@ private:
|
||||||
|
|
||||||
TEST_CASE(danglingLifetimeLambda);
|
TEST_CASE(danglingLifetimeLambda);
|
||||||
TEST_CASE(danglingLifetimeContainer);
|
TEST_CASE(danglingLifetimeContainer);
|
||||||
|
TEST_CASE(danglingLifetimeContainerView);
|
||||||
TEST_CASE(danglingLifetime);
|
TEST_CASE(danglingLifetime);
|
||||||
TEST_CASE(danglingLifetimeFunction);
|
TEST_CASE(danglingLifetimeFunction);
|
||||||
TEST_CASE(danglingLifetimeAggegrateConstructor);
|
TEST_CASE(danglingLifetimeAggegrateConstructor);
|
||||||
|
@ -2444,6 +2445,102 @@ private:
|
||||||
ASSERT_EQUALS("", errout.str());
|
ASSERT_EQUALS("", errout.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void danglingLifetimeContainerView()
|
||||||
|
{
|
||||||
|
check("std::string_view f() {\n"
|
||||||
|
" std::string s = \"\";\n"
|
||||||
|
" return s;\n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS(
|
||||||
|
"[test.cpp:3] -> [test.cpp:2] -> [test.cpp:3]: (error) Returning object that points to local variable 's' that will be invalid when returning.\n",
|
||||||
|
errout.str());
|
||||||
|
|
||||||
|
check("std::string_view f() {\n"
|
||||||
|
" std::string s = \"\";\n"
|
||||||
|
" std::string_view sv = s;\n"
|
||||||
|
" return sv;\n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS(
|
||||||
|
"[test.cpp:3] -> [test.cpp:2] -> [test.cpp:4]: (error) Returning object that points to local variable 's' that will be invalid when returning.\n",
|
||||||
|
errout.str());
|
||||||
|
|
||||||
|
check("std::string_view f() {\n"
|
||||||
|
" std::string s = \"\";\n"
|
||||||
|
" return std::string_view{s};\n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS(
|
||||||
|
"[test.cpp:3] -> [test.cpp:2] -> [test.cpp:3]: (error) Returning object that points to local variable 's' that will be invalid when returning.\n",
|
||||||
|
errout.str());
|
||||||
|
|
||||||
|
check("std::string_view f(std::string_view s) {\n"
|
||||||
|
" return s;\n"
|
||||||
|
"}\n"
|
||||||
|
"std::string_view g() {\n"
|
||||||
|
" std::string s = \"\";\n"
|
||||||
|
" return f(s);\n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS(
|
||||||
|
"[test.cpp:6] -> [test.cpp:6] -> [test.cpp:5] -> [test.cpp:6]: (error) Returning object that points to local variable 's' that will be invalid when returning.\n",
|
||||||
|
errout.str());
|
||||||
|
|
||||||
|
check("const char * f() {\n"
|
||||||
|
" std::string s;\n"
|
||||||
|
" std::string_view sv = s;\n"
|
||||||
|
" return sv.begin();\n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS(
|
||||||
|
"[test.cpp:4] -> [test.cpp:2] -> [test.cpp:4]: (error) Returning iterator to local container 's' that will be invalid when returning.\n",
|
||||||
|
errout.str());
|
||||||
|
|
||||||
|
check("const char * f() {\n"
|
||||||
|
" std::string s;\n"
|
||||||
|
" return std::string_view{s}.begin();\n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS(
|
||||||
|
"[test.cpp:3] -> [test.cpp:2] -> [test.cpp:3]: (error) Returning iterator to local container 's' that will be invalid when returning.\n",
|
||||||
|
errout.str());
|
||||||
|
|
||||||
|
check("const char * f() {\n"
|
||||||
|
" std::string s;\n"
|
||||||
|
" return std::string_view(s).begin();\n"
|
||||||
|
"}\n");
|
||||||
|
TODO_ASSERT_EQUALS(
|
||||||
|
"[test.cpp:3] -> [test.cpp:2] -> [test.cpp:3]: (error) Returning iterator to local container 's' that will be invalid when returning.\n",
|
||||||
|
"",
|
||||||
|
errout.str());
|
||||||
|
|
||||||
|
check("const char * f(std::string_view sv) {\n"
|
||||||
|
" return sv.begin();\n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS("", errout.str());
|
||||||
|
|
||||||
|
check("const char * f(std::string s) {\n"
|
||||||
|
" std::string_view sv = s;\n"
|
||||||
|
" return sv.begin();\n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS(
|
||||||
|
"[test.cpp:3] -> [test.cpp:1] -> [test.cpp:3]: (error) Returning iterator to local container 's' that will be invalid when returning.\n",
|
||||||
|
errout.str());
|
||||||
|
|
||||||
|
check("std::string_view f(std::string s) {\n"
|
||||||
|
" return s;\n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS(
|
||||||
|
"[test.cpp:2] -> [test.cpp:1] -> [test.cpp:2]: (error) Returning object that points to local variable 's' that will be invalid when returning.\n",
|
||||||
|
errout.str());
|
||||||
|
|
||||||
|
check("const char * f(const std::string& s) {\n"
|
||||||
|
" std::string_view sv = s;\n"
|
||||||
|
" return sv.begin();\n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS("", errout.str());
|
||||||
|
|
||||||
|
check("std::string_view f(const std::string_view& sv) {\n"
|
||||||
|
" return sv;\n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS("", errout.str());
|
||||||
|
}
|
||||||
|
|
||||||
void danglingLifetime() {
|
void danglingLifetime() {
|
||||||
check("auto f() {\n"
|
check("auto f() {\n"
|
||||||
" std::vector<int> a;\n"
|
" std::vector<int> a;\n"
|
||||||
|
|
Loading…
Reference in New Issue