diff --git a/Makefile b/Makefile index 884f81d876e..a716d534af2 100644 --- a/Makefile +++ b/Makefile @@ -701,7 +701,7 @@ test/options.o: test/options.cpp test/options.h test/test64bit.o: test/test64bit.cpp lib/addoninfo.h lib/check.h lib/check64bit.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/test64bit.cpp -test/testanalyzerinformation.o: test/testanalyzerinformation.cpp lib/addoninfo.h lib/analyzerinfo.h lib/check.h lib/checkers.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/utils.h test/fixture.h +test/testanalyzerinformation.o: test/testanalyzerinformation.cpp lib/addoninfo.h lib/analyzerinfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/standards.h lib/utils.h test/fixture.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testanalyzerinformation.cpp test/testassert.o: test/testassert.cpp lib/addoninfo.h lib/check.h lib/checkassert.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h diff --git a/gui/checkthread.cpp b/gui/checkthread.cpp index 40396153b9c..e6fc23a0fd8 100644 --- a/gui/checkthread.cpp +++ b/gui/checkthread.cpp @@ -245,7 +245,7 @@ void CheckThread::runAddonsAndTools(const Settings& settings, const FileSettings const std::string &buildDir = settings.buildDir; if (!buildDir.empty()) { - analyzerInfoFile = QString::fromStdString(AnalyzerInformation::getAnalyzerInfoFile(buildDir, fileSettings->filename(), fileSettings->cfg)); + analyzerInfoFile = QString::fromStdString(AnalyzerInformation::getAnalyzerInfoFile(buildDir, fileSettings->filename(), fileSettings->cfg, fileSettings->fileIndex)); QStringList args2(args); args2.insert(0,"-E"); diff --git a/lib/analyzerinfo.cpp b/lib/analyzerinfo.cpp index 9d5b914c2bd..dfb953c9445 100644 --- a/lib/analyzerinfo.cpp +++ b/lib/analyzerinfo.cpp @@ -25,6 +25,7 @@ #include #include +#include #include "xml.h" @@ -47,21 +48,30 @@ static std::string getFilename(const std::string &fullpath) void AnalyzerInformation::writeFilesTxt(const std::string &buildDir, const std::list &sourcefiles, const std::string &userDefines, const std::list &fileSettings) { - std::map fileCount; - const std::string filesTxt(buildDir + "/files.txt"); std::ofstream fout(filesTxt); + fout << getFilesTxt(sourcefiles, userDefines, fileSettings); +} + +std::string AnalyzerInformation::getFilesTxt(const std::list &sourcefiles, const std::string &userDefines, const std::list &fileSettings) { + std::ostringstream ret; + + std::map fileCount; + for (const std::string &f : sourcefiles) { const std::string afile = getFilename(f); - fout << afile << ".a" << (++fileCount[afile]) << "::" << Path::simplifyPath(f) << '\n'; + ret << afile << ".a" << (++fileCount[afile]) << sep << sep << sep << Path::simplifyPath(f) << '\n'; if (!userDefines.empty()) - fout << afile << ".a" << (++fileCount[afile]) << ":" << userDefines << ":" << Path::simplifyPath(f) << '\n'; + ret << afile << ".a" << (++fileCount[afile]) << sep << userDefines << sep << sep << Path::simplifyPath(f) << '\n'; } for (const FileSettings &fs : fileSettings) { const std::string afile = getFilename(fs.filename()); - fout << afile << ".a" << (++fileCount[afile]) << ":" << fs.cfg << ":" << Path::simplifyPath(fs.filename()) << std::endl; + const std::string id = fs.fileIndex > 0 ? std::to_string(fs.fileIndex) : ""; + ret << afile << ".a" << (++fileCount[afile]) << sep << fs.cfg << sep << id << sep << Path::simplifyPath(fs.filename()) << std::endl; } + + return ret.str(); } void AnalyzerInformation::close() @@ -96,25 +106,26 @@ static bool skipAnalysis(const std::string &analyzerInfoFile, std::size_t hash, return true; } -std::string AnalyzerInformation::getAnalyzerInfoFileFromFilesTxt(std::istream& filesTxt, const std::string &sourcefile, const std::string &cfg) +std::string AnalyzerInformation::getAnalyzerInfoFileFromFilesTxt(std::istream& filesTxt, const std::string &sourcefile, const std::string &cfg, int fileIndex) { + const std::string id = (fileIndex > 0) ? std::to_string(fileIndex) : ""; std::string line; - const std::string end(':' + cfg + ':' + Path::simplifyPath(sourcefile)); + const std::string end(sep + cfg + sep + id + sep + Path::simplifyPath(sourcefile)); while (std::getline(filesTxt,line)) { if (line.size() <= end.size() + 2U) continue; if (!endsWith(line, end.c_str(), end.size())) continue; - return line.substr(0,line.find(':')); + return line.substr(0,line.find(sep)); } return ""; } -std::string AnalyzerInformation::getAnalyzerInfoFile(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg) +std::string AnalyzerInformation::getAnalyzerInfoFile(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, int fileIndex) { std::ifstream fin(Path::join(buildDir, "files.txt")); if (fin.is_open()) { - const std::string& ret = getAnalyzerInfoFileFromFilesTxt(fin, sourcefile, cfg); + const std::string& ret = getAnalyzerInfoFileFromFilesTxt(fin, sourcefile, cfg, fileIndex); if (!ret.empty()) return Path::join(buildDir, ret); } @@ -128,13 +139,13 @@ std::string AnalyzerInformation::getAnalyzerInfoFile(const std::string &buildDir return Path::join(buildDir, filename) + ".analyzerinfo"; } -bool AnalyzerInformation::analyzeFile(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, std::size_t hash, std::list &errors) +bool AnalyzerInformation::analyzeFile(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, int fileIndex, std::size_t hash, std::list &errors) { if (buildDir.empty() || sourcefile.empty()) return true; close(); - mAnalyzerInfoFile = AnalyzerInformation::getAnalyzerInfoFile(buildDir,sourcefile,cfg); + mAnalyzerInfoFile = AnalyzerInformation::getAnalyzerInfoFile(buildDir,sourcefile,cfg,fileIndex); if (skipAnalysis(mAnalyzerInfoFile, hash, errors)) return false; @@ -161,3 +172,31 @@ void AnalyzerInformation::setFileInfo(const std::string &check, const std::strin if (mOutputStream.is_open() && !fileInfo.empty()) mOutputStream << " \n" << fileInfo << " \n"; } + +bool AnalyzerInformation::Info::parse(const std::string& filesTxtLine) { + const std::string::size_type sep1 = filesTxtLine.find(sep); + if (sep1 == std::string::npos) + return false; + const std::string::size_type sep2 = filesTxtLine.find(sep, sep1+1); + if (sep2 == std::string::npos) + return false; + const std::string::size_type sep3 = filesTxtLine.find(sep, sep2+1); + if (sep3 == std::string::npos) + return false; + + if (sep3 == sep2 + 1) + fileIndex = 0; + else { + try { + fileIndex = std::stoi(filesTxtLine.substr(sep2+1, sep3-sep2-1)); + } catch (const std::exception&) { + return false; + } + } + + afile = filesTxtLine.substr(0, sep1); + cfg = filesTxtLine.substr(sep1+1, sep2-sep1-1); + sourceFile = filesTxtLine.substr(sep3+1); + return true; +} + diff --git a/lib/analyzerinfo.h b/lib/analyzerinfo.h index 04f41ac4e99..e5466906707 100644 --- a/lib/analyzerinfo.h +++ b/lib/analyzerinfo.h @@ -51,16 +51,30 @@ class CPPCHECKLIB AnalyzerInformation { public: ~AnalyzerInformation(); + static std::string getFilesTxt(const std::list &sourcefiles, const std::string &userDefines, const std::list &fileSettings); + static void writeFilesTxt(const std::string &buildDir, const std::list &sourcefiles, const std::string &userDefines, const std::list &fileSettings); /** Close current TU.analyzerinfo file */ void close(); - bool analyzeFile(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, std::size_t hash, std::list &errors); + bool analyzeFile(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, int fileIndex, std::size_t hash, std::list &errors); void reportErr(const ErrorMessage &msg); void setFileInfo(const std::string &check, const std::string &fileInfo); - static std::string getAnalyzerInfoFile(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg); + static std::string getAnalyzerInfoFile(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, int fileIndex); + + static const char sep = ':'; + + class CPPCHECKLIB Info { + public: + bool parse(const std::string& filesTxtLine); + std::string afile; + std::string cfg; + int fileIndex = 0; + std::string sourceFile; + }; + protected: - static std::string getAnalyzerInfoFileFromFilesTxt(std::istream& filesTxt, const std::string &sourcefile, const std::string &cfg); + static std::string getAnalyzerInfoFileFromFilesTxt(std::istream& filesTxt, const std::string &sourcefile, const std::string &cfg, int fileIndex); private: std::ofstream mOutputStream; std::string mAnalyzerInfoFile; diff --git a/lib/cppcheck.cpp b/lib/cppcheck.cpp index e079cf0354d..68a3bd93213 100644 --- a/lib/cppcheck.cpp +++ b/lib/cppcheck.cpp @@ -331,16 +331,16 @@ static std::vector split(const std::string &str, const std::string return ret; } -static std::string getDumpFileName(const Settings& settings, const std::string& filename) +static std::string getDumpFileName(const Settings& settings, const std::string& filename, int fileIndex) { - std::string extension; - if (settings.dump || !settings.buildDir.empty()) - extension = ".dump"; - else - extension = "." + std::to_string(settings.pid) + ".dump"; + std::string extension = ".dump"; + if (fileIndex > 0) + extension = "." + std::to_string(fileIndex) + extension; + if (!settings.dump && settings.buildDir.empty()) + extension = "." + std::to_string(settings.pid) + extension; if (!settings.dump && !settings.buildDir.empty()) - return AnalyzerInformation::getAnalyzerInfoFile(settings.buildDir, filename, "") + extension; + return AnalyzerInformation::getAnalyzerInfoFile(settings.buildDir, filename, "", fileIndex) + extension; return filename + extension; } @@ -351,12 +351,13 @@ static std::string getCtuInfoFileName(const std::string &dumpFile) static void createDumpFile(const Settings& settings, const FileWithDetails& file, + int fileIndex, std::ofstream& fdump, std::string& dumpFile) { if (!settings.dump && settings.addons.empty()) return; - dumpFile = getDumpFileName(settings, file.spath()); + dumpFile = getDumpFileName(settings, file.spath(), fileIndex); fdump.open(dumpFile); if (!fdump.is_open()) @@ -649,7 +650,7 @@ static std::string getClangFlags(const Settings& setting, Standards::Language la } // TODO: clear error list before returning -unsigned int CppCheck::checkClang(const FileWithDetails &file) +unsigned int CppCheck::checkClang(const FileWithDetails &file, int fileIndex) { // TODO: clear exitcode @@ -662,7 +663,7 @@ unsigned int CppCheck::checkClang(const FileWithDetails &file) // TODO: get language from FileWithDetails object std::string clangStderr; if (!mSettings.buildDir.empty()) - clangStderr = AnalyzerInformation::getAnalyzerInfoFile(mSettings.buildDir, file.spath(), "") + ".clang-stderr"; + clangStderr = AnalyzerInformation::getAnalyzerInfoFile(mSettings.buildDir, file.spath(), "", fileIndex) + ".clang-stderr"; std::string exe = mSettings.clangExecutable; #ifdef _WIN32 @@ -732,7 +733,7 @@ unsigned int CppCheck::checkClang(const FileWithDetails &file) // create dumpfile std::ofstream fdump; std::string dumpFile; - createDumpFile(mSettings, file, fdump, dumpFile); + createDumpFile(mSettings, file, fileIndex, fdump, dumpFile); if (fdump.is_open()) { fdump << getLibraryDumpData(); // TODO: use tinyxml2 to create XML @@ -776,9 +777,9 @@ unsigned int CppCheck::check(const FileWithDetails &file) unsigned int returnValue; if (mSettings.clang) - returnValue = checkClang(file); + returnValue = checkClang(file, 0); else - returnValue = checkFile(file, ""); + returnValue = checkFile(file, "", 0); // TODO: call analyseClangTidy() @@ -788,7 +789,7 @@ unsigned int CppCheck::check(const FileWithDetails &file) unsigned int CppCheck::check(const FileWithDetails &file, const std::string &content) { std::istringstream iss(content); - return checkFile(file, "", &iss); + return checkFile(file, "", 0, &iss); } unsigned int CppCheck::check(const FileSettings &fs) @@ -824,7 +825,7 @@ unsigned int CppCheck::check(const FileSettings &fs) } // need to pass the externally provided ErrorLogger instead of our internal wrapper CppCheck temp(tempSettings, mSuppressions, mErrorLoggerDirect, mUseGlobalSuppressions, mExecuteCommand); - const unsigned int returnValue = temp.checkFile(fs.file, fs.cfg); + const unsigned int returnValue = temp.checkFile(fs.file, fs.cfg, fs.fileIndex); if (mUnusedFunctionsCheck) mUnusedFunctionsCheck->updateFunctionData(*temp.mUnusedFunctionsCheck); while (!temp.mFileInfo.empty()) { @@ -861,7 +862,7 @@ static std::size_t calculateHash(const Preprocessor& preprocessor, const simplec return preprocessor.calculateHash(tokens, toolinfo.str()); } -unsigned int CppCheck::checkFile(const FileWithDetails& file, const std::string &cfgname, std::istream* fileStream) +unsigned int CppCheck::checkFile(const FileWithDetails& file, const std::string &cfgname, int fileIndex, std::istream* fileStream) { // TODO: move to constructor when CppCheck no longer owns the settings if (mSettings.checks.isEnabled(Checks::unusedFunction) && !mUnusedFunctionsCheck) @@ -938,7 +939,7 @@ unsigned int CppCheck::checkFile(const FileWithDetails& file, const std::string mLogger->setAnalyzerInfo(nullptr); std::list errors; - analyzerInformation->analyzeFile(mSettings.buildDir, file.spath(), cfgname, hash, errors); + analyzerInformation->analyzeFile(mSettings.buildDir, file.spath(), cfgname, fileIndex, hash, errors); analyzerInformation->setFileInfo("CheckUnusedFunctions", mUnusedFunctionsCheck->analyzerInfo(tokenizer)); analyzerInformation->close(); } @@ -1012,7 +1013,7 @@ unsigned int CppCheck::checkFile(const FileWithDetails& file, const std::string // Calculate hash so it can be compared with old hash / future hashes const std::size_t hash = calculateHash(preprocessor, tokens1, mSettings, mSuppressions); std::list errors; - if (!analyzerInformation->analyzeFile(mSettings.buildDir, file.spath(), cfgname, hash, errors)) { + if (!analyzerInformation->analyzeFile(mSettings.buildDir, file.spath(), cfgname, fileIndex, hash, errors)) { while (!errors.empty()) { mErrorLogger.reportErr(errors.front()); errors.pop_front(); @@ -1075,7 +1076,7 @@ unsigned int CppCheck::checkFile(const FileWithDetails& file, const std::string // write dump file xml prolog std::ofstream fdump; std::string dumpFile; - createDumpFile(mSettings, file, fdump, dumpFile); + createDumpFile(mSettings, file, fileIndex, fdump, dumpFile); if (fdump.is_open()) { fdump << getLibraryDumpData(); fdump << dumpProlog; @@ -1170,7 +1171,7 @@ unsigned int CppCheck::checkFile(const FileWithDetails& file, const std::string #endif // Simplify tokens into normal form, skip rest of iteration if failed - if (!tokenizer.simplifyTokens1(currentConfig)) + if (!tokenizer.simplifyTokens1(currentConfig, fileIndex)) continue; // dump xml if --dump @@ -1812,12 +1813,12 @@ void CppCheck::executeAddonsWholeProgram(const std::list &files std::vector ctuInfoFiles; for (const auto &f: files) { - const std::string &dumpFileName = getDumpFileName(mSettings, f.path()); + const std::string &dumpFileName = getDumpFileName(mSettings, f.path(), 0); ctuInfoFiles.push_back(getCtuInfoFileName(dumpFileName)); } for (const auto &f: fileSettings) { - const std::string &dumpFileName = getDumpFileName(mSettings, f.filename()); + const std::string &dumpFileName = getDumpFileName(mSettings, f.filename(), f.fileIndex); ctuInfoFiles.push_back(getCtuInfoFileName(dumpFileName)); } @@ -1944,7 +1945,7 @@ void CppCheck::analyseClangTidy(const FileSettings &fileSettings) std::string line; if (!mSettings.buildDir.empty()) { - const std::string analyzerInfoFile = AnalyzerInformation::getAnalyzerInfoFile(mSettings.buildDir, fileSettings.filename(), ""); + const std::string analyzerInfoFile = AnalyzerInformation::getAnalyzerInfoFile(mSettings.buildDir, fileSettings.filename(), "", fileSettings.fileIndex); std::ofstream fcmd(analyzerInfoFile + ".clang-tidy-cmd"); fcmd << istr.str(); } @@ -2036,14 +2037,11 @@ unsigned int CppCheck::analyseWholeProgram(const std::string &buildDir, const st std::ifstream fin(filesTxt); std::string filesTxtLine; while (std::getline(fin, filesTxtLine)) { - const std::string::size_type firstColon = filesTxtLine.find(':'); - if (firstColon == std::string::npos) - continue; - const std::string::size_type lastColon = filesTxtLine.rfind(':'); - if (firstColon == lastColon) + AnalyzerInformation::Info filesTxtInfo; + if (!filesTxtInfo.parse(filesTxtLine)) continue; - const std::string xmlfile = buildDir + '/' + filesTxtLine.substr(0,firstColon); - //const std::string sourcefile = filesTxtLine.substr(lastColon+1); + + const std::string xmlfile = buildDir + '/' + filesTxtInfo.afile; tinyxml2::XMLDocument doc; const tinyxml2::XMLError error = doc.LoadFile(xmlfile.c_str()); @@ -2068,7 +2066,7 @@ unsigned int CppCheck::analyseWholeProgram(const std::string &buildDir, const st for (const Check *check : Check::instances()) { if (checkClassAttr == check->name()) { Check::FileInfo* fi = check->loadFileInfoFromXml(e); - fi->file0 = filesTxtLine.substr(firstColon + 2); + fi->file0 = filesTxtInfo.sourceFile; fileInfoList.push_back(fi); } } diff --git a/lib/cppcheck.h b/lib/cppcheck.h index 67acbf626b6..d7c326704a1 100644 --- a/lib/cppcheck.h +++ b/lib/cppcheck.h @@ -169,7 +169,7 @@ class CPPCHECKLIB CppCheck { * @param fileStream stream the file content can be read from * @return number of errors found */ - unsigned int checkFile(const FileWithDetails& file, const std::string &cfgname, std::istream* fileStream = nullptr); + unsigned int checkFile(const FileWithDetails& file, const std::string &cfgname, int fileIndex, std::istream* fileStream = nullptr); /** * @brief Check normal tokens @@ -198,7 +198,7 @@ class CPPCHECKLIB CppCheck { void executeRules(const std::string &tokenlist, const TokenList &list); #endif - unsigned int checkClang(const FileWithDetails &file); + unsigned int checkClang(const FileWithDetails &file, int fileIndex); const Settings& mSettings; Suppressions& mSuppressions; diff --git a/lib/filesettings.h b/lib/filesettings.h index 601aceea4f1..2c125264bbc 100644 --- a/lib/filesettings.h +++ b/lib/filesettings.h @@ -80,6 +80,7 @@ struct CPPCHECKLIB FileSettings { : file(std::move(path), lang, size) {} + int fileIndex = 0; std::string cfg; FileWithDetails file; const std::string& filename() const diff --git a/lib/importproject.cpp b/lib/importproject.cpp index fdcf5441db9..a4b183aca35 100644 --- a/lib/importproject.cpp +++ b/lib/importproject.cpp @@ -355,6 +355,8 @@ bool ImportProject::importCompileCommands(std::istream &istr) return false; } + std::map fileIndex; + for (const picojson::value &fileInfo : compileCommands.get()) { picojson::object obj = fileInfo.get(); std::string dirpath = Path::fromNativeSeparators(obj["directory"].get()); @@ -420,10 +422,13 @@ bool ImportProject::importCompileCommands(std::istream &istr) printError("'" + path + "' from compilation database does not exist"); return false; } - FileSettings fs{std::move(path), Standards::Language::None, 0}; // file will be identified later on + FileSettings fs{path, Standards::Language::None, 0}; // file will be identified later on fsParseCommand(fs, command); // read settings; -D, -I, -U, -std, -m*, -f* std::map variables; fsSetIncludePaths(fs, directory, fs.includePaths, variables); + // Assign a unique index to each file path. If the file path already exists in the map, + // increment the index to handle duplicate file entries. + fs.fileIndex = fileIndex[path]++; fileSettings.push_back(std::move(fs)); } diff --git a/lib/summaries.cpp b/lib/summaries.cpp index bcedbb9fc2b..9226094baec 100644 --- a/lib/summaries.cpp +++ b/lib/summaries.cpp @@ -34,7 +34,7 @@ -std::string Summaries::create(const Tokenizer &tokenizer, const std::string &cfg) +std::string Summaries::create(const Tokenizer &tokenizer, const std::string &cfg, int fileIndex) { const SymbolDatabase *symbolDatabase = tokenizer.getSymbolDatabase(); const Settings &settings = tokenizer.getSettings(); @@ -82,7 +82,7 @@ std::string Summaries::create(const Tokenizer &tokenizer, const std::string &cfg } if (!settings.buildDir.empty()) { - std::string filename = AnalyzerInformation::getAnalyzerInfoFile(settings.buildDir, tokenizer.list.getSourceFilePath(), cfg); + std::string filename = AnalyzerInformation::getAnalyzerInfoFile(settings.buildDir, tokenizer.list.getSourceFilePath(), cfg, fileIndex); const std::string::size_type pos = filename.rfind(".a"); if (pos != std::string::npos) { filename[pos+1] = 's'; diff --git a/lib/summaries.h b/lib/summaries.h index 87d3c5b5d63..238ea4107d3 100644 --- a/lib/summaries.h +++ b/lib/summaries.h @@ -29,7 +29,7 @@ class Tokenizer; namespace Summaries { - CPPCHECKLIB std::string create(const Tokenizer &tokenizer, const std::string &cfg); + CPPCHECKLIB std::string create(const Tokenizer &tokenizer, const std::string &cfg, int fileIndex); CPPCHECKLIB void loadReturn(const std::string &buildDir, std::set &summaryReturn); } diff --git a/lib/tokenize.cpp b/lib/tokenize.cpp index fa078eaf445..dc55ebe1409 100644 --- a/lib/tokenize.cpp +++ b/lib/tokenize.cpp @@ -3375,7 +3375,7 @@ void Tokenizer::simplifyUsingError(const Token* usingStart, const Token* usingEn } } -bool Tokenizer::simplifyTokens1(const std::string &configuration) +bool Tokenizer::simplifyTokens1(const std::string &configuration, int fileIndex) { // Fill the map mTypeSize.. fillTypeSizes(); @@ -3408,7 +3408,7 @@ bool Tokenizer::simplifyTokens1(const std::string &configuration) }); if (!mSettings.buildDir.empty()) - Summaries::create(*this, configuration); + Summaries::create(*this, configuration, fileIndex); // TODO: apply this through Settings::ValueFlowOptions // TODO: do not run valueflow if no checks are being performed at all - e.g. unusedFunctions only diff --git a/lib/tokenize.h b/lib/tokenize.h index 0f0363354e9..7b927124deb 100644 --- a/lib/tokenize.h +++ b/lib/tokenize.h @@ -79,7 +79,7 @@ class CPPCHECKLIB Tokenizer { */ bool isScopeNoReturn(const Token *endScopeToken, bool *unknown = nullptr) const; - bool simplifyTokens1(const std::string &configuration); + bool simplifyTokens1(const std::string &configuration, int fileIndex=0); private: /** Set variable id */ diff --git a/releasenotes.txt b/releasenotes.txt index 78f2dfba2a2..c84fe6a0479 100644 --- a/releasenotes.txt +++ b/releasenotes.txt @@ -23,4 +23,5 @@ Other: - Updated TinyXML-2 to v11.0.0 - The minimum supported Python version has been bumped to 3.7. - CMake will now unconditionally use Boost.Containers if available. If CMake option `USE_BOOST` is specified it will now bail out when it is not found. +- Fix checking a project that contains several project file entries for the same file. - diff --git a/test/cli/dumpfile_test.py b/test/cli/dumpfile_test.py index 9593e2a1660..6e52b3cebcc 100644 --- a/test/cli/dumpfile_test.py +++ b/test/cli/dumpfile_test.py @@ -1,5 +1,6 @@ # python -m pytest dumpfile_test.py +import json import os import pathlib @@ -92,4 +93,29 @@ def test_language_unk_force_c(tmp_path): def test_language_unk_force_cpp(tmp_path): - __test_language(tmp_path, 'src', force_lang='c++', exp_lang='cpp') \ No newline at end of file + __test_language(tmp_path, 'src', force_lang='c++', exp_lang='cpp') + + +def test_duplicate_file_entries(tmpdir): #13333 + test_file = str(tmpdir / 'test.c') + with open(test_file, 'wt') as f: + f.write('x=1;\n') + + project_file = str(tmpdir / 'compile_commands.json') + with open(project_file, 'wt') as f: + f.write(json.dumps([{ + "file": test_file, + "directory": str(tmpdir), + "command": "cc -c test.c" + },{ + "file": test_file, + "directory": str(tmpdir), + "command": "cc -c test.c" + }])) + + args = ['--project=compile_commands.json', '--dump'] + _, _, _ = cppcheck(args, cwd=str(tmpdir)) + + assert os.path.isfile(test_file + '.dump') + assert os.path.isfile(test_file + '.1.dump') + diff --git a/test/testanalyzerinformation.cpp b/test/testanalyzerinformation.cpp index 6727efb1fcd..f1d94d4af3c 100644 --- a/test/testanalyzerinformation.cpp +++ b/test/testanalyzerinformation.cpp @@ -18,6 +18,7 @@ #include "analyzerinfo.h" +#include "filesettings.h" #include "fixture.h" #include @@ -30,16 +31,69 @@ class TestAnalyzerInformation : public TestFixture, private AnalyzerInformation void run() override { TEST_CASE(getAnalyzerInfoFile); + TEST_CASE(duplicateFile); + TEST_CASE(filesTextDuplicateFile); + TEST_CASE(parse); } void getAnalyzerInfoFile() const { - constexpr char filesTxt[] = "file1.a4::file1.c\n"; + constexpr char filesTxt[] = "file1.a4:::file1.c\n"; std::istringstream f1(filesTxt); - ASSERT_EQUALS("file1.a4", getAnalyzerInfoFileFromFilesTxt(f1, "file1.c", "")); + ASSERT_EQUALS("file1.a4", getAnalyzerInfoFileFromFilesTxt(f1, "file1.c", "", 0)); std::istringstream f2(filesTxt); - ASSERT_EQUALS("file1.a4", getAnalyzerInfoFileFromFilesTxt(f2, "./file1.c", "")); - ASSERT_EQUALS("builddir/file1.c.analyzerinfo", AnalyzerInformation::getAnalyzerInfoFile("builddir", "file1.c", "")); - ASSERT_EQUALS("builddir/file1.c.analyzerinfo", AnalyzerInformation::getAnalyzerInfoFile("builddir", "some/path/file1.c", "")); + ASSERT_EQUALS("file1.a4", getAnalyzerInfoFileFromFilesTxt(f2, "./file1.c", "", 0)); + ASSERT_EQUALS("builddir/file1.c.analyzerinfo", AnalyzerInformation::getAnalyzerInfoFile("builddir", "file1.c", "", 0)); + ASSERT_EQUALS("builddir/file1.c.analyzerinfo", AnalyzerInformation::getAnalyzerInfoFile("builddir", "some/path/file1.c", "", 0)); + } + + void filesTextDuplicateFile() const { + std::list fileSettings; + fileSettings.emplace_back("a.c", Standards::Language::C, 10); + fileSettings.back().fileIndex = 0; + fileSettings.emplace_back("a.c", Standards::Language::C, 10); + fileSettings.back().fileIndex = 1; + + const char expected[] = "a.a1:::a.c\n" + "a.a2::1:a.c\n"; + + ASSERT_EQUALS(expected, getFilesTxt({}, "", fileSettings)); + } + + void duplicateFile() const { + // same file duplicated + constexpr char filesTxt[] = "file1.a1::1:file1.c\n" + "file1.a2::2:file1.c\n"; + std::istringstream f1(filesTxt); + ASSERT_EQUALS("file1.a1", getAnalyzerInfoFileFromFilesTxt(f1, "file1.c", "", 1)); + std::istringstream f2(filesTxt); + ASSERT_EQUALS("file1.a2", getAnalyzerInfoFileFromFilesTxt(f2, "file1.c", "", 2)); + } + + void parse() const { + AnalyzerInformation::Info info; + + ASSERT_EQUALS(false, info.parse("a")); + ASSERT_EQUALS(false, info.parse("a:b")); + ASSERT_EQUALS(false, info.parse("a:b:c")); + ASSERT_EQUALS(false, info.parse("a:b:c:d")); + + ASSERT_EQUALS(true, info.parse("a:b::d")); + ASSERT_EQUALS("a", info.afile); + ASSERT_EQUALS("b", info.cfg); + ASSERT_EQUALS(0, info.fileIndex); + ASSERT_EQUALS("d", info.sourceFile); + + ASSERT_EQUALS(true, info.parse("e:f:12:g")); + ASSERT_EQUALS("e", info.afile); + ASSERT_EQUALS("f", info.cfg); + ASSERT_EQUALS(12, info.fileIndex); + ASSERT_EQUALS("g", info.sourceFile); + + ASSERT_EQUALS(true, info.parse("odr1.a1:::C:/dm/cppcheck-fix-13333/test/cli/whole-program/odr1.cpp")); + ASSERT_EQUALS("odr1.a1", info.afile); + ASSERT_EQUALS("", info.cfg); + ASSERT_EQUALS(0, info.fileIndex); + ASSERT_EQUALS("C:/dm/cppcheck-fix-13333/test/cli/whole-program/odr1.cpp", info.sourceFile); } }; diff --git a/test/testimportproject.cpp b/test/testimportproject.cpp index ca84d13cf44..42888a8e401 100644 --- a/test/testimportproject.cpp +++ b/test/testimportproject.cpp @@ -64,6 +64,7 @@ class TestImportProject : public TestFixture { TEST_CASE(importCompileCommands10); // #10887: include path with space TEST_CASE(importCompileCommands11); // include path order TEST_CASE(importCompileCommands12); // #13040: "directory" is parent directory, relative include paths + TEST_CASE(importCompileCommands13); // #13333: duplicate file entries TEST_CASE(importCompileCommandsArgumentsSection); // Handle arguments section TEST_CASE(importCompileCommandsNoCommandSection); // gracefully handles malformed json TEST_CASE(importCppcheckGuiProject); @@ -338,6 +339,28 @@ class TestImportProject : public TestFixture { ASSERT_EQUALS("/x/", fs.includePaths.front()); } + void importCompileCommands13() const { // #13333 + REDIRECT; + constexpr char json[] = + R"([{ + "file": "/x/src/1.c" , + "directory": "/x", + "command": "cc -c -I. src/1.c" + },{ + "file": "/x/src/1.c" , + "directory": "/x", + "command": "cc -c -I. src/1.c" + }])"; + std::istringstream istr(json); + TestImporter importer; + ASSERT_EQUALS(true, importer.importCompileCommands(istr)); + ASSERT_EQUALS(2, importer.fileSettings.size()); + const FileSettings &fs1 = importer.fileSettings.front(); + const FileSettings &fs2 = importer.fileSettings.back(); + ASSERT_EQUALS(0, fs1.fileIndex); + ASSERT_EQUALS(1, fs2.fileIndex); + } + void importCompileCommandsArgumentsSection() const { REDIRECT; constexpr char json[] = "[ { \"directory\": \"/tmp/\"," diff --git a/test/testsummaries.cpp b/test/testsummaries.cpp index 83674a4da8e..7896ffeb750 100644 --- a/test/testsummaries.cpp +++ b/test/testsummaries.cpp @@ -41,7 +41,7 @@ class TestSummaries : public TestFixture { // tokenize.. SimpleTokenizer tokenizer(settingsDefault, *this); ASSERT_LOC(tokenizer.tokenize(code), file, line); - return Summaries::create(tokenizer, ""); + return Summaries::create(tokenizer, "", 0); } void createSummaries1() {