diff --git a/lib/checkclass.cpp b/lib/checkclass.cpp index 77f16ff8e..e34bff57d 100644 --- a/lib/checkclass.cpp +++ b/lib/checkclass.cpp @@ -1398,6 +1398,85 @@ void CheckClass::thisSubtraction() } } + +void CheckClass::checkConst() +{ + for (const Token *tok = _tokenizer->tokens(); tok; tok = tok->next()) + { + if (Token::Match(tok, "class %var% :|{")) + { + // get class name.. + const std::string classname(tok->strAt(1)); + + // goto initial {' + while (tok && tok->str() != "{") + tok = tok->next(); + if (!tok) + break; + + // parse in this class definition to see if there are any simple getter functions + for (const Token *tok2 = tok->next(); tok2; tok2 = tok2->next()) + { + if (tok2->str() == "{") + tok2 = tok2->link(); + else if (tok2->str() == "}") + break; + + // member function? + if (Token::Match(tok2, "[;}] %type% %var% (")) + { + // get function name + const std::string functionName(tok2->strAt(2)); + + // goto the ')' + tok2 = tok2->tokAt(3)->link(); + if (!tok2) + break; + + // is this function implemented inline? + if (Token::simpleMatch(tok2, ") {")) + { + // if the function doesn't have any assignment nor function call, + // it can be a const function.. + unsigned int indentlevel = 0; + bool isconst = true; + for (const Token *tok3 = tok2; tok3; tok3 = tok3->next()) + { + if (tok3->str() == "{") + ++indentlevel; + else if (tok3->str() == "}") + { + if (indentlevel <= 1) + break; + --indentlevel; + } + else if (tok3->str() == "=" || + Token::Match(tok, "%var% (")) + { + isconst = false; + break; + } + } + + // nothing non-const was found. write error.. + if (isconst) + checkConstError(tok2, classname, functionName); + } + } + } + + } + } + +} + +void CheckClass::checkConstError(const Token *tok, const std::string &classname, const std::string &funcname) +{ + reportError(tok, Severity::style, "functionConst", "The function '" + classname + "::" + funcname + "' can be const"); +} + + + void CheckClass::noConstructorError(const Token *tok, const std::string &classname, bool isStruct) { reportError(tok, Severity::style, "noConstructor", "The " + std::string(isStruct ? "struct" : "class") + " '" + classname + "' has no constructor. Member variables not initialized."); diff --git a/lib/checkclass.h b/lib/checkclass.h index e248a9a38..450543050 100644 --- a/lib/checkclass.h +++ b/lib/checkclass.h @@ -96,6 +96,9 @@ public: /** @brief warn for "this-x". The indented code may be "this->x" */ void thisSubtraction(); + + /** @brief can member function be const? */ + void checkConst(); private: /** @brief Information about a member variable. Used when checking for uninitialized variables */ @@ -161,6 +164,8 @@ private: void operatorEqRetRefThisError(const Token *tok); void operatorEqToSelfError(const Token *tok); + void checkConstError(const Token *tok, const std::string &classname, const std::string &funcname); + void getErrorMessages() { noConstructorError(0, "classname", false); @@ -174,6 +179,7 @@ private: thisSubtractionError(0); operatorEqRetRefThisError(0); operatorEqToSelfError(0); + checkConstError(0, "class", "function"); } std::string name() const @@ -189,7 +195,8 @@ private: "* [[CheckMemset|Warn if memset, memcpy etc are used on a class]]\n" "* If it's a base class, check that the destructor is virtual\n" "* The operator= should return a constant reference to itself\n" - "* Are there unused private functions\n"; + "* Are there unused private functions\n" + "* Constness for member functions\n"; } }; /// @} diff --git a/test/testclass.cpp b/test/testclass.cpp index 239192aa0..958e2380e 100644 --- a/test/testclass.cpp +++ b/test/testclass.cpp @@ -78,6 +78,9 @@ private: TEST_CASE(memsetOnClass); TEST_CASE(this_subtraction); // warn about "this-x" + + // can member function be made const + TEST_CASE(const1); } // Check the operator Equal @@ -1501,6 +1504,32 @@ private: ASSERT_EQUALS("[test.cpp:2]: (possible style) Suspicious pointer subtraction\n" "[test.cpp:3]: (possible style) Suspicious pointer subtraction\n", errout.str()); } + + void checkConst(const char code[]) + { + // Tokenize.. + Tokenizer tokenizer; + std::istringstream istr(code); + tokenizer.tokenize(istr, "test.cpp"); + tokenizer.simplifyTokenList(); + + // Clear the error log + errout.str(""); + + // Check.. + Settings settings; + CheckClass checkClass(&tokenizer, &settings, this); + checkClass.checkConst(); + } + + void const1() + { + checkConst("class Fred {\n" + " int a;\n" + " int getA() { return a; }\n" + "};\n"); + ASSERT_EQUALS("[test.cpp:3]: (style) The function 'Fred::getA' can be const\n", errout.str()); + } }; REGISTER_TEST(TestClass)