diff --git a/.github/workflows/asan.yml b/.github/workflows/asan.yml index c298aa18bcb..d644ac5cc75 100644 --- a/.github/workflows/asan.yml +++ b/.github/workflows/asan.yml @@ -69,7 +69,7 @@ jobs: # TODO: disable all warnings - name: CMake run: | - cmake -S . -B cmake.output -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHAVE_RULES=On -DBUILD_TESTS=On -DBUILD_GUI=Off -DWITH_QCHART=Off -DUSE_MATCHCOMPILER=Verify -DANALYZE_ADDRESS=On -DENABLE_CHECK_INTERNAL=On -DUSE_BOOST=On -DCPPCHK_GLIBCXX_DEBUG=Off -DCMAKE_DISABLE_PRECOMPILE_HEADERS=On -DCMAKE_GLOBAL_AUTOGEN_TARGET=Off -DDISABLE_DMAKE=On -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + cmake -S . -B cmake.output -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHAVE_RULES=On -DBUILD_TESTS=On -DBUILD_GUI=Off -DWITH_QCHART=Off -DUSE_MATCHCOMPILER=Verify -DANALYZE_ADDRESS=On -DENABLE_CHECK_INTERNAL=On -DUSE_BOOST=On -DCPPCHK_GLIBCXX_DEBUG=Off -DCMAKE_DISABLE_PRECOMPILE_HEADERS=On -DCMAKE_GLOBAL_AUTOGEN_TARGET=Off -DDISABLE_DMAKE=On -DFILESDIR= -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache env: CC: clang-18 CXX: clang++-18 diff --git a/.github/workflows/tsan.yml b/.github/workflows/tsan.yml index 6471a069c49..040d926f361 100644 --- a/.github/workflows/tsan.yml +++ b/.github/workflows/tsan.yml @@ -68,7 +68,7 @@ jobs: - name: CMake run: | - cmake -S . -B cmake.output -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHAVE_RULES=On -DBUILD_TESTS=On -DBUILD_GUI=Off -DWITH_QCHART=Off -DUSE_MATCHCOMPILER=Verify -DANALYZE_THREAD=On -DENABLE_CHECK_INTERNAL=On -DUSE_BOOST=On -DCPPCHK_GLIBCXX_DEBUG=Off -DCMAKE_DISABLE_PRECOMPILE_HEADERS=On -DCMAKE_GLOBAL_AUTOGEN_TARGET=Off -DDISABLE_DMAKE=On -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + cmake -S . -B cmake.output -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHAVE_RULES=On -DBUILD_TESTS=On -DBUILD_GUI=Off -DWITH_QCHART=Off -DUSE_MATCHCOMPILER=Verify -DANALYZE_THREAD=On -DENABLE_CHECK_INTERNAL=On -DUSE_BOOST=On -DCPPCHK_GLIBCXX_DEBUG=Off -DCMAKE_DISABLE_PRECOMPILE_HEADERS=On -DCMAKE_GLOBAL_AUTOGEN_TARGET=Off -DDISABLE_DMAKE=On -DFILESDIR= -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache env: CC: clang-18 CXX: clang++-18 diff --git a/.github/workflows/ubsan.yml b/.github/workflows/ubsan.yml index d2e7743d558..7a4b628a107 100644 --- a/.github/workflows/ubsan.yml +++ b/.github/workflows/ubsan.yml @@ -68,7 +68,7 @@ jobs: # TODO: disable warnings - name: CMake run: | - cmake -S . -B cmake.output -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHAVE_RULES=On -DBUILD_TESTS=On -DBUILD_GUI=ON -DWITH_QCHART=ON -DUSE_MATCHCOMPILER=Verify -DANALYZE_UNDEFINED=On -DENABLE_CHECK_INTERNAL=On -DUSE_BOOST=On -DCPPCHK_GLIBCXX_DEBUG=Off -DCMAKE_DISABLE_PRECOMPILE_HEADERS=On -DCMAKE_GLOBAL_AUTOGEN_TARGET=On -DDISABLE_DMAKE=On -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + cmake -S . -B cmake.output -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHAVE_RULES=On -DBUILD_TESTS=On -DBUILD_GUI=ON -DWITH_QCHART=ON -DUSE_MATCHCOMPILER=Verify -DANALYZE_UNDEFINED=On -DENABLE_CHECK_INTERNAL=On -DUSE_BOOST=On -DCPPCHK_GLIBCXX_DEBUG=Off -DCMAKE_DISABLE_PRECOMPILE_HEADERS=On -DCMAKE_GLOBAL_AUTOGEN_TARGET=On -DDISABLE_DMAKE=On -DFILESDIR= -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache env: CC: clang-18 CXX: clang++-18 diff --git a/cli/cmdlineparser.cpp b/cli/cmdlineparser.cpp index 2969b40db4a..5da5b898e4c 100644 --- a/cli/cmdlineparser.cpp +++ b/cli/cmdlineparser.cpp @@ -553,6 +553,10 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a std::strcmp(argv[i], "--debug-normal") == 0) mSettings.debugnormal = true; + // Show debug warnings for lookup for configuration files + else if (std::strcmp(argv[i], "--debug-lookup") == 0) + mSettings.debuglookup = true; + // Flag used for various purposes during debugging else if (std::strcmp(argv[i], "--debug-simplified") == 0) mSettings.debugSimplified = true; @@ -1810,9 +1814,9 @@ bool CmdLineParser::isCppcheckPremium() const { return startsWith(mSettings.cppcheckCfgProductName, "Cppcheck Premium"); } -bool CmdLineParser::tryLoadLibrary(Library& destination, const std::string& basepath, const char* filename) +bool CmdLineParser::tryLoadLibrary(Library& destination, const std::string& basepath, const char* filename, bool debug) { - const Library::Error err = destination.load(basepath.c_str(), filename); + const Library::Error err = destination.load(basepath.c_str(), filename, debug); if (err.errorcode == Library::ErrorCode::UNKNOWN_ELEMENT) mLogger.printMessage("Found unknown elements in configuration file '" + std::string(filename) + "': " + err.reason); // TODO: print as errors @@ -1859,7 +1863,7 @@ bool CmdLineParser::tryLoadLibrary(Library& destination, const std::string& base bool CmdLineParser::loadLibraries(Settings& settings) { - if (!tryLoadLibrary(settings.library, settings.exename, "std.cfg")) { + if (!tryLoadLibrary(settings.library, settings.exename, "std.cfg", settings.debuglookup)) { const std::string msg("Failed to load std.cfg. Your Cppcheck installation is broken, please re-install."); #ifdef FILESDIR const std::string details("The Cppcheck binary was compiled with FILESDIR set to \"" @@ -1877,7 +1881,7 @@ bool CmdLineParser::loadLibraries(Settings& settings) bool result = true; for (const auto& lib : settings.libraries) { - if (!tryLoadLibrary(settings.library, settings.exename, lib.c_str())) { + if (!tryLoadLibrary(settings.library, settings.exename, lib.c_str(), settings.debuglookup)) { result = false; } } diff --git a/cli/cmdlineparser.h b/cli/cmdlineparser.h index fcdd2157ecc..33e4352d958 100644 --- a/cli/cmdlineparser.h +++ b/cli/cmdlineparser.h @@ -137,7 +137,7 @@ class CmdLineParser { * Tries to load a library and prints warning/error messages * @return false, if an error occurred (except unknown XML elements) */ - bool tryLoadLibrary(Library& destination, const std::string& basepath, const char* filename); + bool tryLoadLibrary(Library& destination, const std::string& basepath, const char* filename, bool debug); /** * @brief Load libraries diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 8eb30a1f60f..d4b28c43c04 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -836,24 +836,33 @@ void MainWindow::addIncludeDirs(const QStringList &includeDirs, Settings &result } } -Library::Error MainWindow::loadLibrary(Library &library, const QString &filename) +Library::Error MainWindow::loadLibrary(Library &library, const QString &filename, bool debug) { Library::Error ret; // Try to load the library from the project folder.. if (mProjectFile) { QString path = QFileInfo(mProjectFile->getFilename()).canonicalPath(); - ret = library.load(nullptr, (path+"/"+filename).toLatin1()); + QString libpath = path+"/"+filename; + if (debug) + std::cout << "looking for library '" + libpath.toStdString() + "'" << std::endl; + ret = library.load(nullptr, libpath.toLatin1()); if (ret.errorcode != Library::ErrorCode::FILE_NOT_FOUND) return ret; } // Try to load the library from the application folder.. const QString appPath = QFileInfo(QCoreApplication::applicationFilePath()).canonicalPath(); - ret = library.load(nullptr, (appPath+"/"+filename).toLatin1()); + QString libpath = appPath+"/"+filename; + if (debug) + std::cout << "looking for library '" + libpath.toStdString() + "'" << std::endl; + ret = library.load(nullptr, libpath.toLatin1()); if (ret.errorcode != Library::ErrorCode::FILE_NOT_FOUND) return ret; - ret = library.load(nullptr, (appPath+"/cfg/"+filename).toLatin1()); + libpath = appPath+"/cfg/"+filename; + if (debug) + std::cout << "looking for library '" + libpath.toStdString() + "'" << std::endl; + ret = library.load(nullptr, libpath.toLatin1()); if (ret.errorcode != Library::ErrorCode::FILE_NOT_FOUND) return ret; @@ -861,10 +870,16 @@ Library::Error MainWindow::loadLibrary(Library &library, const QString &filename // Try to load the library from FILESDIR/cfg.. const QString filesdir = FILESDIR; if (!filesdir.isEmpty()) { - ret = library.load(nullptr, (filesdir+"/cfg/"+filename).toLatin1()); + libpath = filesdir+"/cfg/"+filename; + if (debug) + std::cout << "looking for library '" + libpath.toStdString() + "'" << std::endl; + ret = library.load(nullptr, libpath.toLatin1()); if (ret.errorcode != Library::ErrorCode::FILE_NOT_FOUND) return ret; - ret = library.load(nullptr, (filesdir+filename).toLatin1()); + libpath = filesdir+filename; + if (debug) + std::cout << "looking for library '" + libpath.toStdString() + "'" << std::endl; + ret = library.load(nullptr, libpath.toLatin1()); if (ret.errorcode != Library::ErrorCode::FILE_NOT_FOUND) return ret; } @@ -873,14 +888,23 @@ Library::Error MainWindow::loadLibrary(Library &library, const QString &filename // Try to load the library from the cfg subfolder.. const QString datadir = getDataDir(); if (!datadir.isEmpty()) { - ret = library.load(nullptr, (datadir+"/"+filename).toLatin1()); + libpath = datadir+"/"+filename; + if (debug) + std::cout << "looking for library '" + libpath.toStdString() + "'" << std::endl; + ret = library.load(nullptr, libpath.toLatin1()); if (ret.errorcode != Library::ErrorCode::FILE_NOT_FOUND) return ret; - ret = library.load(nullptr, (datadir+"/cfg/"+filename).toLatin1()); + libpath = datadir+"/cfg/"+filename; + if (debug) + std::cout << "looking for library '" + libpath.toStdString() + "'" << std::endl; + ret = library.load(nullptr, libpath.toLatin1()); if (ret.errorcode != Library::ErrorCode::FILE_NOT_FOUND) return ret; } + if (debug) + std::cout << "library not found: '" + filename.toStdString() + "'" << std::endl; + return ret; } diff --git a/gui/mainwindow.h b/gui/mainwindow.h index 6a039bf0af1..61be78af194 100644 --- a/gui/mainwindow.h +++ b/gui/mainwindow.h @@ -392,7 +392,7 @@ private slots: * @param filename filename (no path) * @return error code */ - Library::Error loadLibrary(Library &library, const QString &filename); + Library::Error loadLibrary(Library &library, const QString &filename, bool debug = false); /** * @brief Tries to load library file, prints message on error diff --git a/lib/library.cpp b/lib/library.cpp index 07270b7e520..57b89917172 100644 --- a/lib/library.cpp +++ b/lib/library.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -65,9 +66,12 @@ static void gettokenlistfromvalid(const std::string& valid, bool cpp, TokenList& } } -Library::Error Library::load(const char exename[], const char path[]) +Library::Error Library::load(const char exename[], const char path[], bool debug) { + // TODO: remove handling of multiple libraries at once? if (std::strchr(path,',') != nullptr) { + if (debug) + std::cout << "handling multiple libraries '" + std::string(path) + "'" << std::endl; std::string p(path); for (;;) { const std::string::size_type pos = p.find(','); @@ -86,15 +90,21 @@ Library::Error Library::load(const char exename[], const char path[]) std::string absolute_path; // open file.. tinyxml2::XMLDocument doc; + if (debug) + std::cout << "looking for library '" + std::string(path) + "'" << std::endl; tinyxml2::XMLError error = doc.LoadFile(path); if (error == tinyxml2::XML_ERROR_FILE_READ_ERROR && Path::getFilenameExtension(path).empty()) + { // Reading file failed, try again... error = tinyxml2::XML_ERROR_FILE_NOT_FOUND; + } if (error == tinyxml2::XML_ERROR_FILE_NOT_FOUND) { // failed to open file.. is there no extension? std::string fullfilename(path); if (Path::getFilenameExtension(fullfilename).empty()) { fullfilename += ".cfg"; + if (debug) + std::cout << "looking for library '" + std::string(fullfilename) + "'" << std::endl; error = doc.LoadFile(fullfilename.c_str()); if (error != tinyxml2::XML_ERROR_FILE_NOT_FOUND) absolute_path = Path::getAbsoluteFilePath(fullfilename); @@ -116,6 +126,8 @@ Library::Error Library::load(const char exename[], const char path[]) cfgfolders.pop_back(); const char *sep = (!cfgfolder.empty() && endsWith(cfgfolder,'/') ? "" : "/"); const std::string filename(cfgfolder + sep + fullfilename); + if (debug) + std::cout << "looking for library '" + std::string(filename) + "'" << std::endl; error = doc.LoadFile(filename.c_str()); if (error != tinyxml2::XML_ERROR_FILE_NOT_FOUND) absolute_path = Path::getAbsoluteFilePath(filename); @@ -134,10 +146,13 @@ Library::Error Library::load(const char exename[], const char path[]) return Error(ErrorCode::OK); // ignore duplicates } + if (debug) + std::cout << "library not found: '" + std::string(path) + "'" << std::endl; + if (error == tinyxml2::XML_ERROR_FILE_NOT_FOUND) return Error(ErrorCode::FILE_NOT_FOUND); - doc.PrintError(); + doc.PrintError(); // TODO: do not print stray messages return Error(ErrorCode::BAD_XML); } diff --git a/lib/library.h b/lib/library.h index 0dc600b994b..941a165e27c 100644 --- a/lib/library.h +++ b/lib/library.h @@ -71,7 +71,7 @@ class CPPCHECKLIB Library { std::string reason; }; - Error load(const char exename[], const char path[]); + Error load(const char exename[], const char path[], bool debug = false); struct AllocFunc { int groupId; diff --git a/lib/settings.h b/lib/settings.h index 2c356d827dc..6ec189c9c2c 100644 --- a/lib/settings.h +++ b/lib/settings.h @@ -173,6 +173,9 @@ class CPPCHECKLIB WARN_UNUSED Settings { /** @brief Are we running from DACA script? */ bool daca{}; + /** @brief Internal: Is --debug-lookup given? */ + bool debuglookup{}; + /** @brief Is --debug-normal given? */ bool debugnormal{}; diff --git a/test/cli/other_test.py b/test/cli/other_test.py index 181b63b2255..f497459ea82 100644 --- a/test/cli/other_test.py +++ b/test/cli/other_test.py @@ -6,7 +6,24 @@ import pytest import json -from testutils import cppcheck, assert_cppcheck +from testutils import cppcheck, assert_cppcheck, cppcheck_ex + + +def __remove_std_lookup_log(l : list, exepath): + print(l) + l.remove("looking for library 'std.cfg'") + l.remove("looking for library '{}/std.cfg'".format(exepath)) + l.remove("looking for library '{}/../cfg/std.cfg'".format(exepath)) + l.remove("looking for library '{}/cfg/std.cfg'".format(exepath)) + return l + + +def __remove_verbose_log(l : list): + l.remove('Defines:') + l.remove('Undefines:') + l.remove('Includes:') + l.remove('Platform:native') + return l def test_missing_include(tmpdir): # #11283 @@ -452,13 +469,9 @@ class _clz { exitcode, stdout, stderr = cppcheck(args) assert exitcode == 0 - lines = stdout.splitlines() + lines = __remove_verbose_log(stdout.splitlines()) assert lines == [ - 'Checking {} ...'.format(test_file), - 'Defines:', - 'Undefines:', - 'Includes:', - 'Platform:native' + 'Checking {} ...'.format(test_file) ] lines = [line for line in stderr.splitlines() if line != ''] expect = [ @@ -585,13 +598,9 @@ def test_addon_namingng_config(tmpdir): exitcode, stdout, stderr = cppcheck(args) assert exitcode == 0 - lines = stdout.splitlines() + lines = __remove_verbose_log(stdout.splitlines()) assert lines == [ - 'Checking {} ...'.format(test_file), - 'Defines:', - 'Undefines:', - 'Includes:', - 'Platform:native' + 'Checking {} ...'.format(test_file) ] lines = stderr.splitlines() # ignore the first line, stating that the addon failed to run properly @@ -725,13 +734,9 @@ def test_invalid_addon_py_verbose(tmpdir): exitcode, stdout, stderr = cppcheck(args) assert exitcode == 0 # TODO: needs to be 1 - lines = stdout.splitlines() + lines = __remove_verbose_log(stdout.splitlines()) assert lines == [ - 'Checking {} ...'.format(test_file), - 'Defines:', - 'Undefines:', - 'Includes:', - 'Platform:native' + 'Checking {} ...'.format(test_file) ] """ /tmp/pytest-of-user/pytest-11/test_invalid_addon_py_20/file.cpp:0:0: error: Bailing out from analysis: Checking file failed: Failed to execute addon 'addon1' - exitcode is 1: python3 /home/user/CLionProjects/cppcheck/addons/runaddon.py /tmp/pytest-of-user/pytest-11/test_invalid_addon_py_20/addon1.py --cli /tmp/pytest-of-user/pytest-11/test_invalid_addon_py_20/file.cpp.24762.dump @@ -1497,13 +1502,17 @@ def test_cpp_probe(tmpdir): ]) args = ['-q', '--template=simple', '--cpp-header-probe', '--verbose', test_file] - err_lines = [ + + exitcode, stdout, stderr = cppcheck(args) + assert exitcode == 0, stdout + lines = stdout.splitlines() + assert lines == [] + lines = stderr.splitlines() + assert lines == [ # TODO: fix that awkward format "{}:1:1: error: Code 'classA{{' is invalid C code.: Use --std, -x or --language to enforce C++. Or --cpp-header-probe to identify C++ headers via the Emacs marker. [syntaxError]".format(test_file) ] - assert_cppcheck(args, ec_exp=0, err_exp=err_lines, out_exp=[]) - def test_cpp_probe_2(tmpdir): test_file = os.path.join(tmpdir, 'test.h') @@ -1516,3 +1525,48 @@ def test_cpp_probe_2(tmpdir): args = ['-q', '--template=simple', '--cpp-header-probe', test_file] assert_cppcheck(args, ec_exp=0, err_exp=[], out_exp=[]) + + +# TODO: test with FILESDIR +def test_lib_lookup(tmpdir): + test_file = os.path.join(tmpdir, 'test.c') + with open(test_file, 'wt'): + pass + + exitcode, stdout, _, exe = cppcheck_ex(['--library=gnu', '--debug-lookup', test_file]) + exepath = os.path.dirname(exe) + if sys.platform == 'win32': + exepath = exepath.replace('\\', '/') + assert exitcode == 0, stdout + lines = __remove_std_lookup_log(stdout.splitlines(), exepath) + assert lines == [ + "looking for library 'gnu'", + "looking for library 'gnu.cfg'", + "looking for library '{}/gnu.cfg'".format(exepath), + "looking for library '{}/../cfg/gnu.cfg'".format(exepath), + "looking for library '{}/cfg/gnu.cfg'".format(exepath), + 'Checking {} ...'.format(test_file) + ] + + +# TODO: test with FILESDIR +def test_lib_lookup_notfound(tmpdir): + test_file = os.path.join(tmpdir, 'test.c') + with open(test_file, 'wt'): + pass + + exitcode, stdout, _, exe = cppcheck_ex(['--library=none', '--debug-lookup', test_file]) + exepath = os.path.dirname(exe) + if sys.platform == 'win32': + exepath = exepath.replace('\\', '/') + assert exitcode == 1, stdout + lines = __remove_std_lookup_log(stdout.splitlines(), exepath) + assert lines == [ + "looking for library 'none'", + "looking for library 'none.cfg'", + "looking for library '{}/none.cfg'".format(exepath), + "looking for library '{}/../cfg/none.cfg'".format(exepath), + "looking for library '{}/cfg/none.cfg'".format(exepath), + "library not found: 'none'", + "cppcheck: Failed to load library configuration file 'none'. File not found" + ] diff --git a/test/cli/testutils.py b/test/cli/testutils.py index 6ad31e95d94..2e875e2126c 100644 --- a/test/cli/testutils.py +++ b/test/cli/testutils.py @@ -150,7 +150,7 @@ def __run_subprocess(args, env=None, cwd=None, timeout=None): # Run Cppcheck with args -def cppcheck(args, env=None, remove_checkers_report=True, cwd=None, cppcheck_exe=None, timeout=None, tty=False): +def cppcheck_ex(args, env=None, remove_checkers_report=True, cwd=None, cppcheck_exe=None, timeout=None, tty=False): exe = cppcheck_exe if cppcheck_exe else __lookup_cppcheck_exe() assert exe is not None, 'no cppcheck binary found' @@ -211,6 +211,11 @@ def cppcheck(args, env=None, remove_checkers_report=True, cwd=None, cppcheck_exe stderr = '' elif stderr[pos - 1] == '\n': stderr = stderr[:pos] + return return_code, stdout, stderr, exe + + +def cppcheck(*args, **kwargs): + return_code, stdout, stderr, _ = cppcheck_ex(*args, **kwargs) return return_code, stdout, stderr