diff --git a/Makefile b/Makefile index c1db7c3f1..04c6a3cbd 100644 --- a/Makefile +++ b/Makefile @@ -264,6 +264,7 @@ CLIOBJ = cli/cmdlineparser.o \ cli/threadexecutor.o TESTOBJ = test/fixture.o \ + test/helpers.o \ test/main.o \ test/options.o \ test/test64bit.o \ @@ -659,6 +660,9 @@ cli/threadexecutor.o: cli/threadexecutor.cpp cli/cppcheckexecutor.h cli/executor test/fixture.o: test/fixture.cpp lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/suppressions.h test/fixture.h test/options.h test/redirect.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/fixture.cpp +test/helpers.o: test/helpers.cpp lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/helpers.h + $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/helpers.cpp + test/main.o: test/main.cpp externals/simplecpp/simplecpp.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/preprocessor.h lib/suppressions.h test/fixture.h test/options.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/main.cpp @@ -770,7 +774,7 @@ test/testplatform.o: test/testplatform.cpp externals/tinyxml2/tinyxml2.h lib/che test/testpostfixoperator.o: test/testpostfixoperator.cpp lib/check.h lib/checkpostfixoperator.h 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/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testpostfixoperator.cpp -test/testpreprocessor.o: test/testpreprocessor.cpp externals/simplecpp/simplecpp.h lib/check.h 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/preprocessor.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h +test/testpreprocessor.o: test/testpreprocessor.cpp externals/simplecpp/simplecpp.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/preprocessor.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testpreprocessor.cpp test/testprocessexecutor.o: test/testprocessexecutor.cpp cli/executor.h cli/processexecutor.h lib/check.h 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/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h test/redirect.h diff --git a/lib/preprocessor.cpp b/lib/preprocessor.cpp index fd18ae1d1..b329a82e7 100644 --- a/lib/preprocessor.cpp +++ b/lib/preprocessor.cpp @@ -626,6 +626,7 @@ static simplecpp::DUI createDUI(const Settings &mSettings, const std::string &cf dui.std = mSettings.standards.getCPP(); else dui.std = mSettings.standards.getC(); + dui.clearIncludeCache = mSettings.clearIncludeCache; return dui; } diff --git a/lib/settings.cpp b/lib/settings.cpp index 94d455afb..70d09a769 100644 --- a/lib/settings.cpp +++ b/lib/settings.cpp @@ -44,6 +44,7 @@ Settings::Settings() clang(false), clangExecutable("clang"), clangTidy(false), + clearIncludeCache(false), daca(false), debugnormal(false), debugSimplified(false), diff --git a/lib/settings.h b/lib/settings.h index 8216b2d18..08f8af126 100644 --- a/lib/settings.h +++ b/lib/settings.h @@ -150,6 +150,9 @@ public: /** Use clang-tidy */ bool clangTidy; + /** Internal: Clear the simplecpp non-existing include cache */ + bool clearIncludeCache; + /** @brief include paths excluded from checking the configuration */ std::set configExcludePaths; diff --git a/test/helpers.cpp b/test/helpers.cpp new file mode 100644 index 000000000..31af95c7f --- /dev/null +++ b/test/helpers.cpp @@ -0,0 +1,61 @@ +/* + * Cppcheck - A tool for static C/C++ code analysis + * Copyright (C) 2007-2022 Cppcheck team. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "helpers.h" + +#include "path.h" + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#endif + +ScopedFile::ScopedFile(std::string name, const std::string &content, std::string path) + : mName(std::move(name)) + , mPath(Path::toNativeSeparators(std::move(path))) + , mFullPath(Path::join(mPath, mName)) +{ + if (!mPath.empty() && mPath != Path::getCurrentPath()) { +#ifdef _WIN32 + if (!CreateDirectoryA(mPath.c_str(), nullptr)) + throw std::runtime_error("ScopedFile(" + mFullPath + ") - could not create directory"); +#else + if (mkdir(mPath.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) != 0) + throw std::runtime_error("ScopedFile(" + mFullPath + ") - could not create directory"); +#endif + } + + std::ofstream of(mFullPath); + if (!of.is_open()) + throw std::runtime_error("ScopedFile(" + mFullPath + ") - could not open file"); + of << content; +} + +ScopedFile::~ScopedFile() { + std::remove(mFullPath.c_str()); + if (!mPath.empty() && mPath != Path::getCurrentPath()) { +#ifdef _WIN32 + RemoveDirectoryA(mPath.c_str()); +#else + rmdir(mPath.c_str()); +#endif + } +} diff --git a/test/helpers.h b/test/helpers.h index 76ec87953..1a50b05a1 100644 --- a/test/helpers.h +++ b/test/helpers.h @@ -74,16 +74,17 @@ private: class ScopedFile { public: - ScopedFile(std::string name, const std::string &content) : mName(std::move(name)) { - std::ofstream of(mName); - of << content; - } + ScopedFile(std::string name, const std::string &content, std::string path = ""); + ~ScopedFile(); - ~ScopedFile() { - remove(mName.c_str()); + const std::string& path() const + { + return mFullPath; } private: - std::string mName; + const std::string mName; + const std::string mPath; + const std::string mFullPath; }; #endif // helpersH diff --git a/test/testpreprocessor.cpp b/test/testpreprocessor.cpp index a9bfcbc3e..ed7776e1d 100644 --- a/test/testpreprocessor.cpp +++ b/test/testpreprocessor.cpp @@ -21,6 +21,7 @@ // the code for a known configuration, it generates the code for each configuration. #include "errortypes.h" +#include "path.h" #include "platform.h" #include "preprocessor.h" #include "settings.h" @@ -259,6 +260,17 @@ private: TEST_CASE(testDirectiveIncludeComments); TEST_CASE(testMissingInclude); + TEST_CASE(testMissingInclude2); + TEST_CASE(testMissingInclude3); + TEST_CASE(testMissingInclude4); + TEST_CASE(testMissingInclude5); + TEST_CASE(testMissingInclude6); + TEST_CASE(testMissingSystemInclude); + TEST_CASE(testMissingSystemInclude2); + TEST_CASE(testMissingSystemInclude3); + TEST_CASE(testMissingSystemInclude4); + TEST_CASE(testMissingSystemInclude5); + TEST_CASE(testMissingIncludeMixed); TEST_CASE(testMissingIncludeCheckConfig); } @@ -2432,11 +2444,299 @@ private: ASSERT_EQUALS(dumpdata, ostr.str()); } + // test for existing local include void testMissingInclude() { Preprocessor::missingIncludeFlag = false; Preprocessor::missingSystemIncludeFlag = false; Settings settings; + settings.clearIncludeCache = true; + settings.severity.clear(); + settings.checks.enable(Checks::missingInclude); + Preprocessor preprocessor(settings, this); + + ScopedFile header("header.h", ""); + + std::string code("#include \"header.h\""); + errout.str(""); + preprocessor.getcode(code, "", "test.c"); + ASSERT_EQUALS(false, Preprocessor::missingIncludeFlag); + ASSERT_EQUALS(false, Preprocessor::missingSystemIncludeFlag); + + // the expected messages are emitted outside of the Preprocessor + ASSERT_EQUALS("", errout.str()); + + Preprocessor::missingIncludeFlag = false; + Preprocessor::missingSystemIncludeFlag = false; + } + + // test for missing local include + void testMissingInclude2() { + Preprocessor::missingIncludeFlag = false; + Preprocessor::missingSystemIncludeFlag = false; + + Settings settings; + settings.clearIncludeCache = true; + settings.severity.clear(); + settings.checks.enable(Checks::missingInclude); + Preprocessor preprocessor(settings, this); + + std::string code("#include \"header.h\""); + errout.str(""); + preprocessor.getcode(code, "", "test.c"); + ASSERT_EQUALS(true, Preprocessor::missingIncludeFlag); + ASSERT_EQUALS(false, Preprocessor::missingSystemIncludeFlag); + + // the expected messages are emitted outside of the Preprocessor + ASSERT_EQUALS("", errout.str()); + + Preprocessor::missingIncludeFlag = false; + Preprocessor::missingSystemIncludeFlag = false; + } + + // test for missing local include - no include path given + void testMissingInclude3() { + Preprocessor::missingIncludeFlag = false; + Preprocessor::missingSystemIncludeFlag = false; + + Settings settings; + settings.clearIncludeCache = true; + settings.severity.clear(); + settings.checks.enable(Checks::missingInclude); + Preprocessor preprocessor(settings, this); + + ScopedFile header("header.h", "", "inc"); + + std::string code("#include \"header.h\""); + errout.str(""); + preprocessor.getcode(code, "", "test.c"); + ASSERT_EQUALS(true, Preprocessor::missingIncludeFlag); + ASSERT_EQUALS(false, Preprocessor::missingSystemIncludeFlag); + + // the expected messages are emitted outside of the Preprocessor + ASSERT_EQUALS("", errout.str()); + + Preprocessor::missingIncludeFlag = false; + Preprocessor::missingSystemIncludeFlag = false; + } + + // test for existing local include - include path provided + void testMissingInclude4() { + Preprocessor::missingIncludeFlag = false; + Preprocessor::missingSystemIncludeFlag = false; + + Settings settings; + settings.clearIncludeCache = true; + settings.severity.clear(); + settings.checks.enable(Checks::missingInclude); + settings.includePaths.emplace_back("inc"); + Preprocessor preprocessor(settings, this); + + ScopedFile header("header.h", "", "inc"); + + std::string code("#include \"inc/header.h\""); + errout.str(""); + preprocessor.getcode(code, "", "test.c"); + ASSERT_EQUALS(false, Preprocessor::missingIncludeFlag); + ASSERT_EQUALS(false, Preprocessor::missingSystemIncludeFlag); + + // the expected messages are emitted outside of the Preprocessor + ASSERT_EQUALS("", errout.str()); + + Preprocessor::missingIncludeFlag = false; + Preprocessor::missingSystemIncludeFlag = false; + } + + // test for existing local include - absolute path + void testMissingInclude5() { + Preprocessor::missingIncludeFlag = false; + Preprocessor::missingSystemIncludeFlag = false; + + Settings settings; + settings.clearIncludeCache = true; + settings.severity.clear(); + settings.checks.enable(Checks::missingInclude); + settings.includePaths.emplace_back("inc"); + Preprocessor preprocessor(settings, this); + + ScopedFile header("header.h", "", Path::getCurrentPath()); + + std::string code("#include \"" + header.path() + "\""); + errout.str(""); + preprocessor.getcode(code, "", "test.c"); + ASSERT_EQUALS(false, Preprocessor::missingIncludeFlag); + ASSERT_EQUALS(false, Preprocessor::missingSystemIncludeFlag); + + // the expected messages are emitted outside of the Preprocessor + ASSERT_EQUALS("", errout.str()); + + Preprocessor::missingIncludeFlag = false; + Preprocessor::missingSystemIncludeFlag = false; + } + + // test for missing local include - absolute path + void testMissingInclude6() { + Preprocessor::missingIncludeFlag = false; + Preprocessor::missingSystemIncludeFlag = false; + + Settings settings; + settings.clearIncludeCache = true; + settings.severity.clear(); + settings.checks.enable(Checks::missingInclude); + Preprocessor preprocessor(settings, this); + + const std::string header = Path::join(Path::getCurrentPath(), "header.h"); + + std::string code("#include \"" + header + "\""); + errout.str(""); + preprocessor.getcode(code, "", "test.c"); + ASSERT_EQUALS(true, Preprocessor::missingIncludeFlag); + ASSERT_EQUALS(false, Preprocessor::missingSystemIncludeFlag); + + // the expected messages are emitted outside of the Preprocessor + ASSERT_EQUALS("", errout.str()); + + Preprocessor::missingIncludeFlag = false; + Preprocessor::missingSystemIncludeFlag = false; + } + + // test for missing system include - system includes are not searched for in relative path + void testMissingSystemInclude() { + Preprocessor::missingIncludeFlag = false; + Preprocessor::missingSystemIncludeFlag = false; + + Settings settings; + settings.clearIncludeCache = true; + settings.severity.clear(); + settings.checks.enable(Checks::missingInclude); + Preprocessor preprocessor(settings, this); + + ScopedFile header("header.h", ""); + + std::string code("#include "); + errout.str(""); + preprocessor.getcode(code, "", "test.c"); + ASSERT_EQUALS(false, Preprocessor::missingIncludeFlag); + ASSERT_EQUALS(true, Preprocessor::missingSystemIncludeFlag); + + // the expected messages are emitted outside of the Preprocessor + ASSERT_EQUALS("", errout.str()); + + Preprocessor::missingIncludeFlag = false; + Preprocessor::missingSystemIncludeFlag = false; + } + + // test for missing system include + void testMissingSystemInclude2() { + Preprocessor::missingIncludeFlag = false; + Preprocessor::missingSystemIncludeFlag = false; + + Settings settings; + settings.clearIncludeCache = true; + settings.severity.clear(); + settings.checks.enable(Checks::missingInclude); + Preprocessor preprocessor(settings, this); + + std::string code("#include "); + errout.str(""); + preprocessor.getcode(code, "", "test.c"); + ASSERT_EQUALS(false, Preprocessor::missingIncludeFlag); + ASSERT_EQUALS(true, Preprocessor::missingSystemIncludeFlag); + + // the expected messages are emitted outside of the Preprocessor + ASSERT_EQUALS("", errout.str()); + + Preprocessor::missingIncludeFlag = false; + Preprocessor::missingSystemIncludeFlag = false; + } + + // test for existing system include in system include path + void testMissingSystemInclude3() { + Preprocessor::missingIncludeFlag = false; + Preprocessor::missingSystemIncludeFlag = false; + + Settings settings; + settings.clearIncludeCache = true; + settings.severity.clear(); + settings.checks.enable(Checks::missingInclude); + settings.includePaths.emplace_back("system"); + Preprocessor preprocessor(settings, this); + + ScopedFile header("header.h", "", "system"); + + std::string code("#include "); + errout.str(""); + preprocessor.getcode(code, "", "test.c"); + ASSERT_EQUALS(false, Preprocessor::missingIncludeFlag); + ASSERT_EQUALS(false, Preprocessor::missingSystemIncludeFlag); + + // the expected messages are emitted outside of the Preprocessor + ASSERT_EQUALS("", errout.str()); + + Preprocessor::missingIncludeFlag = false; + Preprocessor::missingSystemIncludeFlag = false; + } + + // test for existing system include - absolute path + void testMissingSystemInclude4() { + Preprocessor::missingIncludeFlag = false; + Preprocessor::missingSystemIncludeFlag = false; + + Settings settings; + settings.clearIncludeCache = true; + settings.severity.clear(); + settings.checks.enable(Checks::missingInclude); + settings.includePaths.emplace_back("inc"); + Preprocessor preprocessor(settings, this); + + ScopedFile header("header.h", "", Path::getCurrentPath()); + + std::string code("#include <" + header.path() + ">"); + errout.str(""); + preprocessor.getcode(code, "", "test.c"); + ASSERT_EQUALS(false, Preprocessor::missingIncludeFlag); + ASSERT_EQUALS(false, Preprocessor::missingSystemIncludeFlag); + + // the expected messages are emitted outside of the Preprocessor + ASSERT_EQUALS("", errout.str()); + + Preprocessor::missingIncludeFlag = false; + Preprocessor::missingSystemIncludeFlag = false; + } + + // test for missing system include - absolute path + void testMissingSystemInclude5() { + Preprocessor::missingIncludeFlag = false; + Preprocessor::missingSystemIncludeFlag = false; + + Settings settings; + settings.clearIncludeCache = true; + settings.severity.clear(); + settings.checks.enable(Checks::missingInclude); + Preprocessor preprocessor(settings, this); + + const std::string header = Path::join(Path::getCurrentPath(), "header.h"); + + std::string code("#include <" + header + ">"); + errout.str(""); + preprocessor.getcode(code, "", "test.c"); + ASSERT_EQUALS(false, Preprocessor::missingIncludeFlag); + ASSERT_EQUALS(true, Preprocessor::missingSystemIncludeFlag); + + // the expected messages are emitted outside of the Preprocessor + ASSERT_EQUALS("", errout.str()); + + Preprocessor::missingIncludeFlag = false; + Preprocessor::missingSystemIncludeFlag = false; + } + + // test for missing local and system include + void testMissingIncludeMixed() { + Preprocessor::missingIncludeFlag = false; + Preprocessor::missingSystemIncludeFlag = false; + + Settings settings; + settings.clearIncludeCache = true; settings.severity.clear(); settings.checks.enable(Checks::missingInclude); Preprocessor preprocessor(settings, this); @@ -2465,18 +2765,34 @@ private: Preprocessor::missingSystemIncludeFlag = false; Settings settings; + settings.clearIncludeCache = true; settings.checkConfiguration = true; settings.severity.clear(); + settings.includePaths.emplace_back("system"); // needs to be reported regardless of enabled checks Preprocessor preprocessor(settings, this); ScopedFile header("header.h", ""); ScopedFile header2("header2.h", ""); + ScopedFile header3("header3.h", "", "system"); + ScopedFile header4("header4.h", "", "inc"); + ScopedFile header5("header5.h", "", Path::getCurrentPath()); + ScopedFile header6("header6.h", "", Path::getCurrentPath()); + + const std::string missing3 = Path::join(Path::getCurrentPath(), "missing3.h"); + const std::string missing4 = Path::join(Path::getCurrentPath(), "missing4.h"); std::string code("#include \"missing.h\"\n" "#include \n" "#include \n" - "#include \"header2.h\""); + "#include \"header2.h\"\n" + "#include \n" + "#include \"header4.h\"\n" + "#include \"inc/header4.h\"\n" + "#include \"" + header5.path() + "\"\n" + "#include \"" + missing3 + "\"\n" + "#include <" + header6.path() + ">\n" + "#include <" + missing4 + ">\n"); errout.str(""); preprocessor.getcode(code, "", "test.c"); ASSERT_EQUALS(true, Preprocessor::missingIncludeFlag); @@ -2484,7 +2800,10 @@ private: ASSERT_EQUALS("[test.c:1]: (information) Include file: \"missing.h\" not found.\n" "[test.c:2]: (information) Include file: not found. Please note: Cppcheck does not need standard library headers to get proper results.\n" - "[test.c:3]: (information) Include file: not found. Please note: Cppcheck does not need standard library headers to get proper results.\n", errout.str()); + "[test.c:3]: (information) Include file: not found. Please note: Cppcheck does not need standard library headers to get proper results.\n" + "[test.c:6]: (information) Include file: \"header4.h\" not found.\n" + "[test.c:9]: (information) Include file: \"" + missing3 + "\" not found.\n" + "[test.c:11]: (information) Include file: <" + missing4 + "> not found. Please note: Cppcheck does not need standard library headers to get proper results.\n", errout.str()); Preprocessor::missingIncludeFlag = false; Preprocessor::missingSystemIncludeFlag = false; diff --git a/test/testrunner.vcxproj b/test/testrunner.vcxproj index 5be67464d..3ed1f7bec 100755 --- a/test/testrunner.vcxproj +++ b/test/testrunner.vcxproj @@ -39,6 +39,7 @@ Create Create +