From 91070ca794a4cee98e66efa43119311f5e7fef15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Fri, 8 Sep 2023 19:30:25 +0200 Subject: [PATCH] utils.h: added `startsWith()` and started using it (#5381) This makes the code much more readable. It also makes it less prone to errors because we do not need to specify the length of the string to match and the returnvalue is clear. The code with the bad returnvalue check was never executed and I added a test to show that. --- Makefile | 2 +- cli/cmdlineparser.cpp | 2 +- gui/checkthread.cpp | 5 +++-- lib/astutils.cpp | 2 +- lib/checkleakautovar.cpp | 5 +++-- lib/checkunusedvar.cpp | 2 +- lib/clangimport.cpp | 20 ++++++++--------- lib/cppcheck.cpp | 12 +++++----- lib/errorlogger.cpp | 4 ++-- lib/importproject.cpp | 41 ++++++++-------------------------- lib/library.cpp | 4 ++-- lib/path.cpp | 4 ++-- lib/preprocessor.cpp | 5 +++-- lib/suppressions.cpp | 6 ++--- lib/symboldatabase.cpp | 2 +- lib/symboldatabase.h | 3 ++- lib/timer.cpp | 4 +++- lib/tokenize.cpp | 12 +++++----- lib/utils.h | 11 +++++++++ lib/valueflow.cpp | 2 +- test/cli/test-more-projects.py | 29 +++++++++++++++++++++++- test/testcmdlineparser.cpp | 9 ++++---- test/testsimplifyusing.cpp | 3 ++- test/testutils.cpp | 14 ++++++++++++ tools/dmake.cpp | 12 +++++----- 25 files changed, 126 insertions(+), 89 deletions(-) diff --git a/Makefile b/Makefile index d1aa9cc09..c1d8aac3a 100644 --- a/Makefile +++ b/Makefile @@ -618,7 +618,7 @@ $(libcppdir)/symboldatabase.o: lib/symboldatabase.cpp lib/astutils.h lib/color.h $(libcppdir)/templatesimplifier.o: lib/templatesimplifier.cpp lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/templatesimplifier.cpp -$(libcppdir)/timer.o: lib/timer.cpp lib/config.h lib/timer.h +$(libcppdir)/timer.o: lib/timer.cpp lib/config.h lib/timer.h lib/utils.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/timer.cpp $(libcppdir)/token.o: lib/token.cpp lib/astutils.h lib/config.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenlist.h lib/tokenrange.h lib/utils.h lib/valueflow.h lib/vfvalue.h diff --git a/cli/cmdlineparser.cpp b/cli/cmdlineparser.cpp index 54a2e8af7..a40ad42fd 100644 --- a/cli/cmdlineparser.cpp +++ b/cli/cmdlineparser.cpp @@ -1418,5 +1418,5 @@ void CmdLineParser::printHelp() bool CmdLineParser::isCppcheckPremium() const { if (mSettings.cppcheckCfgProductName.empty()) mSettings.loadCppcheckCfg(); - return mSettings.cppcheckCfgProductName.compare(0, 16, "Cppcheck Premium") == 0; + return startsWith(mSettings.cppcheckCfgProductName, "Cppcheck Premium"); } diff --git a/gui/checkthread.cpp b/gui/checkthread.cpp index 0a8fbf346..f59897897 100644 --- a/gui/checkthread.cpp +++ b/gui/checkthread.cpp @@ -27,6 +27,7 @@ #include "settings.h" #include "standards.h" #include "threadresult.h" +#include "utils.h" #include #include @@ -75,7 +76,7 @@ static bool executeCommand(std::string exe, std::vector args, std:: } else output = process.readAllStandardOutput().toStdString(); - if (redirect.compare(0,3,"2> ") == 0) { + if (startsWith(redirect, "2> ")) { std::ofstream fout(redirect.substr(3)); fout << process.readAllStandardError().toStdString(); } @@ -157,7 +158,7 @@ void CheckThread::runAddonsAndTools(const ImportProject::FileSettings *fileSetti if (!fileSettings) continue; - if (!fileSettings->cfg.empty() && fileSettings->cfg.compare(0,5,"Debug") != 0) + if (!fileSettings->cfg.empty() && !startsWith(fileSettings->cfg,"Debug")) continue; QStringList args; diff --git a/lib/astutils.cpp b/lib/astutils.cpp index 82fcb2ecf..462e862b5 100644 --- a/lib/astutils.cpp +++ b/lib/astutils.cpp @@ -2407,7 +2407,7 @@ bool isVariableChangedByFunctionCall(const Token *tok, int indirect, const Setti return false; // not a function => variable not changed if (Token::simpleMatch(tok, "{") && isTrivialConstructor(tok)) return false; - if (tok->isKeyword() && !isCPPCastKeyword(tok) && tok->str().compare(0,8,"operator") != 0) + if (tok->isKeyword() && !isCPPCastKeyword(tok) && !startsWith(tok->str(),"operator")) return false; // A functional cast won't modify the variable if (Token::Match(tok, "%type% (|{") && tok->tokType() == Token::eType && astIsPrimitive(tok->next())) diff --git a/lib/checkleakautovar.cpp b/lib/checkleakautovar.cpp index 5589760a2..b2c9d0872 100644 --- a/lib/checkleakautovar.cpp +++ b/lib/checkleakautovar.cpp @@ -32,6 +32,7 @@ #include "symboldatabase.h" #include "token.h" #include "tokenize.h" +#include "utils.h" #include "vfvalue.h" #include @@ -824,7 +825,7 @@ const Token * CheckLeakAutoVar::checkTokenInsideExpression(const Token * const t } else if (rhs->str() == "(" && !mSettings->library.returnValue(rhs->astOperand1()).empty()) { // #9298, assignment through return value of a function const std::string &returnValue = mSettings->library.returnValue(rhs->astOperand1()); - if (returnValue.compare(0, 3, "arg") == 0) { + if (startsWith(returnValue, "arg")) { int argn; const Token *func = getTokenArgumentFunction(tok, argn); if (func) { @@ -850,7 +851,7 @@ const Token * CheckLeakAutoVar::checkTokenInsideExpression(const Token * const t alloc.status = VarInfo::NOALLOC; functionCall(tok, openingPar, varInfo, alloc, nullptr); const std::string &returnValue = mSettings->library.returnValue(tok); - if (returnValue.compare(0, 3, "arg") == 0) + if (startsWith(returnValue, "arg")) // the function returns one of its argument, we need to process a potential assignment return openingPar; return isCPPCast(tok->astParent()) ? openingPar : openingPar->link(); diff --git a/lib/checkunusedvar.cpp b/lib/checkunusedvar.cpp index b853785eb..a8ab5b0f5 100644 --- a/lib/checkunusedvar.cpp +++ b/lib/checkunusedvar.cpp @@ -1273,7 +1273,7 @@ void CheckUnusedVar::checkFunctionVariableUsage() (!op1Var->valueType() || op1Var->valueType()->type == ValueType::Type::UNKNOWN_TYPE)) { // Check in the library if we should bailout or not.. std::string typeName = op1Var->getTypeName(); - if (typeName.compare(0, 2, "::") == 0) + if (startsWith(typeName, "::")) typeName.erase(typeName.begin(), typeName.begin() + 2); switch (mSettings->library.getTypeCheck("unusedvar", typeName)) { case Library::TypeCheck::def: diff --git a/lib/clangimport.cpp b/lib/clangimport.cpp index 2742eb5c9..0b7e9d4e9 100644 --- a/lib/clangimport.cpp +++ b/lib/clangimport.cpp @@ -412,9 +412,9 @@ std::string clangimport::AstNode::getSpelling() const return ""; } const std::string &str = mExtTokens[typeIndex - 1]; - if (str.compare(0,4,"col:") == 0) + if (startsWith(str,"col:")) return ""; - if (str.compare(0,8,"(ext.substr(5, ext.find_first_of(",>", 5) - 5)); - else if (ext.compare(0, 6, "(ext.substr(6, ext.find_first_of(":,>", 6) - 6)); const auto pos = ext.find(", col:"); if (pos != std::string::npos) @@ -542,7 +542,7 @@ const ::Type * clangimport::AstNode::addTypeTokens(TokenList *tokenList, const s return addTypeTokens(tokenList, str.substr(0, str.find("\':\'") + 1), scope); } - if (str.compare(0, 16, "'enum (anonymous") == 0) + if (startsWith(str, "'enum (anonymous")) return nullptr; std::string type; @@ -943,7 +943,7 @@ Token *clangimport::AstNode::createTokens(TokenList *tokenList) } if (nodeType == DeclRefExpr) { int addrIndex = mExtTokens.size() - 1; - while (addrIndex > 1 && mExtTokens[addrIndex].compare(0,2,"0x") != 0) + while (addrIndex > 1 && !startsWith(mExtTokens[addrIndex],"0x")) --addrIndex; const std::string addr = mExtTokens[addrIndex]; std::string name = unquote(getSpelling()); @@ -985,7 +985,7 @@ Token *clangimport::AstNode::createTokens(TokenList *tokenList) } if (nodeType == EnumDecl) { int colIndex = mExtTokens.size() - 1; - while (colIndex > 0 && mExtTokens[colIndex].compare(0,4,"col:") != 0 && mExtTokens[colIndex].compare(0,5,"line:") != 0) + while (colIndex > 0 && !startsWith(mExtTokens[colIndex],"col:") && !startsWith(mExtTokens[colIndex],"line:")) --colIndex; if (colIndex == 0) return nullptr; @@ -1131,10 +1131,10 @@ Token *clangimport::AstNode::createTokens(TokenList *tokenList) Token *s = getChild(0)->createTokens(tokenList); Token *dot = addtoken(tokenList, "."); std::string memberName = getSpelling(); - if (memberName.compare(0, 2, "->") == 0) { + if (startsWith(memberName, "->")) { dot->originalName("->"); memberName = memberName.substr(2); - } else if (memberName.compare(0, 1, ".") == 0) { + } else if (startsWith(memberName, ".")) { memberName = memberName.substr(1); } if (memberName.empty()) @@ -1150,7 +1150,7 @@ Token *clangimport::AstNode::createTokens(TokenList *tokenList) return nullptr; const Token *defToken = addtoken(tokenList, "namespace"); const std::string &s = mExtTokens[mExtTokens.size() - 2]; - const Token* nameToken = (s.compare(0, 4, "col:") == 0 || s.compare(0, 5, "line:") == 0) ? + const Token* nameToken = (startsWith(s, "col:") || startsWith(s, "line:")) ? addtoken(tokenList, mExtTokens.back()) : nullptr; Scope *scope = createScope(tokenList, Scope::ScopeType::eNamespace, children, defToken); if (nameToken) diff --git a/lib/cppcheck.cpp b/lib/cppcheck.cpp index 481617d34..0feb733bf 100644 --- a/lib/cppcheck.cpp +++ b/lib/cppcheck.cpp @@ -360,7 +360,7 @@ static std::string executeAddon(const AddonInfo &addonInfo, break; } #endif - if (executeCommand(py_exe, split("--version"), redirect, out) && out.compare(0, 7, "Python ") == 0 && std::isdigit(out[7])) { + if (executeCommand(py_exe, split("--version"), redirect, out) && startsWith(out, "Python ") && std::isdigit(out[7])) { pythonExe = py_exe; break; } @@ -393,7 +393,7 @@ static std::string executeAddon(const AddonInfo &addonInfo, std::istringstream istr(result); std::string line; while (std::getline(istr, line)) { - if (line.compare(0,9,"Checking ", 0, 9) != 0 && !line.empty() && line[0] != '{') { + if (!startsWith(line,"Checking ") && !line.empty() && line[0] != '{') { result.erase(result.find_last_not_of('\n') + 1, std::string::npos); // Remove trailing newlines throw InternalError(nullptr, "Failed to execute '" + pythonExe + " " + args + "'. " + result); } @@ -834,7 +834,7 @@ unsigned int CppCheck::checkFile(const std::string& filename, const std::string std::string code; const std::list &directives = preprocessor.getDirectives(); for (const Directive &dir : directives) { - if (dir.str.compare(0,8,"#define ") == 0 || dir.str.compare(0,9,"#include ") == 0) + if (startsWith(dir.str,"#define ") || startsWith(dir.str,"#include ")) code += "#line " + std::to_string(dir.linenr) + " \"" + dir.file + "\"\n" + dir.str + '\n'; } Tokenizer tokenizer2(&mSettings, this); @@ -883,7 +883,7 @@ unsigned int CppCheck::checkFile(const std::string& filename, const std::string std::string codeWithoutCfg = preprocessor.getcode(tokens1, mCurrentConfig, files, true); t.stop(); - if (codeWithoutCfg.compare(0,5,"#file") == 0) + if (startsWith(codeWithoutCfg,"#file")) codeWithoutCfg.insert(0U, "//"); std::string::size_type pos = 0; while ((pos = codeWithoutCfg.find("\n#file",pos)) != std::string::npos) @@ -1456,7 +1456,7 @@ void CppCheck::executeAddons(const std::vector& files) const bool misraC2023 = mSettings.premiumArgs.find("--misra-c-2023") != std::string::npos; while (std::getline(istr, line)) { - if (line.compare(0,1,"{") != 0) + if (!startsWith(line,"{")) continue; picojson::value res; @@ -1486,7 +1486,7 @@ void CppCheck::executeAddons(const std::vector& files) } errmsg.id = obj["addon"].get() + "-" + obj["errorId"].get(); - if (misraC2023 && errmsg.id.compare(0, 12, "misra-c2012-") == 0) + if (misraC2023 && startsWith(errmsg.id, "misra-c2012-")) errmsg.id = "misra-c2023-" + errmsg.id.substr(12); const std::string text = obj["message"].get(); errmsg.setmsg(text); diff --git a/lib/errorlogger.cpp b/lib/errorlogger.cpp index af322cb02..27f7765c9 100644 --- a/lib/errorlogger.cpp +++ b/lib/errorlogger.cpp @@ -135,7 +135,7 @@ ErrorMessage::ErrorMessage(const ErrorPath &errorPath, const TokenList *tokenLis std::string info = e.second; - if (info.compare(0,8,"$symbol:") == 0 && info.find('\n') < info.size()) { + if (startsWith(info,"$symbol:") && info.find('\n') < info.size()) { const std::string::size_type pos = info.find('\n'); const std::string &symbolName = info.substr(8, pos - 8); info = replaceStr(info.substr(pos+1), "$symbol", symbolName); @@ -215,7 +215,7 @@ void ErrorMessage::setmsg(const std::string &msg) if (pos == std::string::npos) { mShortMessage = replaceStr(msg, "$symbol", symbolName); mVerboseMessage = replaceStr(msg, "$symbol", symbolName); - } else if (msg.compare(0,8,"$symbol:") == 0) { + } else if (startsWith(msg,"$symbol:")) { mSymbolNames += msg.substr(8, pos-7); setmsg(msg.substr(pos + 1)); } else { diff --git a/lib/importproject.cpp b/lib/importproject.cpp index a27b3a493..f7d20d132 100644 --- a/lib/importproject.cpp +++ b/lib/importproject.cpp @@ -156,7 +156,7 @@ void ImportProject::FileSettings::setIncludePaths(const std::string &basepath, c for (const std::string &ipath : copyIn) { if (ipath.empty()) continue; - if (ipath.compare(0,2,"%(")==0) + if (startsWith(ipath,"%(")) continue; std::string s(Path::fromNativeSeparators(ipath)); if (!found.insert(s).second) @@ -298,9 +298,9 @@ void ImportProject::FileSettings::parseCommand(const std::string& command) if (F=='D') { std::string defval = readUntil(command, &pos, " "); defs += fval; - if (defval.size() >= 3 && defval.compare(0,2,"=\"")==0 && defval.back()=='\"') + if (defval.size() >= 3 && startsWith(defval,"=\"") && defval.back()=='\"') defval = "=" + unescape(defval.substr(2, defval.size() - 3)); - else if (defval.size() >= 5 && defval.compare(0, 3, "=\\\"") == 0 && endsWith(defval, "\\\"")) + else if (defval.size() >= 5 && startsWith(defval, "=\\\"") && endsWith(defval, "\\\"")) defval = "=\"" + unescape(defval.substr(3, defval.size() - 5)) + "\""; if (!defval.empty()) defs += defval; @@ -313,32 +313,9 @@ void ImportProject::FileSettings::parseCommand(const std::string& command) i = unescape(i.substr(1, i.size() - 2)); if (std::find(includePaths.cbegin(), includePaths.cend(), i) == includePaths.cend()) includePaths.push_back(std::move(i)); - } else if (F=='s' && fval.compare(0,2,"td") == 0) { + } else if (F=='s' && startsWith(fval,"td")) { ++pos; - const std::string stdval = readUntil(command, &pos, " "); - standard = stdval; - // TODO: use simplecpp::DUI::std instead of specifying it manually - if (standard.compare(0, 3, "c++") || standard.compare(0, 5, "gnu++")) { - const std::string stddef = simplecpp::getCppStdString(standard); - if (stddef.empty()) { - // TODO: log error - continue; - } - - defs += "__cplusplus="; - defs += stddef; - defs += ";"; - } else if (standard.compare(0, 1, "c") || standard.compare(0, 3, "gnu")) { - const std::string stddef = simplecpp::getCStdString(standard); - if (stddef.empty()) { - // TODO: log error - continue; - } - - defs += "__STDC_VERSION__="; - defs += stddef; - defs += ";"; - } + standard = readUntil(command, &pos, " "); } else if (F == 'i' && fval == "system") { ++pos; std::string isystem = readUntil(command, &pos, " "); @@ -459,9 +436,9 @@ bool ImportProject::importSln(std::istream &istr, const std::string &path, const return false; } - if (line.find("Microsoft Visual Studio Solution File") != 0) { + if (!startsWith(line, "Microsoft Visual Studio Solution File")) { // Skip BOM - if (!std::getline(istr, line) || line.find("Microsoft Visual Studio Solution File") != 0) { + if (!std::getline(istr, line) || !startsWith(line, "Microsoft Visual Studio Solution File")) { printError("Visual Studio solution file header not found"); return false; } @@ -473,7 +450,7 @@ bool ImportProject::importSln(std::istream &istr, const std::string &path, const bool found = false; while (std::getline(istr,line)) { - if (line.compare(0,8,"Project(")!=0) + if (!startsWith(line,"Project(")) continue; const std::string::size_type pos = line.find(".vcxproj"); if (pos == std::string::npos) @@ -1302,7 +1279,7 @@ void ImportProject::selectOneVsConfig(cppcheck::Platform::Type platform) } const ImportProject::FileSettings &fs = *it; bool remove = false; - if (fs.cfg.compare(0,5,"Debug") != 0) + if (!startsWith(fs.cfg,"Debug")) remove = true; if (platform == cppcheck::Platform::Type::Win64 && fs.platformType != platform) remove = true; diff --git a/lib/library.cpp b/lib/library.cpp index 15d649171..4c886f699 100644 --- a/lib/library.cpp +++ b/lib/library.cpp @@ -1174,7 +1174,7 @@ const Library::Container* Library::detectContainerInternal(const Token* const ty if (container.startPattern.empty()) continue; - const int offset = (withoutStd && container.startPattern2.find("std :: ") == 0) ? 7 : 0; + const int offset = (withoutStd && startsWith(container.startPattern2, "std :: ")) ? 7 : 0; // If endPattern is undefined, it will always match, but itEndPattern has to be defined. if (detect != IteratorOnly && container.endPattern.empty()) { @@ -1754,7 +1754,7 @@ std::shared_ptr createTokenFromExpression(const std::string& returnValue, // set varids for (Token* tok2 = tokenList->front(); tok2; tok2 = tok2->next()) { - if (tok2->str().compare(0, 3, "arg") != 0) + if (!startsWith(tok2->str(), "arg")) continue; nonneg int const id = strToInt(tok2->str().c_str() + 3); tok2->varId(id); diff --git a/lib/path.cpp b/lib/path.cpp index 078c6997d..74e6f3f46 100644 --- a/lib/path.cpp +++ b/lib/path.cpp @@ -171,7 +171,7 @@ bool Path::isAbsolute(const std::string& path) return false; // On Windows, 'C:\foo\bar' is an absolute path, while 'C:foo\bar' is not - return nativePath.compare(0, 2, "\\\\") == 0 || (std::isalpha(nativePath[0]) != 0 && nativePath.compare(1, 2, ":\\") == 0); + return startsWith(nativePath, "\\\\") || (std::isalpha(nativePath[0]) != 0 && nativePath.compare(1, 2, ":\\") == 0); #else return !nativePath.empty() && nativePath[0] == '/'; #endif @@ -227,7 +227,7 @@ bool Path::acceptFile(const std::string &path, const std::set &extr bool Path::isHeader(const std::string &path) { const std::string extension = getFilenameExtensionInLowerCase(path); - return (extension.compare(0, 2, ".h") == 0); + return startsWith(extension, ".h"); } std::string Path::getAbsoluteFilePath(const std::string& filePath) diff --git a/lib/preprocessor.cpp b/lib/preprocessor.cpp index a2058de4b..aa572cb5c 100644 --- a/lib/preprocessor.cpp +++ b/lib/preprocessor.cpp @@ -27,6 +27,7 @@ #include "settings.h" #include "standards.h" #include "suppressions.h" +#include "utils.h" #include #include @@ -370,7 +371,7 @@ static const simplecpp::Token *gotoEndIf(const simplecpp::Token *cmdtok) int level = 0; while (nullptr != (cmdtok = cmdtok->next)) { if (cmdtok->op == '#' && !sameline(cmdtok->previous,cmdtok) && sameline(cmdtok, cmdtok->next)) { - if (cmdtok->next->str().compare(0,2,"if")==0) + if (startsWith(cmdtok->next->str(),"if")) ++level; else if (cmdtok->next->str() == "endif") { --level; @@ -755,7 +756,7 @@ void Preprocessor::reportOutput(const simplecpp::OutputList &outputList, bool sh for (const simplecpp::Output &out : outputList) { switch (out.type) { case simplecpp::Output::ERROR: - if (out.msg.compare(0,6,"#error")!=0 || showerror) + if (!startsWith(out.msg,"#error") || showerror) error(out.location.file(), out.location.line, out.msg); break; case simplecpp::Output::WARNING: diff --git a/lib/suppressions.cpp b/lib/suppressions.cpp index eac6ae6cf..10b5fa7a0 100644 --- a/lib/suppressions.cpp +++ b/lib/suppressions.cpp @@ -172,7 +172,7 @@ std::vector Suppressions::parseMultiSuppressComment(c break; if (word.find_first_not_of("+-*/%#;") == std::string::npos) break; - if (word.compare(0, 11, "symbolName=") == 0) { + if (startsWith(word, "symbolName=")) { s.symbolName = word.substr(11); } else { if (errorMessage && errorMessage->empty()) @@ -316,7 +316,7 @@ bool Suppressions::Suppression::parseComment(std::string comment, std::string *e break; if (word.find_first_not_of("+-*/%#;") == std::string::npos) break; - if (word.compare(0,11,"symbolName=")==0) + if (startsWith(word,"symbolName=")) symbolName = word.substr(11); else if (errorMessage && errorMessage->empty()) *errorMessage = "Bad suppression attribute '" + word + "'. You can write comments in the comment after a ; or //. Valid suppression attributes; symbolName=sym"; @@ -377,7 +377,7 @@ std::string Suppressions::Suppression::getText() const ret += " symbolName=" + symbolName; if (hash > 0) ret += " hash=" + std::to_string(hash); - if (ret.compare(0,1," ")==0) + if (startsWith(ret," ")) return ret.substr(1); return ret; } diff --git a/lib/symboldatabase.cpp b/lib/symboldatabase.cpp index 9628dc0c6..3225ef2cf 100644 --- a/lib/symboldatabase.cpp +++ b/lib/symboldatabase.cpp @@ -2389,7 +2389,7 @@ static bool isOperator(const Token *tokenDef) if (tokenDef->isOperatorKeyword()) return true; const std::string &name = tokenDef->str(); - return name.size() > 8 && name.compare(0,8,"operator")==0 && std::strchr("+-*/%&|~^<>!=[(", name[8]); + return name.size() > 8 && startsWith(name,"operator") && std::strchr("+-*/%&|~^<>!=[(", name[8]); } Function::Function(const Tokenizer *mTokenizer, diff --git a/lib/symboldatabase.h b/lib/symboldatabase.h index fc7554a70..b620313c7 100644 --- a/lib/symboldatabase.h +++ b/lib/symboldatabase.h @@ -27,6 +27,7 @@ #include "mathlib.h" #include "sourcelocation.h" #include "token.h" +#include "utils.h" #include #include @@ -1061,7 +1062,7 @@ public: bool isAnonymous() const { // TODO: Check if class/struct is anonymous - return className.size() > 9 && className.compare(0,9,"Anonymous") == 0 && std::isdigit(className[9]); + return className.size() > 9 && startsWith(className,"Anonymous") && std::isdigit(className[9]); } const Enumerator * findEnumerator(const std::string & name) const { diff --git a/lib/timer.cpp b/lib/timer.cpp index 4e04b5819..73bb3f024 100644 --- a/lib/timer.cpp +++ b/lib/timer.cpp @@ -18,6 +18,8 @@ #include "timer.h" +#include "utils.h" + #include #include #include @@ -61,7 +63,7 @@ void TimerResults::showResults(SHOWTIME_MODES mode) const bool hasParent = false; { // Do not use valueFlow.. in "Overall time" because those are included in Tokenizer already - if (iter->first.compare(0,9,"valueFlow") == 0) + if (startsWith(iter->first,"valueFlow")) hasParent = true; // Do not use inner timers in "Overall time" diff --git a/lib/tokenize.cpp b/lib/tokenize.cpp index cc4f3cfa0..aaa7d599a 100644 --- a/lib/tokenize.cpp +++ b/lib/tokenize.cpp @@ -246,7 +246,7 @@ bool Tokenizer::duplicateTypedef(Token **tokPtr, const Token *name, const Token if (end) end = end->next(); } else if (end->str() == "(") { - if (tok->previous()->str().compare(0, 8, "operator") == 0) + if (startsWith(tok->previous()->str(), "operator")) // conversion operator return false; if (tok->previous()->str() == "typedef") @@ -8124,7 +8124,7 @@ static bool isNonMacro(const Token* tok) return true; if (cAlternativeTokens.count(tok->str()) > 0) return true; - if (tok->str().compare(0, 2, "__") == 0) // attribute/annotation + if (startsWith(tok->str(), "__")) // attribute/annotation return true; if (Token::simpleMatch(tok, "alignas (")) return true; @@ -8236,7 +8236,7 @@ void Tokenizer::reportUnknownMacros() const continue; if (cAlternativeTokens.count(tok->linkAt(2)->next()->str()) > 0) continue; - if (tok->next()->str().compare(0, 2, "__") == 0) // attribute/annotation + if (startsWith(tok->next()->str(), "__")) // attribute/annotation continue; unknownMacroError(tok->next()); } @@ -8990,7 +8990,7 @@ void Tokenizer::simplifyCppcheckAttribute() if (!tok->previous()) continue; const std::string &attr = tok->previous()->str(); - if (attr.compare(0, 11, "__cppcheck_") != 0) // TODO: starts_with("__cppcheck_") + if (!startsWith(attr, "__cppcheck_")) continue; if (attr.compare(attr.size()-2, 2, "__") != 0) // TODO: ends_with("__") continue; @@ -8998,7 +8998,7 @@ void Tokenizer::simplifyCppcheckAttribute() Token *vartok = tok->link(); while (Token::Match(vartok->next(), "%name%|*|&|::")) { vartok = vartok->next(); - if (Token::Match(vartok, "%name% (") && vartok->str().compare(0,11,"__cppcheck_") == 0) + if (Token::Match(vartok, "%name% (") && startsWith(vartok->str(),"__cppcheck_")) vartok = vartok->linkAt(1); } @@ -10471,7 +10471,7 @@ bool Tokenizer::hasIfdef(const Token *start, const Token *end) const assert(mPreprocessor); return std::any_of(mPreprocessor->getDirectives().cbegin(), mPreprocessor->getDirectives().cend(), [&](const Directive& d) { - return d.str.compare(0, 3, "#if") == 0 && + return startsWith(d.str, "#if") && d.linenr >= start->linenr() && d.linenr <= end->linenr() && start->fileIndex() < list.getFiles().size() && diff --git a/lib/utils.h b/lib/utils.h index 46ee7d698..690a47c10 100644 --- a/lib/utils.h +++ b/lib/utils.h @@ -80,6 +80,17 @@ struct EnumClassHash { } }; +inline bool startsWith(const std::string& str, const char start[], std::size_t startlen) +{ + return str.compare(0, startlen, start) == 0; +} + +template +bool startsWith(const std::string& str, const char (&start)[N]) +{ + return startsWith(str, start, N - 1); +} + inline bool endsWith(const std::string &str, char c) { return !str.empty() && str.back() == c; diff --git a/lib/valueflow.cpp b/lib/valueflow.cpp index 3036bdc42..a6e5311dc 100644 --- a/lib/valueflow.cpp +++ b/lib/valueflow.cpp @@ -4494,7 +4494,7 @@ static void valueFlowLifetimeFunction(Token *tok, TokenList &tokenlist, ErrorLog valueFlowForwardLifetime(tok->next(), tokenlist, errorLogger, settings); } else { const std::string& retVal = settings->library.returnValue(tok); - if (retVal.compare(0, 3, "arg") == 0) { + if (startsWith(retVal, "arg")) { std::size_t iArg{}; try { iArg = strToInt(retVal.substr(3)); diff --git a/test/cli/test-more-projects.py b/test/cli/test-more-projects.py index 6269d64a5..b2945df77 100644 --- a/test/cli/test-more-projects.py +++ b/test/cli/test-more-projects.py @@ -224,4 +224,31 @@ def test_project_missing_subproject(tmpdir): ret, stdout, stderr = cppcheck(['--project=' + project_file, '--template=cppcheck1']) assert ret == 1, stdout assert stdout == "cppcheck: error: failed to open project '{}/dummy.json'. The file does not exist.\n".format(str(tmpdir).replace('\\', '/')) - assert stderr == '' \ No newline at end of file + assert stderr == '' + + +def test_project_std(tmpdir): + with open(os.path.join(tmpdir, 'bug1.cpp'), 'wt') as f: + f.write(""" + #if __cplusplus == 201402L + int x = 123 / 0; + #endif + """) + + compile_commands = os.path.join(tmpdir, 'compile_commands.json') + + compilation_db = [ + { + "directory": str(tmpdir), + "command": "c++ -o bug1.o -c bug1.cpp -std=c++14", + "file": "bug1.cpp", + "output": "bug1.o" + } + ] + + with open(compile_commands, 'wt') as f: + f.write(json.dumps(compilation_db)) + + ret, stdout, stderr = cppcheck(['--project=' + compile_commands, '--enable=all', '-rp=' + str(tmpdir), '--template=cppcheck1']) + assert ret == 0, stdout + assert (stderr == '[bug1.cpp:3]: (error) Division by zero.\n') \ No newline at end of file diff --git a/test/testcmdlineparser.cpp b/test/testcmdlineparser.cpp index 1eede5650..d1f41d455 100644 --- a/test/testcmdlineparser.cpp +++ b/test/testcmdlineparser.cpp @@ -28,6 +28,7 @@ #include "suppressions.h" #include "fixture.h" #include "timer.h" +#include "utils.h" #include #include @@ -272,7 +273,7 @@ private: const char * const argv[] = {"cppcheck"}; ASSERT(parser->parseFromArgs(1, argv)); ASSERT_EQUALS(true, parser->getShowHelp()); - ASSERT(GET_REDIRECT_OUTPUT.find("Cppcheck - A tool for static C/C++ code analysis") == 0); + ASSERT(startsWith(GET_REDIRECT_OUTPUT, "Cppcheck - A tool for static C/C++ code analysis")); } void helpshort() { @@ -280,7 +281,7 @@ private: const char * const argv[] = {"cppcheck", "-h"}; ASSERT(parser->parseFromArgs(2, argv)); ASSERT_EQUALS(true, parser->getShowHelp()); - ASSERT(GET_REDIRECT_OUTPUT.find("Cppcheck - A tool for static C/C++ code analysis") == 0); + ASSERT(startsWith(GET_REDIRECT_OUTPUT, "Cppcheck - A tool for static C/C++ code analysis")); } void helplong() { @@ -288,7 +289,7 @@ private: const char * const argv[] = {"cppcheck", "--help"}; ASSERT(parser->parseFromArgs(2, argv)); ASSERT_EQUALS(true, parser->getShowHelp()); - ASSERT(GET_REDIRECT_OUTPUT.find("Cppcheck - A tool for static C/C++ code analysis") == 0); + ASSERT(startsWith(GET_REDIRECT_OUTPUT, "Cppcheck - A tool for static C/C++ code analysis")); } void showversion() { @@ -1594,7 +1595,7 @@ private: const char * const argv[] = {"cppcheck", "--doc"}; ASSERT(parser->parseFromArgs(2, argv)); ASSERT(parser->exitAfterPrinting()); - ASSERT(GET_REDIRECT_OUTPUT.find("## ") == 0); + ASSERT(startsWith(GET_REDIRECT_OUTPUT, "## ")); } void showtime() { diff --git a/test/testsimplifyusing.cpp b/test/testsimplifyusing.cpp index 32287681a..02cc14aaf 100644 --- a/test/testsimplifyusing.cpp +++ b/test/testsimplifyusing.cpp @@ -23,6 +23,7 @@ #include "fixture.h" #include "token.h" #include "tokenize.h" +#include "utils.h" #include @@ -1391,7 +1392,7 @@ private: "STAMP(B, A);\n" "STAMP(C, B);\n"; tok(code, cppcheck::Platform::Type::Native, /*debugwarnings*/ true, /*preprocess*/ true); - ASSERT_EQUALS(errout.str().compare(0, 64, "[test.cpp:6]: (debug) Failed to parse 'using C = S < S < S < int"), 0); + ASSERT(startsWith(errout.str(), "[test.cpp:6]: (debug) Failed to parse 'using C = S < S < S < int")); } void scopeInfo1() { diff --git a/test/testutils.cpp b/test/testutils.cpp index bdc095f54..3c2a7a960 100644 --- a/test/testutils.cpp +++ b/test/testutils.cpp @@ -39,6 +39,7 @@ private: TEST_CASE(isCharLiteral); TEST_CASE(strToInt); TEST_CASE(id_string); + TEST_CASE(startsWith); } void isValidGlobPattern() const { @@ -345,6 +346,19 @@ private: ASSERT_EQUALS(std::string(16,'f'), id_string_i(~0ULL)); } } + + void startsWith() const + { + ASSERT(::startsWith("test", "test")); + ASSERT(::startsWith("test2", "test")); + ASSERT(::startsWith("test test", "test")); + ASSERT(::startsWith("test", "t")); + ASSERT(!::startsWith("2test", "test")); + ASSERT(!::startsWith("", "test")); + ASSERT(!::startsWith("tes", "test")); + ASSERT(!::startsWith("2test", "t")); + ASSERT(!::startsWith("t", "test")); + } }; REGISTER_TEST(TestUtils) diff --git a/tools/dmake.cpp b/tools/dmake.cpp index bc0c7a064..388efd50d 100644 --- a/tools/dmake.cpp +++ b/tools/dmake.cpp @@ -38,7 +38,7 @@ static std::string builddir(std::string filename) { - if (filename.compare(0,4,"lib/") == 0) + if (startsWith(filename,"lib/")) filename = "$(libcppdir)" + filename.substr(3); return filename; } @@ -77,13 +77,13 @@ static void getDeps(const std::string &filename, std::vector &depfi * Files are searched according to the following priority: * [test, tools] -> cli -> lib -> externals */ - if (filename.compare(0, 4, "cli/") == 0) + if (startsWith(filename, "cli/")) getDeps("lib" + filename.substr(filename.find('/')), depfiles); - else if (filename.compare(0, 5, "test/") == 0) + else if (startsWith(filename, "test/")) getDeps("cli" + filename.substr(filename.find('/')), depfiles); - else if (filename.compare(0, 6, "tools/") == 0) + else if (startsWith(filename, "tools/")) getDeps("cli" + filename.substr(filename.find('/')), depfiles); - else if (filename.compare(0, 4, "lib/") == 0) { + else if (startsWith(filename, "lib/")) { for (const std::string & external : externalfolders) getDeps(external + filename.substr(filename.find('/')), depfiles); } @@ -126,7 +126,7 @@ static void getDeps(const std::string &filename, std::vector &depfi static void compilefiles(std::ostream &fout, const std::vector &files, const std::string &args) { for (const std::string &file : files) { - const bool external(file.compare(0,10,"externals/") == 0); + const bool external(startsWith(file,"externals/")); fout << objfile(file) << ": " << file; std::vector depfiles; getDeps(file, depfiles);