From caceec6efb72e3c74fb0dee8ba9ce1941756a318 Mon Sep 17 00:00:00 2001 From: firewave Date: Tue, 3 Jan 2023 16:04:43 +0100 Subject: [PATCH 1/6] added `Path::identify()` and deprecated flawed `Path::isCPP()`, `Path::isC()` and `Path::isHeader()` --- Makefile | 8 +-- cli/cmdlineparser.cpp | 4 +- gui/resultstree.cpp | 4 +- gui/resultsview.cpp | 8 ++- lib/config.h | 11 +++ lib/cppcheck.cpp | 13 ++-- lib/path.cpp | 43 +++++++++++- lib/path.h | 18 ++++- lib/preprocessor.cpp | 6 +- lib/tokenlist.cpp | 5 +- test/testpath.cpp | 153 ++++++++++++++++++++++++++++++++++++++++-- 11 files changed, 243 insertions(+), 30 deletions(-) diff --git a/Makefile b/Makefile index 6cbb850ad46..f514cb71425 100644 --- a/Makefile +++ b/Makefile @@ -467,7 +467,7 @@ $(libcppdir)/tokenize.o: lib/tokenize.cpp externals/simplecpp/simplecpp.h lib/ad $(libcppdir)/symboldatabase.o: lib/symboldatabase.cpp lib/addoninfo.h lib/astutils.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/keywords.h lib/library.h lib/mathlib.h lib/path.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/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/symboldatabase.cpp -$(libcppdir)/addoninfo.o: lib/addoninfo.cpp externals/picojson/picojson.h lib/addoninfo.h lib/config.h lib/json.h lib/path.h lib/utils.h +$(libcppdir)/addoninfo.o: lib/addoninfo.cpp externals/picojson/picojson.h lib/addoninfo.h lib/config.h lib/json.h lib/path.h lib/standards.h lib/utils.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/addoninfo.cpp $(libcppdir)/analyzerinfo.o: lib/analyzerinfo.cpp externals/tinyxml2/tinyxml2.h lib/analyzerinfo.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/path.h lib/platform.h lib/standards.h lib/utils.h lib/xml.h @@ -599,13 +599,13 @@ $(libcppdir)/library.o: lib/library.cpp externals/tinyxml2/tinyxml2.h lib/astuti $(libcppdir)/mathlib.o: lib/mathlib.cpp externals/simplecpp/simplecpp.h lib/config.h lib/errortypes.h lib/mathlib.h lib/utils.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/mathlib.cpp -$(libcppdir)/path.o: lib/path.cpp externals/simplecpp/simplecpp.h lib/config.h lib/path.h lib/utils.h +$(libcppdir)/path.o: lib/path.cpp externals/simplecpp/simplecpp.h lib/config.h lib/path.h lib/standards.h lib/utils.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/path.cpp $(libcppdir)/pathanalysis.o: lib/pathanalysis.cpp lib/astutils.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/pathanalysis.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/utils.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/pathanalysis.cpp -$(libcppdir)/pathmatch.o: lib/pathmatch.cpp lib/config.h lib/path.h lib/pathmatch.h lib/utils.h +$(libcppdir)/pathmatch.o: lib/pathmatch.cpp lib/config.h lib/path.h lib/pathmatch.h lib/standards.h lib/utils.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/pathmatch.cpp $(libcppdir)/platform.o: lib/platform.cpp externals/tinyxml2/tinyxml2.h lib/config.h lib/path.h lib/platform.h lib/standards.h lib/utils.h lib/xml.h @@ -662,7 +662,7 @@ cli/cppcheckexecutorsig.o: cli/cppcheckexecutorsig.cpp cli/cppcheckexecutor.h cl cli/executor.o: cli/executor.cpp cli/executor.h lib/addoninfo.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/utils.h $(CXX) ${INCLUDE_FOR_CLI} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ cli/executor.cpp -cli/filelister.o: cli/filelister.cpp cli/filelister.h lib/config.h lib/path.h lib/pathmatch.h lib/utils.h +cli/filelister.o: cli/filelister.cpp cli/filelister.h lib/config.h lib/path.h lib/pathmatch.h lib/standards.h lib/utils.h $(CXX) ${INCLUDE_FOR_CLI} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ cli/filelister.cpp cli/main.o: cli/main.cpp cli/cppcheckexecutor.h lib/config.h lib/errortypes.h lib/filesettings.h lib/platform.h lib/standards.h lib/utils.h diff --git a/cli/cmdlineparser.cpp b/cli/cmdlineparser.cpp index 0cb85af412b..cbcba94154b 100644 --- a/cli/cmdlineparser.cpp +++ b/cli/cmdlineparser.cpp @@ -185,7 +185,9 @@ bool CmdLineParser::fillSettingsFromArgs(int argc, const char* const argv[]) // Output a warning for the user if he tries to exclude headers const std::vector& ignored = getIgnoredPaths(); const bool warn = std::any_of(ignored.cbegin(), ignored.cend(), [](const std::string& i) { - return Path::isHeader(i); + bool header; + Path::identify(i, &header); + return header; }); if (warn) { mLogger.printMessage("filename exclusion does not apply to header (.h and .hpp) files."); diff --git a/gui/resultstree.cpp b/gui/resultstree.cpp index 66dbbd67660..1953e4af929 100644 --- a/gui/resultstree.cpp +++ b/gui/resultstree.cpp @@ -962,7 +962,9 @@ void ResultsTree::recheckSelectedFiles() askFileDir(currentFile); return; } - if (Path::isHeader(currentFile.toStdString())) { + bool header = false; + Path::identify(currentFile.toStdString(), &header); + if (header) { if (!data[FILE0].toString().isEmpty() && !selectedItems.contains(data[FILE0].toString())) { selectedItems<<((!mCheckPath.isEmpty() && (data[FILE0].toString().indexOf(mCheckPath) != 0)) ? (mCheckPath + "/" + data[FILE0].toString()) : data[FILE0].toString()); if (!selectedItems.contains(fileNameWithCheckPath)) diff --git a/gui/resultsview.cpp b/gui/resultsview.cpp index a8bf09c42a0..c3466dead65 100644 --- a/gui/resultsview.cpp +++ b/gui/resultsview.cpp @@ -462,8 +462,12 @@ void ResultsView::updateDetails(const QModelIndex &index) QString formattedMsg = message; const QString file0 = data["file0"].toString(); - if (!file0.isEmpty() && Path::isHeader(data["file"].toString().toStdString())) - formattedMsg += QString("\n\n%1: %2").arg(tr("First included by"), QDir::toNativeSeparators(file0)); + if (!file0.isEmpty()) { + bool header = false; + Path::identify(data["file"].toString().toStdString(), &header); + if (header) + formattedMsg += QString("\n\n%1: %2").arg(tr("First included by"), QDir::toNativeSeparators(file0)); + } if (data["cwe"].toInt() > 0) formattedMsg.prepend("CWE: " + QString::number(data["cwe"].toInt()) + "\n"); diff --git a/lib/config.h b/lib/config.h index 5379eada441..060462e6474 100644 --- a/lib/config.h +++ b/lib/config.h @@ -110,6 +110,17 @@ # define WARN_UNUSED #endif +// deprecated +#if defined(__GNUC__) \ + || defined(__clang__) \ + || defined(__CPPCHECK__) +# define DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) +# define DEPRECATED __declspec(deprecated) +#else +# define DEPRECATED +#endif + #define REQUIRES(msg, ...) class=typename std::enable_if<__VA_ARGS__::value>::type #include diff --git a/lib/cppcheck.cpp b/lib/cppcheck.cpp index 032a4a47049..6c3954920ea 100644 --- a/lib/cppcheck.cpp +++ b/lib/cppcheck.cpp @@ -195,9 +195,11 @@ static void createDumpFile(const Settings& settings, language = " language=\"cpp\""; break; case Standards::Language::None: - if (Path::isCPP(filename)) + // TODO: error out on unknown language? + const Standards::Language lang = Path::identify(filename); + if (lang == Standards::Language::CPP) language = " language=\"cpp\""; - else if (Path::isC(filename)) + else if (lang == Standards::Language::C) language = " language=\"c\""; break; } @@ -429,7 +431,8 @@ unsigned int CppCheck::checkClang(const std::string &path) mErrorLogger.reportOut(std::string("Checking ") + path + " ...", Color::FgGreen); // TODO: this ignores the configured language - const std::string lang = Path::isCPP(path) ? "-x c++" : "-x c"; + const bool isCpp = Path::identify(path) == Standards::Language::CPP; + const std::string langOpt = isCpp ? "-x c++" : "-x c"; const std::string analyzerInfo = mSettings.buildDir.empty() ? std::string() : AnalyzerInformation::getAnalyzerInfoFile(mSettings.buildDir, path, emptyString); const std::string clangcmd = analyzerInfo + ".clang-cmd"; const std::string clangStderr = analyzerInfo + ".clang-stderr"; @@ -442,9 +445,9 @@ unsigned int CppCheck::checkClang(const std::string &path) } #endif - std::string flags(lang + " "); + std::string flags(langOpt + " "); // TODO: does not apply C standard - if (Path::isCPP(path) && !mSettings.standards.stdValue.empty()) + if (isCpp && !mSettings.standards.stdValue.empty()) flags += "-std=" + mSettings.standards.stdValue + " "; for (const std::string &i: mSettings.includePaths) diff --git a/lib/path.cpp b/lib/path.cpp index 013044fd05e..5fbf882d3ba 100644 --- a/lib/path.cpp +++ b/lib/path.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -193,6 +194,18 @@ std::string Path::getRelativePath(const std::string& absolutePath, const std::ve return absolutePath; } +static const std::unordered_set cpp_src_exts = { + ".cpp", ".cxx", ".cc", ".c++", ".tpp", ".txx", ".ipp", ".ixx" +}; + +static const std::unordered_set c_src_exts = { + ".c", ".cl" +}; + +static const std::unordered_set header_exts = { + ".h", ".hpp", ".h++", ".hxx", ".hh" +}; + bool Path::isC(const std::string &path) { // In unix, ".C" is considered C++ file @@ -220,7 +233,8 @@ bool Path::isCPP(const std::string &path) bool Path::acceptFile(const std::string &path, const std::set &extra) { - return !Path::isHeader(path) && (Path::isCPP(path) || Path::isC(path) || extra.find(getFilenameExtension(path)) != extra.end()); + bool header = false; + return (identify(path, &header) != Standards::Language::None && !header) || extra.find(getFilenameExtension(path)) != extra.end(); } bool Path::isHeader(const std::string &path) @@ -229,6 +243,33 @@ bool Path::isHeader(const std::string &path) return startsWith(extension, ".h"); } +Standards::Language Path::identify(const std::string &path, bool *header) +{ + if (header) + *header = false; + + std::string ext = getFilenameExtension(path); + if (ext == ".C") + return Standards::Language::CPP; + if (c_src_exts.find(ext) != c_src_exts.end()) + return Standards::Language::C; + if (!caseInsensitiveFilesystem()) + strTolower(ext); + if (ext == ".h") { + if (header) + *header = true; + return Standards::Language::C; // treat as C for now + } + if (cpp_src_exts.find(ext) != cpp_src_exts.end()) + return Standards::Language::CPP; + if (header_exts.find(ext) != header_exts.end()) { + if (header) + *header = true; + return Standards::Language::CPP; + } + return Standards::Language::None; +} + std::string Path::getAbsoluteFilePath(const std::string& filePath) { std::string absolute_path; diff --git a/lib/path.h b/lib/path.h index 82545f44e3a..84cffd2ca84 100644 --- a/lib/path.h +++ b/lib/path.h @@ -22,6 +22,7 @@ //--------------------------------------------------------------------------- #include "config.h" +#include "standards.h" #include #include @@ -156,22 +157,33 @@ class CPPCHECKLIB Path { * @brief Identify language based on file extension. * @param path filename to check. path info is optional * @return true if extension is meant for C files + * @deprecated does not account for headers - use @identify() instead */ - static bool isC(const std::string &path); + static DEPRECATED bool isC(const std::string &path); /** * @brief Identify language based on file extension. * @param path filename to check. path info is optional * @return true if extension is meant for C++ files + * @deprecated returns true for some header extensions - use @identify() instead */ - static bool isCPP(const std::string &path); + static DEPRECATED bool isCPP(const std::string &path); /** * @brief Is filename a header based on file extension * @param path filename to check. path info is optional * @return true if filename extension is meant for headers + * @deprecated returns only heuristic result - use @identify() instead */ - static bool isHeader(const std::string &path); + static DEPRECATED bool isHeader(const std::string &path); + + /** + * @brief Identify the language based on the file extension + * @param path filename to check. path info is optional + * @param header if provided indicates if the file is a header + * @return the language type + */ + static Standards::Language identify(const std::string &path, bool *header = nullptr); /** * @brief Get filename without a directory path part. diff --git a/lib/preprocessor.cpp b/lib/preprocessor.cpp index efad79ac3f7..7398f385486 100644 --- a/lib/preprocessor.cpp +++ b/lib/preprocessor.cpp @@ -730,11 +730,13 @@ static simplecpp::DUI createDUI(const Settings &mSettings, const std::string &cf dui.includePaths = mSettings.includePaths; // -I dui.includes = mSettings.userIncludes; // --include // TODO: use mSettings.standards.stdValue instead - if (Path::isCPP(filename)) { + // TODO: error out on unknown language? + const Standards::Language lang = Path::identify(filename); + if (lang == Standards::Language::CPP) { dui.std = mSettings.standards.getCPP(); splitcfg(mSettings.platform.getLimitsDefines(Standards::getCPP(dui.std)), dui.defines, ""); } - else { + else if (lang == Standards::Language::C) { dui.std = mSettings.standards.getC(); splitcfg(mSettings.platform.getLimitsDefines(Standards::getC(dui.std)), dui.defines, ""); } diff --git a/lib/tokenlist.cpp b/lib/tokenlist.cpp index 4f482d6f177..b28a197e593 100644 --- a/lib/tokenlist.cpp +++ b/lib/tokenlist.cpp @@ -85,10 +85,7 @@ void TokenList::determineCppC() { // only try to determine it if it wasn't enforced if (mLang == Standards::Language::None) { - if (Path::isC(getSourceFilePath())) - mLang = Standards::Language::C; - else if (Path::isCPP(getSourceFilePath())) - mLang = Standards::Language::CPP; + mLang = Path::identify(getSourceFilePath()); } } diff --git a/test/testpath.cpp b/test/testpath.cpp index 2150f5ee7b2..b93f2cc276e 100644 --- a/test/testpath.cpp +++ b/test/testpath.cpp @@ -38,12 +38,14 @@ class TestPath : public TestFixture { TEST_CASE(getRelative); TEST_CASE(is_c); TEST_CASE(is_cpp); + TEST_CASE(is_header); TEST_CASE(get_path_from_filename); TEST_CASE(join); TEST_CASE(isDirectory); TEST_CASE(isFile); TEST_CASE(sameFileName); TEST_CASE(getFilenameExtension); + TEST_CASE(identify); } void removeQuotationMarks() const { @@ -59,6 +61,7 @@ class TestPath : public TestFixture { } void acceptFile() const { + ASSERT(Path::acceptFile("index.c")); ASSERT(Path::acceptFile("index.cpp")); ASSERT(Path::acceptFile("index.invalid.cpp")); ASSERT(Path::acceptFile("index.invalid.Cpp")); @@ -72,6 +75,14 @@ class TestPath : public TestFixture { // don't accept any headers ASSERT_EQUALS(false, Path::acceptFile("index.h")); ASSERT_EQUALS(false, Path::acceptFile("index.hpp")); + + const std::set extra = { ".extra", ".header" }; + ASSERT(Path::acceptFile("index.c", extra)); + ASSERT(Path::acceptFile("index.cpp", extra)); + ASSERT(Path::acceptFile("index.extra", extra)); + ASSERT(Path::acceptFile("index.header", extra)); + ASSERT(Path::acceptFile("index.h", extra)==false); + ASSERT(Path::acceptFile("index.hpp", extra)==false); } void getCurrentPath() const { @@ -117,12 +128,16 @@ class TestPath : public TestFixture { ASSERT_EQUALS("C:/foobar/test.cpp", Path::getRelativePath("C:/foobar/test.cpp", basePaths)); } +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + void is_c() const { - ASSERT(Path::isC("index.cpp")==false); - ASSERT(Path::isC("")==false); - ASSERT(Path::isC("c")==false); ASSERT(Path::isC("index.c")); + ASSERT(Path::isC("index.cl")); ASSERT(Path::isC("C:\\foo\\index.c")); + ASSERT(Path::isC("/mnt/c/foo/index.c")); // In unix .C is considered C++ #if defined(_WIN32) || (defined(__APPLE__) && defined(__MACH__)) @@ -130,10 +145,28 @@ class TestPath : public TestFixture { #else ASSERT_EQUALS(false, Path::isC("C:\\foo\\index.C")); #endif + + ASSERT(Path::isC("index.cpp")==false); + ASSERT(Path::isC("")==false); + ASSERT(Path::isC("c")==false); + + // unlike isCPP() it does not account for headers + ASSERT(Path::isC(".h")==false); } void is_cpp() const { - ASSERT(Path::isCPP("index.c")==false); + ASSERT(Path::isCPP("index.cpp")); + ASSERT(Path::isCPP("index.cxx")); + ASSERT(Path::isCPP("index.cc")); + ASSERT(Path::isCPP("index.c++")); + ASSERT(Path::isCPP("index.tpp")); + ASSERT(Path::isCPP("index.txx")); + ASSERT(Path::isCPP("index.ipp")); + ASSERT(Path::isCPP("index.ixx")); + ASSERT(Path::isCPP("C:\\foo\\index.cpp")); + ASSERT(Path::isCPP("C:\\foo\\index.Cpp")); + ASSERT(Path::isCPP("/mnt/c/foo/index.cpp")); + ASSERT(Path::isCPP("/mnt/c/foo/index.Cpp")); // In unix .C is considered C++ #if defined(_WIN32) || (defined(__APPLE__) && defined(__MACH__)) @@ -141,11 +174,36 @@ class TestPath : public TestFixture { #else ASSERT_EQUALS(true, Path::isCPP("index.C")); #endif - ASSERT(Path::isCPP("index.cpp")); - ASSERT(Path::isCPP("C:\\foo\\index.cpp")); - ASSERT(Path::isCPP("C:\\foo\\index.Cpp")); + + ASSERT(Path::isCPP("index.c")==false); + + // C++ headers are also considered C++ + ASSERT(Path::isCPP("index.hpp")); + // .h++ is missing in the list of C++ headers + ASSERT(Path::isCPP("index.h++")==false); } + void is_header() const { + ASSERT(Path::isHeader("index.h")); + ASSERT(Path::isHeader("index.hpp")); + ASSERT(Path::isHeader("index.hxx")); + ASSERT(Path::isHeader("index.h++")); + ASSERT(Path::isHeader("index.hh")); + + ASSERT(Path::isHeader("index.c")==false); + ASSERT(Path::isHeader("index.cpp")==false); + + // function uses heuristic approach which causes these false positives + // no need to fix - function is deprecated and was replaced by identify() + TODO_ASSERT(Path::isHeader("index.header")==false); + TODO_ASSERT(Path::isHeader("index.htm")==false); + TODO_ASSERT(Path::isHeader("index.html")==false); + } + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + void get_path_from_filename() const { ASSERT_EQUALS("", Path::getPathFromFilename("index.h")); ASSERT_EQUALS("/tmp/", Path::getPathFromFilename("/tmp/index.h")); @@ -225,6 +283,87 @@ class TestPath : public TestFixture { ASSERT_EQUALS(".hh", Path::getFilenameExtension("test.hH", true)); #endif } + + + void identify() const { + Standards::Language lang; + bool header; + + ASSERT_EQUALS(Standards::Language::None, Path::identify("")); + ASSERT_EQUALS(Standards::Language::None, Path::identify("c")); + ASSERT_EQUALS(Standards::Language::None, Path::identify("cpp")); + ASSERT_EQUALS(Standards::Language::None, Path::identify("h")); + ASSERT_EQUALS(Standards::Language::None, Path::identify("hpp")); + + // TODO: what about files starting with a "."? + //ASSERT_EQUALS(Standards::Language::None, Path::identify(".c")); + //ASSERT_EQUALS(Standards::Language::None, Path::identify(".cpp")); + //ASSERT_EQUALS(Standards::Language::None, Path::identify(".h")); + //ASSERT_EQUALS(Standards::Language::None, Path::identify(".hpp")); + + // C + ASSERT_EQUALS(Standards::Language::C, Path::identify("index.c")); + ASSERT_EQUALS(Standards::Language::C, Path::identify("index.cl")); + ASSERT_EQUALS(Standards::Language::C, Path::identify("C:\\foo\\index.c")); + ASSERT_EQUALS(Standards::Language::C, Path::identify("/mnt/c/foo/index.c")); + + // In unix .C is considered C++ +#ifdef _WIN32 + ASSERT_EQUALS(Standards::Language::C, Path::identify("C:\\foo\\index.C")); +#endif + + lang = Path::identify("index.c", &header); + ASSERT_EQUALS(Standards::Language::C, lang); + ASSERT_EQUALS(false, header); + + // C++ + ASSERT_EQUALS(Standards::Language::CPP, Path::identify("index.cpp")); + ASSERT_EQUALS(Standards::Language::CPP, Path::identify("index.cxx")); + ASSERT_EQUALS(Standards::Language::CPP, Path::identify("index.cc")); + ASSERT_EQUALS(Standards::Language::CPP, Path::identify("index.c++")); + ASSERT_EQUALS(Standards::Language::CPP, Path::identify("index.tpp")); + ASSERT_EQUALS(Standards::Language::CPP, Path::identify("index.txx")); + ASSERT_EQUALS(Standards::Language::CPP, Path::identify("index.ipp")); + ASSERT_EQUALS(Standards::Language::CPP, Path::identify("index.ixx")); + ASSERT_EQUALS(Standards::Language::CPP, Path::identify("C:\\foo\\index.cpp")); + ASSERT_EQUALS(Standards::Language::CPP, Path::identify("C:\\foo\\index.Cpp")); + ASSERT_EQUALS(Standards::Language::CPP, Path::identify("/mnt/c/foo/index.cpp")); + ASSERT_EQUALS(Standards::Language::CPP, Path::identify("/mnt/c/foo/index.Cpp")); + + // TODO: check for case-insenstive filesystem instead + // In unix .C is considered C++ +#if !defined(_WIN32) && !(defined(__APPLE__) && defined(__MACH__)) + ASSERT_EQUALS(Standards::Language::CPP, Path::identify("index.C")); +#else + ASSERT_EQUALS(Standards::Language::C, Path::identify("index.C")); +#endif + + lang = Path::identify("index.cpp", &header); + ASSERT_EQUALS(Standards::Language::CPP, lang); + ASSERT_EQUALS(false, header); + + // headers + lang = Path::identify("index.h", &header); + ASSERT_EQUALS(Standards::Language::C, lang); + ASSERT_EQUALS(true, header); + + lang = Path::identify("index.hpp", &header); + ASSERT_EQUALS(Standards::Language::CPP, lang); + ASSERT_EQUALS(true, header); + lang = Path::identify("index.hxx", &header); + ASSERT_EQUALS(Standards::Language::CPP, lang); + ASSERT_EQUALS(true, header); + lang = Path::identify("index.h++", &header); + ASSERT_EQUALS(Standards::Language::CPP, lang); + ASSERT_EQUALS(true, header); + lang = Path::identify("index.hh", &header); + ASSERT_EQUALS(Standards::Language::CPP, lang); + ASSERT_EQUALS(true, header); + + ASSERT_EQUALS(Standards::Language::None, Path::identify("index.header")); + ASSERT_EQUALS(Standards::Language::None, Path::identify("index.htm")); + ASSERT_EQUALS(Standards::Language::None, Path::identify("index.html")); + } }; REGISTER_TEST(TestPath) From f87b356c3cef8ef0680390c4d408c5515af0c7a7 Mon Sep 17 00:00:00 2001 From: firewave Date: Wed, 4 Jan 2023 00:54:59 +0100 Subject: [PATCH 2/6] added `Path::isHeader2()` as a convenience function --- cli/cmdlineparser.cpp | 4 +--- gui/resultstree.cpp | 4 +--- gui/resultsview.cpp | 8 ++------ lib/path.cpp | 7 +++++++ lib/path.h | 9 ++++++++- test/testpath.cpp | 15 +++++++++++++++ 6 files changed, 34 insertions(+), 13 deletions(-) diff --git a/cli/cmdlineparser.cpp b/cli/cmdlineparser.cpp index cbcba94154b..59d999f9d7f 100644 --- a/cli/cmdlineparser.cpp +++ b/cli/cmdlineparser.cpp @@ -185,9 +185,7 @@ bool CmdLineParser::fillSettingsFromArgs(int argc, const char* const argv[]) // Output a warning for the user if he tries to exclude headers const std::vector& ignored = getIgnoredPaths(); const bool warn = std::any_of(ignored.cbegin(), ignored.cend(), [](const std::string& i) { - bool header; - Path::identify(i, &header); - return header; + return Path::isHeader2(i); }); if (warn) { mLogger.printMessage("filename exclusion does not apply to header (.h and .hpp) files."); diff --git a/gui/resultstree.cpp b/gui/resultstree.cpp index 1953e4af929..b54d1e2c186 100644 --- a/gui/resultstree.cpp +++ b/gui/resultstree.cpp @@ -962,9 +962,7 @@ void ResultsTree::recheckSelectedFiles() askFileDir(currentFile); return; } - bool header = false; - Path::identify(currentFile.toStdString(), &header); - if (header) { + if (Path::isHeader2(currentFile.toStdString())) { if (!data[FILE0].toString().isEmpty() && !selectedItems.contains(data[FILE0].toString())) { selectedItems<<((!mCheckPath.isEmpty() && (data[FILE0].toString().indexOf(mCheckPath) != 0)) ? (mCheckPath + "/" + data[FILE0].toString()) : data[FILE0].toString()); if (!selectedItems.contains(fileNameWithCheckPath)) diff --git a/gui/resultsview.cpp b/gui/resultsview.cpp index c3466dead65..e595e9eecf1 100644 --- a/gui/resultsview.cpp +++ b/gui/resultsview.cpp @@ -462,12 +462,8 @@ void ResultsView::updateDetails(const QModelIndex &index) QString formattedMsg = message; const QString file0 = data["file0"].toString(); - if (!file0.isEmpty()) { - bool header = false; - Path::identify(data["file"].toString().toStdString(), &header); - if (header) - formattedMsg += QString("\n\n%1: %2").arg(tr("First included by"), QDir::toNativeSeparators(file0)); - } + if (!file0.isEmpty() && Path::isHeader2(data["file"].toString().toStdString())) + formattedMsg += QString("\n\n%1: %2").arg(tr("First included by"), QDir::toNativeSeparators(file0)); if (data["cwe"].toInt() > 0) formattedMsg.prepend("CWE: " + QString::number(data["cwe"].toInt()) + "\n"); diff --git a/lib/path.cpp b/lib/path.cpp index 5fbf882d3ba..f7f3258a3d7 100644 --- a/lib/path.cpp +++ b/lib/path.cpp @@ -270,6 +270,13 @@ Standards::Language Path::identify(const std::string &path, bool *header) return Standards::Language::None; } +bool Path::isHeader2(const std::string &path) +{ + bool header; + (void)Path::identify(path, &header); + return header; +} + std::string Path::getAbsoluteFilePath(const std::string& filePath) { std::string absolute_path; diff --git a/lib/path.h b/lib/path.h index 84cffd2ca84..1633287ac3a 100644 --- a/lib/path.h +++ b/lib/path.h @@ -173,10 +173,17 @@ class CPPCHECKLIB Path { * @brief Is filename a header based on file extension * @param path filename to check. path info is optional * @return true if filename extension is meant for headers - * @deprecated returns only heuristic result - use @identify() instead + * @deprecated returns only heuristic result - use @identify() or @isHeader2() instead */ static DEPRECATED bool isHeader(const std::string &path); + /** + * @brief Is filename a header based on file extension + * @param path filename to check. path info is optional + * @return true if filename extension is meant for headers + */ + static bool isHeader2(const std::string &path); + /** * @brief Identify the language based on the file extension * @param path filename to check. path info is optional diff --git a/test/testpath.cpp b/test/testpath.cpp index b93f2cc276e..61b49ef5f50 100644 --- a/test/testpath.cpp +++ b/test/testpath.cpp @@ -46,6 +46,7 @@ class TestPath : public TestFixture { TEST_CASE(sameFileName); TEST_CASE(getFilenameExtension); TEST_CASE(identify); + TEST_CASE(is_header_2); } void removeQuotationMarks() const { @@ -364,6 +365,20 @@ class TestPath : public TestFixture { ASSERT_EQUALS(Standards::Language::None, Path::identify("index.htm")); ASSERT_EQUALS(Standards::Language::None, Path::identify("index.html")); } + + void is_header_2() const { + ASSERT(Path::isHeader2("index.h")); + ASSERT(Path::isHeader2("index.hpp")); + ASSERT(Path::isHeader2("index.hxx")); + ASSERT(Path::isHeader2("index.h++")); + ASSERT(Path::isHeader2("index.hh")); + + ASSERT(Path::isHeader2("index.c")==false); + ASSERT(Path::isHeader2("index.cpp")==false); + ASSERT(Path::isHeader2("index.header")==false); + ASSERT(Path::isHeader2("index.htm")==false); + ASSERT(Path::isHeader2("index.html")==false); + } }; REGISTER_TEST(TestPath) From 461decf4831c6ceb5fc24d80d444e7d603c31420 Mon Sep 17 00:00:00 2001 From: firewave Date: Wed, 4 Jan 2023 01:33:32 +0100 Subject: [PATCH 3/6] fixed assumption in `setVarIdParseDeclaration()` that `isC() == false` implies `isHeader() == true` / added `TokenList::isHeader()` and `Tokenizer::isHeader()` --- lib/tokenize.cpp | 16 ++++++++-------- test/testvarid.cpp | 18 +++++++++++++++++- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/lib/tokenize.cpp b/lib/tokenize.cpp index 15c747b7fe0..584dd3fa3e8 100644 --- a/lib/tokenize.cpp +++ b/lib/tokenize.cpp @@ -4156,7 +4156,7 @@ void VariableMap::addVariable(const std::string& varname, bool globalNamespace) it->second = ++mVarId; } -static bool setVarIdParseDeclaration(Token** tok, const VariableMap& variableMap, bool executableScope, bool cpp, bool c) +static bool setVarIdParseDeclaration(Token** tok, const VariableMap& variableMap, bool executableScope) { const Token* const tok1 = *tok; Token* tok2 = *tok; @@ -4174,14 +4174,14 @@ static bool setVarIdParseDeclaration(Token** tok, const VariableMap& variableMap tok2 = tok2->linkAt(1)->next(); continue; } - if (cpp && Token::Match(tok2, "namespace|public|private|protected")) + if (tok2->isCpp() && Token::Match(tok2, "namespace|public|private|protected")) return false; - if (cpp && Token::simpleMatch(tok2, "decltype (")) { + if (tok2->isCpp() && Token::simpleMatch(tok2, "decltype (")) { typeCount = 1; tok2 = tok2->linkAt(1)->next(); continue; } - if (Token::Match(tok2, "struct|union|enum") || (!c && Token::Match(tok2, "class|typename"))) { + if (Token::Match(tok2, "struct|union|enum") || (tok2->isCpp() && Token::Match(tok2, "class|typename"))) { hasstruct = true; typeCount = 0; singleNameCount = 0; @@ -4197,8 +4197,8 @@ static bool setVarIdParseDeclaration(Token** tok, const VariableMap& variableMap ++typeCount; ++singleNameCount; } - } else if (!c && ((TemplateSimplifier::templateParameters(tok2) > 0) || - Token::simpleMatch(tok2, "< >") /* Ticket #4764 */)) { + } else if (tok2->isCpp() && ((TemplateSimplifier::templateParameters(tok2) > 0) || + Token::simpleMatch(tok2, "< >") /* Ticket #4764 */)) { const Token *start = *tok; if (Token::Match(start->previous(), "%or%|%oror%|&&|&|^|+|-|*|/")) return false; @@ -4263,7 +4263,7 @@ static bool setVarIdParseDeclaration(Token** tok, const VariableMap& variableMap } } - if (cpp && tok3 && Token::simpleMatch(tok3->previous(), "] (") && + if (tok3 && tok3->isCpp() && Token::simpleMatch(tok3->previous(), "] (") && (Token::simpleMatch(tok3->link(), ") {") || Token::Match(tok3->link(), ") . %name%"))) isLambdaArg = true; } @@ -4683,7 +4683,7 @@ void Tokenizer::setVarIdPass1() } try { /* Ticket #8151 */ - decl = setVarIdParseDeclaration(&tok2, variableMap, scopeStack.top().isExecutable, isCPP(), isC()); + decl = setVarIdParseDeclaration(&tok2, variableMap, scopeStack.top().isExecutable); } catch (const Token * errTok) { syntaxError(errTok); } diff --git a/test/testvarid.cpp b/test/testvarid.cpp index f54f25cca74..d9ef8934178 100644 --- a/test/testvarid.cpp +++ b/test/testvarid.cpp @@ -2812,6 +2812,14 @@ class TestVarID : public TestFixture { } void varid_header() { + ASSERT_EQUALS("1: class A@1 ;\n" + "2: struct B {\n" + "3: void setData ( const A@1 & a ) ;\n" + "4: } ;\n", + tokenize("class A;\n" + "struct B {\n" + " void setData(const A & a);\n" + "}; ", "test.h")); ASSERT_EQUALS("1: class A ;\n" "2: struct B {\n" "3: void setData ( const A & a@1 ) ;\n" @@ -2819,7 +2827,15 @@ class TestVarID : public TestFixture { tokenize("class A;\n" "struct B {\n" " void setData(const A & a);\n" - "}; ", "test.h")); + "}; ", "test.hpp")); + ASSERT_EQUALS("1: void f ( )\n" + "2: {\n" + "3: int class@1 ;\n" + "4: }\n", + tokenize("void f()\n" + "{\n" + " int class;\n" + "}", "test.h")); } void varid_rangeBasedFor() { From d710de3ed8c53e2002f87a916fc06a24cb9b4510 Mon Sep 17 00:00:00 2001 From: firewave Date: Wed, 4 Jan 2023 12:54:06 +0100 Subject: [PATCH 4/6] path.cpp: suppress `knownConditionTrueFalse` false positive --- lib/path.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/path.cpp b/lib/path.cpp index f7f3258a3d7..bb67027a674 100644 --- a/lib/path.cpp +++ b/lib/path.cpp @@ -253,6 +253,7 @@ Standards::Language Path::identify(const std::string &path, bool *header) return Standards::Language::CPP; if (c_src_exts.find(ext) != c_src_exts.end()) return Standards::Language::C; + // cppcheck-suppress knownConditionTrueFalse - TODO: FP if (!caseInsensitiveFilesystem()) strTolower(ext); if (ext == ".h") { From f64f81f77bd82f2f78a07d270f12a2ce59358741 Mon Sep 17 00:00:00 2001 From: firewave Date: Mon, 8 Jan 2024 10:26:37 +0100 Subject: [PATCH 5/6] path.cpp: mitigated `unusedFunction` selfcheck warning --- lib/path.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/path.cpp b/lib/path.cpp index bb67027a674..0c849a7802e 100644 --- a/lib/path.cpp +++ b/lib/path.cpp @@ -237,6 +237,7 @@ bool Path::acceptFile(const std::string &path, const std::set &extr return (identify(path, &header) != Standards::Language::None && !header) || extra.find(getFilenameExtension(path)) != extra.end(); } +// cppcheck-suppress unusedFunction bool Path::isHeader(const std::string &path) { const std::string extension = getFilenameExtensionInLowerCase(path); From 5f24e443ebcf54eaa65e20e117990b48844398f9 Mon Sep 17 00:00:00 2001 From: firewave Date: Mon, 8 Jan 2024 10:28:55 +0100 Subject: [PATCH 6/6] path.cpp: mitigated `uninitvar` selfcheck false positive --- lib/path.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/path.cpp b/lib/path.cpp index 0c849a7802e..d469cf352af 100644 --- a/lib/path.cpp +++ b/lib/path.cpp @@ -246,6 +246,7 @@ bool Path::isHeader(const std::string &path) Standards::Language Path::identify(const std::string &path, bool *header) { + // cppcheck-suppress uninitvar - TODO: FP if (header) *header = false;