diff --git a/cfg/std.cfg b/cfg/std.cfg index 132f2b679..4cd2a00a0 100644 --- a/cfg/std.cfg +++ b/cfg/std.cfg @@ -1932,6 +1932,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1950,6 +2015,7 @@ + false diff --git a/lib/library.cpp b/lib/library.cpp index fa3a67579..ea900f631 100644 --- a/lib/library.cpp +++ b/lib/library.cpp @@ -388,6 +388,106 @@ Library::Error Library::load(const tinyxml2::XMLDocument &doc) } } + else if (nodename == "container") { + const char* const id = node->Attribute("id"); + if (!id) + return Error(MISSING_ATTRIBUTE, "id"); + + Container& container = containers[id]; + + const char* const inherits = node->Attribute("inherits"); + if (inherits) { + std::map::const_iterator i = containers.find(inherits); + if (inherits) + container = i->second; // Take values from parent and overwrite them if necessary + else + return Error(BAD_ATTRIBUTE_VALUE, inherits); + } + + const char* const startPattern = node->Attribute("startPattern"); + if (startPattern) + container.startPattern = startPattern; + const char* const endPattern = node->Attribute("endPattern"); + if (endPattern) + container.endPattern = endPattern; + + for (const tinyxml2::XMLElement *containerNode = node->FirstChildElement(); containerNode; containerNode = containerNode->NextSiblingElement()) { + const std::string containerNodeName = containerNode->Name(); + if (containerNodeName == "size" || containerNodeName == "access" || containerNodeName == "other") { + for (const tinyxml2::XMLElement *functionNode = containerNode->FirstChildElement(); functionNode; functionNode = functionNode->NextSiblingElement()) { + if (std::string(functionNode->Name()) != "function") + return Error(BAD_ELEMENT, functionNode->Name()); + + const char* const functionName = functionNode->Attribute("name"); + if (!functionName) + return Error(MISSING_ATTRIBUTE, "name"); + + const char* const action_ptr = functionNode->Attribute("action"); + Container::Action action = Container::NO_ACTION; + if (action_ptr) { + std::string actionName = action_ptr; + if (actionName == "resize") + action = Container::RESIZE; + else if (actionName == "clear") + action = Container::CLEAR; + else if (actionName == "push") + action = Container::PUSH; + else if (actionName == "pop") + action = Container::POP; + else + return Error(BAD_ATTRIBUTE_VALUE, actionName); + } + + const char* const yield_ptr = functionNode->Attribute("yields"); + Container::Yield yield = Container::NO_YIELD; + if (yield_ptr) { + std::string yieldName = yield_ptr; + if (yieldName == "at_index") + yield = Container::AT_INDEX; + else if (yieldName == "item") + yield = Container::ITEM; + else if (yieldName == "buffer") + yield = Container::BUFFER; + else if (yieldName == "buffer-nt") + yield = Container::BUFFER_NT; + else if (yieldName == "start-iterator") + yield = Container::START_ITERATOR; + else if (yieldName == "end-iterator") + yield = Container::END_ITERATOR; + else if (yieldName == "size") + yield = Container::SIZE; + else if (yieldName == "empty") + yield = Container::EMPTY; + else + return Error(BAD_ATTRIBUTE_VALUE, yieldName); + } + + container.functions[functionName].action = action; + container.functions[functionName].yield = yield; + } + + if (containerNodeName == "size") { + const char* const templateArg = containerNode->Attribute("templateParameter"); + if (templateArg) + container.size_templateArgNo = atoi(templateArg); + } else if (containerNodeName == "access") { + const char* const indexArg = containerNode->Attribute("indexOperator"); + if (indexArg) + container.arrayLike_indexOp = std::string(indexArg) == "array-like"; + } + } else if (containerNodeName == "type") { + const char* const templateArg = containerNode->Attribute("templateParameter"); + if (templateArg) + container.type_templateArgNo = atoi(templateArg); + + const char* const string = containerNode->Attribute("string"); + if (string) + container.stdStringLike = std::string(string) == "std-like"; + } else + return Error(BAD_ELEMENT, containerNodeName); + } + } + else if (nodename == "podtype") { const char * const name = node->Attribute("name"); if (!name) @@ -535,3 +635,25 @@ bool Library::isScopeNoReturn(const Token *end, std::string *unknownFunc) const return false; } +const Library::Container* Library::detectContainer(const Token* typeStart) const +{ + for (std::map::const_iterator i = containers.begin(); i != containers.end(); ++i) { + const Container& container = i->second; + if (container.startPattern.empty()) + continue; + + if (Token::Match(typeStart, container.startPattern.c_str())) { + if (container.endPattern.empty()) + return &container; + + for (const Token* tok = typeStart; tok && !tok->varId(); tok = tok->next()) { + if (tok->link()) { + if (Token::Match(tok->link(), container.endPattern.c_str())) + return &container; + break; + } + } + } + } + return nullptr; +} diff --git a/lib/library.h b/lib/library.h index 6560683ac..a1f787e02 100644 --- a/lib/library.h +++ b/lib/library.h @@ -137,6 +137,51 @@ public: bool isScopeNoReturn(const Token *end, std::string *unknownFunc) const; + class Container { + public: + Container() : + type_templateArgNo(-1), + size_templateArgNo(-1), + arrayLike_indexOp(false), + stdStringLike(false) { + } + + enum Action { + RESIZE, CLEAR, PUSH, POP, + NO_ACTION + }; + enum Yield { + AT_INDEX, ITEM, BUFFER, BUFFER_NT, START_ITERATOR, END_ITERATOR, SIZE, EMPTY, + NO_YIELD + }; + struct Function { + Action action; + Yield yield; + }; + std::string startPattern, endPattern; + std::map functions; + int type_templateArgNo; + int size_templateArgNo; + bool arrayLike_indexOp; + bool stdStringLike; + + Action getAction(const std::string& function) const { + std::map::const_iterator i = functions.find(function); + if (i != functions.end()) + return i->second.action; + return NO_ACTION; + } + + Yield getYield(const std::string& function) const { + std::map::const_iterator i = functions.find(function); + if (i != functions.end()) + return i->second.yield; + return NO_YIELD; + } + }; + std::map containers; + const Container* detectContainer(const Token* typeStart) const; + class ArgumentChecks { public: ArgumentChecks() : diff --git a/lib/token.cpp b/lib/token.cpp index 9eb671a49..077ff248b 100644 --- a/lib/token.cpp +++ b/lib/token.cpp @@ -788,6 +788,19 @@ Token* Token::nextArgumentBeforeCreateLinks2() const return 0; } +Token* Token::nextTemplateArgument() const +{ + for (const Token* tok = this; tok; tok = tok->next()) { + if (tok->str() == ",") + return tok->next(); + else if (tok->link() && Token::Match(tok, "(|{|[|<")) + tok = tok->link(); + else if (Token::Match(tok, ">|;")) + return 0; + } + return 0; +} + const Token * Token::findClosingBracket() const { const Token *closing = nullptr; diff --git a/lib/token.h b/lib/token.h index a7316663b..b071856a5 100644 --- a/lib/token.h +++ b/lib/token.h @@ -649,6 +649,13 @@ public: */ Token* nextArgumentBeforeCreateLinks2() const; + /** + * @return the first token of the next template argument. Does only work on template argument + * lists. Requires that Tokenizer::createLinks2() has been called before. + * Returns 0, if there is no next argument. + */ + Token* nextTemplateArgument() const; + /** * Returns the closing bracket of opening '<'. Should only be used if link() * is unavailable. diff --git a/man/manual.docbook b/man/manual.docbook index f60f7d443..6eecba9fb 100644 --- a/man/manual.docbook +++ b/man/manual.docbook @@ -920,6 +920,32 @@ Checking unusedvar.cpp... [unusedvar.cpp:2]: (style) Unused variable: a +
+ container + + A lot of C++ libraries, among those the STL itself, provide containers with very similar functionality. Libraries can be used to tell cppcheck about their behaviour. Each container needs a unique ID. It can optionally have a startPattern, which must be a valid Token::Match pattern and an endPattern that is compared to the linked token of the first token with such a link. The optional attribute "inherits" takes an ID from a previously defined 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". + + The following example provides a definition for std::vector, based on the definition of "stdContainer" (not shown): + + <?xml version="1.0"?> +<def> + <container id="stdVector" startPattern="std :: vector &lt;" inherits="stdContainer"> + <size> + <function name="push_back" action="push"/> + <function name="pop_back" action="pop"/> + </size> + <access indexOperator="array-like"> + <function name="at" yields="at_index"/> + <function name="front" yields="fixed"/> + <function name="back" yields="fixed"/> + </access> + </container> +</def> + +
+
Example configuration for strcpy() diff --git a/test/testlibrary.cpp b/test/testlibrary.cpp index 3e8a357f7..7b35de41b 100644 --- a/test/testlibrary.cpp +++ b/test/testlibrary.cpp @@ -39,6 +39,7 @@ private: TEST_CASE(memory2); // define extra "free" allocation functions TEST_CASE(resource); TEST_CASE(podtype); + TEST_CASE(container); TEST_CASE(version); } @@ -276,6 +277,80 @@ private: ASSERT_EQUALS(0, type ? type->sign : '?'); } + void container() const { + const char xmldata[] = "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" // Inherits all but templateParameter + " \n" + " \n" + " \n" + " \n" + " \n" + ""; + tinyxml2::XMLDocument doc; + doc.Parse(xmldata, sizeof(xmldata)); + + Library library; + library.load(doc); + + Library::Container& A = library.containers["A"]; + Library::Container& B = library.containers["B"]; + Library::Container& C = library.containers["C"]; + + ASSERT_EQUALS(A.type_templateArgNo, 1); + ASSERT_EQUALS(A.size_templateArgNo, 4); + ASSERT_EQUALS(A.startPattern, "std :: A <"); + ASSERT_EQUALS(A.endPattern, "> !!::"); + ASSERT_EQUALS(A.stdStringLike, false); + ASSERT_EQUALS(A.arrayLike_indexOp, false); + ASSERT_EQUALS(Library::Container::SIZE, A.getYield("size")); + ASSERT_EQUALS(Library::Container::EMPTY, A.getYield("empty")); + ASSERT_EQUALS(Library::Container::AT_INDEX, A.getYield("at")); + ASSERT_EQUALS(Library::Container::START_ITERATOR, A.getYield("begin")); + ASSERT_EQUALS(Library::Container::END_ITERATOR, A.getYield("end")); + ASSERT_EQUALS(Library::Container::BUFFER, A.getYield("data")); + ASSERT_EQUALS(Library::Container::BUFFER_NT, A.getYield("c_str")); + ASSERT_EQUALS(Library::Container::ITEM, A.getYield("front")); + ASSERT_EQUALS(Library::Container::NO_YIELD, A.getYield("foo")); + ASSERT_EQUALS(Library::Container::RESIZE, A.getAction("resize")); + ASSERT_EQUALS(Library::Container::CLEAR, A.getAction("clear")); + ASSERT_EQUALS(Library::Container::PUSH, A.getAction("push_back")); + ASSERT_EQUALS(Library::Container::POP, A.getAction("pop_back")); + ASSERT_EQUALS(Library::Container::NO_ACTION, A.getAction("foo")); + + ASSERT_EQUALS(B.type_templateArgNo, 1); + ASSERT_EQUALS(B.size_templateArgNo, 3); + ASSERT_EQUALS(B.startPattern, "std :: B <"); + ASSERT_EQUALS(B.endPattern, "> !!::"); + ASSERT_EQUALS(B.functions.size(), A.functions.size()); + + ASSERT(C.functions.empty()); + ASSERT_EQUALS(C.type_templateArgNo, -1); + ASSERT_EQUALS(C.size_templateArgNo, -1); + ASSERT_EQUALS(C.stdStringLike, true); + ASSERT_EQUALS(C.arrayLike_indexOp, true); + } + void version() const { { const char xmldata [] = "\n"