diff --git a/cli/cmdlineparser.cpp b/cli/cmdlineparser.cpp index 92f4c6cef3b..f183b5c646e 100644 --- a/cli/cmdlineparser.cpp +++ b/cli/cmdlineparser.cpp @@ -621,6 +621,9 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a mSettings.cppHeaderProbe = true; } + else if (std::strcmp(argv[i], "--debug-analyzerinfo") == 0) + mSettings.debugainfo = true; + else if (std::strcmp(argv[i], "--debug-ast") == 0) mSettings.debugast = true; diff --git a/cli/cppcheckexecutor.cpp b/cli/cppcheckexecutor.cpp index a16f76183c0..ef47920a5d9 100644 --- a/cli/cppcheckexecutor.cpp +++ b/cli/cppcheckexecutor.cpp @@ -299,9 +299,10 @@ int CppCheckExecutor::check_wrapper(const Settings& settings, Suppressions& supp * @param unmatched list of unmatched suppressions (from Settings::Suppressions::getUnmatched(Local|Global)Suppressions) * @return true is returned if errors are reported */ -static bool reportUnmatchedSuppressions(const std::list &unmatched, ErrorLogger &errorLogger, const std::vector& filters) +static std::vector<::ErrorMessage> reportUnmatchedSuppressions(const std::list &unmatched, const std::vector& filters) { - bool err = false; + std::vector<::ErrorMessage> errors; + // Report unmatched suppressions for (const SuppressionList::Suppression &s : unmatched) { // check if this unmatched suppression is suppressed @@ -329,10 +330,11 @@ static bool reportUnmatchedSuppressions(const std::list &files, const std::list& fileSettings, ErrorLogger& errorLogger) { @@ -358,21 +360,55 @@ bool CppCheckExecutor::reportUnmatchedSuppressions(const Settings &settings, con supprlist.addSuppression(std::move(s)); } + const auto reportErrorsFn = [&](const std::string& sourcefile, std::size_t fsFileId, const std::vector<::ErrorMessage>& errors) -> bool { + if (errors.empty()) + return false; + + // TODO: what if sourcefile is empty? + + AnalyzerInformation analyzerInfo; + // FIXME: this is a horrible hack + // we need to "re-open" the file so we can add the unmatchedSuppression findings. + // we cannot keep it open conditionally because the whole program analysis read the XML. + // re-ordering is also not an option because the unmatched suppression reporting needs to be run after all other checks. + analyzerInfo.reopen(settings.buildDir, sourcefile, /*cfgname*/"", fsFileId); + + for (const auto& errmsg : errors) { + analyzerInfo.reportErr(errmsg); + errorLogger.reportErr(errmsg); + } + return true; + }; + bool err = false; for (auto i = files.cbegin(); i != files.cend(); ++i) { - err |= ::reportUnmatchedSuppressions(supprlist.getUnmatchedLocalSuppressions(*i), errorLogger, settings.unmatchedSuppressionFilters); + const std::vector errors = ::reportUnmatchedSuppressions(supprlist.getUnmatchedLocalSuppressions(*i), settings.unmatchedSuppressionFilters); + err |= reportErrorsFn(i->spath(), 0, errors); } for (auto i = fileSettings.cbegin(); i != fileSettings.cend(); ++i) { - err |= ::reportUnmatchedSuppressions(supprlist.getUnmatchedLocalSuppressions(i->file), errorLogger, settings.unmatchedSuppressionFilters); + const std::vector errors = ::reportUnmatchedSuppressions(supprlist.getUnmatchedLocalSuppressions(i->file), settings.unmatchedSuppressionFilters); + err |= reportErrorsFn(i->file.spath(), i->fileIndex, errors); } if (settings.inlineSuppressions) { - err |= ::reportUnmatchedSuppressions(supprlist.getUnmatchedInlineSuppressions(), errorLogger, settings.unmatchedSuppressionFilters); + const std::vector errors = ::reportUnmatchedSuppressions(supprlist.getUnmatchedInlineSuppressions(), settings.unmatchedSuppressionFilters); + for (const auto& errmsg : errors) { + std::string sourcefile; + if (!errmsg.callStack.empty()) + sourcefile = errmsg.callStack.cbegin()->getfile(); + err |= reportErrorsFn(sourcefile, 0, {errmsg}); + } } - err |= ::reportUnmatchedSuppressions(supprlist.getUnmatchedGlobalSuppressions(), errorLogger, settings.unmatchedSuppressionFilters); + const std::vector errors = ::reportUnmatchedSuppressions(supprlist.getUnmatchedGlobalSuppressions(), settings.unmatchedSuppressionFilters); + for (const auto& errmsg : errors) { + std::string sourcefile; + if (!errmsg.callStack.empty()) + sourcefile = errmsg.callStack.cbegin()->getfile(); + err |= reportErrorsFn(sourcefile, 0, {errmsg}); + } return err; } @@ -425,9 +461,10 @@ int CppCheckExecutor::check_internal(const Settings& settings, Suppressions& sup #endif } + // TODO: is this run again instead of using previously cached results? returnValue |= cppcheck.analyseWholeProgram(settings.buildDir, mFiles, mFileSettings, stdLogger.getCtuInfo()); - if (settings.severity.isEnabled(Severity::information) || settings.checkConfiguration) { + if ((settings.severity.isEnabled(Severity::information) || settings.checkConfiguration) && !supprs.nomsg.getSuppressions().empty()) { const bool err = reportUnmatchedSuppressions(settings, supprs.nomsg, mFiles, mFileSettings, stdLogger); if (err && returnValue == 0) returnValue = settings.exitCode; diff --git a/lib/analyzerinfo.cpp b/lib/analyzerinfo.cpp index c7045252f3d..9a7c770c7f5 100644 --- a/lib/analyzerinfo.cpp +++ b/lib/analyzerinfo.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -84,29 +85,55 @@ void AnalyzerInformation::close() } } -bool AnalyzerInformation::skipAnalysis(const tinyxml2::XMLDocument &analyzerInfoDoc, std::size_t hash, std::list &errors) +bool AnalyzerInformation::skipAnalysis(const tinyxml2::XMLDocument &analyzerInfoDoc, std::size_t hash, std::list &errors, bool debug) { const tinyxml2::XMLElement * const rootNode = analyzerInfoDoc.FirstChildElement(); - if (rootNode == nullptr) + if (rootNode == nullptr) { + if (debug) + std::cout << "discarding cached result - no root node found" << std::endl; return false; + } - const char *attr = rootNode->Attribute("hash"); - if (!attr || attr != std::to_string(hash)) + if (strcmp(rootNode->Name(), "analyzerinfo") != 0) { + if (debug) + std::cout << "discarding cached result - unexpected root node" << std::endl; return false; + } - // Check for invalid license error or internal error, in which case we should retry analysis - for (const tinyxml2::XMLElement *e = rootNode->FirstChildElement(); e; e = e->NextSiblingElement()) { - if (std::strcmp(e->Name(), "error") == 0 && - (e->Attribute("id", "premium-invalidLicense") || - e->Attribute("id", "premium-internalError") || - e->Attribute("id", "internalError") - )) - return false; + const char * const attr = rootNode->Attribute("hash"); + if (!attr) { + if (debug) + std::cout << "discarding cached result - no 'hash' attribute found" << std::endl; + return false; + } + if (attr != std::to_string(hash)) { + if (debug) + std::cout << "discarding cached result - hash mismatch" << std::endl; + return false; } for (const tinyxml2::XMLElement *e = rootNode->FirstChildElement(); e; e = e->NextSiblingElement()) { - if (std::strcmp(e->Name(), "error") == 0) - errors.emplace_back(e); + if (std::strcmp(e->Name(), "error") != 0) + continue; + + // TODO: discarding results on internalError doesn't make sense since that won't fix itself + // Check for invalid license error or internal error, in which case we should retry analysis + static std::array s_ids{ + "premium-invalidLicense", + "premium-internalError", + "internalError" + }; + for (const auto* id : s_ids) + { + if (e->Attribute("id", id)) { + if (debug) + std::cout << "discarding cached result - '" << id << "' encountered" << std::endl; + errors.clear(); + return false; + } + } + + errors.emplace_back(e); } return true; @@ -114,15 +141,13 @@ bool AnalyzerInformation::skipAnalysis(const tinyxml2::XMLDocument &analyzerInfo 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(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(sep)); + AnalyzerInformation::Info filesTxtInfo; + if (!filesTxtInfo.parse(line)) + continue; // TODO: report error? + if (filesTxtInfo.sourceFile == sourcefile && filesTxtInfo.cfg == cfg && filesTxtInfo.fileIndex == fileIndex) + return filesTxtInfo.afile; } return ""; } @@ -145,24 +170,39 @@ std::string AnalyzerInformation::getAnalyzerInfoFile(const std::string &buildDir return Path::join(buildDir, std::move(filename)) + ".analyzerinfo"; } -bool AnalyzerInformation::analyzeFile(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, int fileIndex, 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, bool debug) { + if (mOutputStream.is_open()) + throw std::runtime_error("analyzer information file is already open"); + if (buildDir.empty() || sourcefile.empty()) return true; - close(); const std::string analyzerInfoFile = AnalyzerInformation::getAnalyzerInfoFile(buildDir,sourcefile,cfg,fileIndex); - tinyxml2::XMLDocument analyzerInfoDoc; - const tinyxml2::XMLError xmlError = analyzerInfoDoc.LoadFile(analyzerInfoFile.c_str()); - if (xmlError == tinyxml2::XML_SUCCESS && skipAnalysis(analyzerInfoDoc, hash, errors)) - return false; + { + tinyxml2::XMLDocument analyzerInfoDoc; + const tinyxml2::XMLError xmlError = analyzerInfoDoc.LoadFile(analyzerInfoFile.c_str()); + if (xmlError == tinyxml2::XML_SUCCESS) { + if (skipAnalysis(analyzerInfoDoc, hash, errors, debug)) { + if (debug) + std::cout << "skipping analysis - loaded " << errors.size() << " cached finding(s) from '" << analyzerInfoFile << "'" << std::endl; + return false; + } + } + else if (xmlError != tinyxml2::XML_ERROR_FILE_NOT_FOUND) { + if (debug) + std::cout << "discarding cached result - failed to load '" << analyzerInfoFile << "' (" << tinyxml2::XMLDocument::ErrorIDToName(xmlError) << ")" << std::endl; + } + else if (debug) + std::cout << "no cached result '" << analyzerInfoFile << "' found" << std::endl; + } mOutputStream.open(analyzerInfoFile); - if (mOutputStream.is_open()) { - mOutputStream << "\n"; - mOutputStream << "\n"; - } + if (!mOutputStream.is_open()) + throw std::runtime_error("failed to open '" + analyzerInfoFile + "'"); + mOutputStream << "\n"; + mOutputStream << "\n"; return true; } @@ -179,6 +219,7 @@ void AnalyzerInformation::setFileInfo(const std::string &check, const std::strin mOutputStream << " \n" << fileInfo << " \n"; } +// TODO: report detailed errors? bool AnalyzerInformation::Info::parse(const std::string& filesTxtLine) { const std::string::size_type sep1 = filesTxtLine.find(sep); if (sep1 == std::string::npos) @@ -206,37 +247,70 @@ bool AnalyzerInformation::Info::parse(const std::string& filesTxtLine) { return true; } -// TODO: bail out on unexpected data -void AnalyzerInformation::processFilesTxt(const std::string& buildDir, const std::function& handler) +std::string AnalyzerInformation::processFilesTxt(const std::string& buildDir, const std::function& handler, bool debug) { const std::string filesTxt(buildDir + "/files.txt"); std::ifstream fin(filesTxt.c_str()); std::string filesTxtLine; while (std::getline(fin, filesTxtLine)) { AnalyzerInformation::Info filesTxtInfo; - if (!filesTxtInfo.parse(filesTxtLine)) { - return; - } + if (!filesTxtInfo.parse(filesTxtLine)) + return "failed to parse '" + filesTxtLine + "' from '" + filesTxt + "'"; + + if (filesTxtInfo.afile.empty()) + return "empty afile from '" + filesTxt + "'"; const std::string xmlfile = buildDir + '/' + filesTxtInfo.afile; tinyxml2::XMLDocument doc; const tinyxml2::XMLError error = doc.LoadFile(xmlfile.c_str()); + if (error == tinyxml2::XML_ERROR_FILE_NOT_FOUND) + return "'" + xmlfile + "' from '" + filesTxt + "' not found"; + if (error != tinyxml2::XML_SUCCESS) - return; + return "failed to load '" + xmlfile + "' from '" + filesTxt + "'"; const tinyxml2::XMLElement * const rootNode = doc.FirstChildElement(); if (rootNode == nullptr) - return; + return "no root node found in '" + xmlfile + "' from '" + filesTxt + "'"; + + if (strcmp(rootNode->Name(), "analyzerinfo") != 0) + return "unexpected root node in '" + xmlfile + "' from '" + filesTxt + "'"; for (const tinyxml2::XMLElement *e = rootNode->FirstChildElement(); e; e = e->NextSiblingElement()) { if (std::strcmp(e->Name(), "FileInfo") != 0) continue; const char *checkattr = e->Attribute("check"); - if (checkattr == nullptr) + if (checkattr == nullptr) { + if (debug) + std::cout << "'check' attribute missing in 'FileInfo' in '" << xmlfile << "' from '" << filesTxt + "'"; continue; + } handler(checkattr, e, filesTxtInfo); } } + + // TODO: error on empty file? + return ""; } +void AnalyzerInformation::reopen(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, int fileIndex) +{ + if (buildDir.empty() || sourcefile.empty()) + return; + + const std::string analyzerInfoFile = AnalyzerInformation::getAnalyzerInfoFile(buildDir,sourcefile,cfg,fileIndex); + std::ifstream ifs(analyzerInfoFile); + if (!ifs.is_open()) + return; + + std::ostringstream iss; + iss << ifs.rdbuf(); + ifs.close(); + + std::string content = iss.str(); + content = content.substr(0, content.find("")); + + mOutputStream.open(analyzerInfoFile, std::ios::trunc); + mOutputStream << content; +} diff --git a/lib/analyzerinfo.h b/lib/analyzerinfo.h index 881076c09f4..c6db1997148 100644 --- a/lib/analyzerinfo.h +++ b/lib/analyzerinfo.h @@ -61,11 +61,16 @@ class CPPCHECKLIB AnalyzerInformation { /** Close current TU.analyzerinfo file */ void close(); - bool analyzeFile(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, int fileIndex, std::size_t hash, std::list &errors); + /** + * @throws std::runtime_error thrown if the output file is already open or the output file cannot be opened + */ + bool analyzeFile(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, int fileIndex, std::size_t hash, std::list &errors, bool debug = false); 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, int fileIndex); + void reopen(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, int fileIndex); + static const char sep = ':'; class CPPCHECKLIB Info { @@ -77,14 +82,14 @@ class CPPCHECKLIB AnalyzerInformation { std::string sourceFile; }; - static void processFilesTxt(const std::string& buildDir, const std::function& handler); + static std::string processFilesTxt(const std::string& buildDir, const std::function& handler, bool debug = false); protected: static std::string getFilesTxt(const std::list &sourcefiles, const std::string &userDefines, const std::list &fileSettings); static std::string getAnalyzerInfoFileFromFilesTxt(std::istream& filesTxt, const std::string &sourcefile, const std::string &cfg, int fileIndex); - static bool skipAnalysis(const tinyxml2::XMLDocument &analyzerInfoDoc, std::size_t hash, std::list &errors); + static bool skipAnalysis(const tinyxml2::XMLDocument &analyzerInfoDoc, std::size_t hash, std::list &errors, bool debug = false); private: std::ofstream mOutputStream; diff --git a/lib/checkunusedfunctions.cpp b/lib/checkunusedfunctions.cpp index 7edaff6c241..eb06a4691f1 100644 --- a/lib/checkunusedfunctions.cpp +++ b/lib/checkunusedfunctions.cpp @@ -482,7 +482,12 @@ void CheckUnusedFunctions::analyseWholeProgram(const Settings &settings, ErrorLo } }; - AnalyzerInformation::processFilesTxt(buildDir, handler); + const std::string err = AnalyzerInformation::processFilesTxt(buildDir, handler, settings.debugainfo); + if (!err.empty()) { + const ErrorMessage errmsg({}, "", Severity::error, err, "internalError", Certainty::normal); + errorLogger.reportErr(errmsg); + return; + } for (auto decl = decls.cbegin(); decl != decls.cend(); ++decl) { const std::string &functionName = stripTemplateParameters(decl->first); diff --git a/lib/cppcheck.cpp b/lib/cppcheck.cpp index 399eb6d3d6e..407b956bb88 100644 --- a/lib/cppcheck.cpp +++ b/lib/cppcheck.cpp @@ -974,7 +974,7 @@ unsigned int CppCheck::checkInternal(const FileWithDetails& file, const std::str mLogger->setAnalyzerInfo(nullptr); std::list errors; - analyzerInformation->analyzeFile(mSettings.buildDir, file.spath(), cfgname, fileIndex, hash, errors); + analyzerInformation->analyzeFile(mSettings.buildDir, file.spath(), cfgname, fileIndex, hash, errors, mSettings.debugainfo); analyzerInformation->setFileInfo("CheckUnusedFunctions", mUnusedFunctionsCheck->analyzerInfo(tokenizer)); analyzerInformation->close(); } @@ -1020,7 +1020,7 @@ unsigned int CppCheck::checkInternal(const FileWithDetails& file, const std::str // Calculate hash so it can be compared with old hash / future hashes const std::size_t hash = calculateHash(preprocessor, file.spath()); std::list errors; - if (!analyzerInformation->analyzeFile(mSettings.buildDir, file.spath(), cfgname, fileIndex, hash, errors)) { + if (!analyzerInformation->analyzeFile(mSettings.buildDir, file.spath(), cfgname, fileIndex, hash, errors, mSettings.debugainfo)) { while (!errors.empty()) { mErrorLogger.reportErr(errors.front()); errors.pop_front(); @@ -1861,12 +1861,17 @@ unsigned int CppCheck::analyseWholeProgram(const std::string &buildDir, const st } }; - AnalyzerInformation::processFilesTxt(buildDir, handler); - - // Analyse the tokens - // cppcheck-suppress shadowFunction - TODO: fix this - for (Check *check : Check::instances()) - check->analyseWholeProgram(ctuFileInfo, fileInfoList, mSettings, mErrorLogger); + const std::string err = AnalyzerInformation::processFilesTxt(buildDir, handler, mSettings.debugainfo); + if (!err.empty()) { + const ErrorMessage errmsg({}, "", Severity::error, err, "internalError", Certainty::normal); + mErrorLogger.reportErr(errmsg); + } + else { + // Analyse the tokens + // cppcheck-suppress shadowFunction - TODO: fix this + for (Check *check : Check::instances()) + check->analyseWholeProgram(ctuFileInfo, fileInfoList, mSettings, mErrorLogger); + } for (Check::FileInfo *fi : fileInfoList) delete fi; diff --git a/lib/errorlogger.cpp b/lib/errorlogger.cpp index e62ed6b806d..13f66c13d1a 100644 --- a/lib/errorlogger.cpp +++ b/lib/errorlogger.cpp @@ -162,6 +162,7 @@ ErrorMessage::ErrorMessage(ErrorPath errorPath, const TokenList *tokenList, Seve // hash = calculateWarningHash(tokenList, hashWarning.str()); } +// TODO: improve errorhandling? ErrorMessage::ErrorMessage(const tinyxml2::XMLElement * const errmsg) : severity(Severity::none), cwe(0U), diff --git a/lib/settings.h b/lib/settings.h index 04541cda08c..6521ba4581d 100644 --- a/lib/settings.h +++ b/lib/settings.h @@ -189,6 +189,9 @@ class CPPCHECKLIB WARN_UNUSED Settings { /** @brief Are we running from DACA script? */ bool daca{}; + /** @brief Is --debug-analyzerinfo given? */ + bool debugainfo{}; + /** @brief Is --debug-ast given? */ bool debugast{}; diff --git a/test/cli/other_test.py b/test/cli/other_test.py index c4ae2fab5cf..20485a08359 100644 --- a/test/cli/other_test.py +++ b/test/cli/other_test.py @@ -2353,12 +2353,12 @@ def test_inline_suppr_builddir(tmp_path): __test_inline_suppr(tmp_path, ['--cppcheck-build-dir={}'.format(build_dir), '-j1']) -# TODO: the suppressions are generated outside of the scope which captures the analysis information -@pytest.mark.xfail(strict=True) def test_inline_suppr_builddir_cached(tmp_path): build_dir = tmp_path / 'b1' os.mkdir(build_dir) __test_inline_suppr(tmp_path, ['--cppcheck-build-dir={}'.format(build_dir), '-j1']) + with open(build_dir / 'test.a1' , 'r') as f: + print(f.readlines()) __test_inline_suppr(tmp_path, ['--cppcheck-build-dir={}'.format(build_dir), '-j1']) @@ -2368,8 +2368,6 @@ def test_inline_suppr_builddir_j(tmp_path): __test_inline_suppr(tmp_path, ['--cppcheck-build-dir={}'.format(build_dir), '-j2']) -# TODO: the suppressions are generated outside of the scope which captures the analysis information -@pytest.mark.xfail(strict=True) def test_inline_suppr_builddir_j_cached(tmp_path): build_dir = tmp_path / 'b1' os.mkdir(build_dir) @@ -4119,3 +4117,101 @@ def test_active_unusedfunction_only_misra_builddir(tmp_path): 'CheckUnusedFunctions::check' ] __test_active_checkers(tmp_path, 1, 1175, use_unusedfunction_only=True, use_misra=True, checkers_exp=checkers_exp) + + +def test_analyzerinfo(tmp_path): + test_file = tmp_path / 'test.c' + with open(test_file, "w") as f: + f.write( +"""void f() +{ + (void)(*((int*)0)); +} +""") + + build_dir = tmp_path / 'b1' + os.makedirs(build_dir) + + test_a1_file = build_dir / 'test.a1' + + args = [ + '-q', + '--debug-analyzerinfo', + '--template=simple', + '--cppcheck-build-dir={}'.format(build_dir), + '--enable=all', + str(test_file) + ] + + stderr_exp = [ + '{}:3:14: error: Null pointer dereference: (int*)0 [nullPointer]'.format(test_file), + "{}:1:6: style: The function 'f' is never used. [unusedFunction]".format(test_file) + ] + + def run_and_assert_cppcheck(stdout_exp): + exitcode, stdout, stderr = cppcheck(args) + assert exitcode == 0, stdout + assert stdout.splitlines() == stdout_exp + assert stderr.splitlines() == stderr_exp + + # no cached results + run_and_assert_cppcheck([ + "no cached result '{}' found".format(test_a1_file) + ]) + + # cached results + run_and_assert_cppcheck([ + "skipping analysis - loaded 1 cached finding(s) from '{}'".format(test_a1_file) + ]) + + # modified file + with open(test_file, 'a') as f: + f.write('\n#define DEF') + + run_and_assert_cppcheck([ + "discarding cached result - hash mismatch" # TODO: add filename + ]) + + # invalid XML + with open(test_a1_file, 'a') as f: + f.write('.') + + run_and_assert_cppcheck([ + "discarding cached result - failed to load '{}' (XML_ERROR_PARSING_TEXT)".format(test_a1_file) + ]) + + # missing root node + with open(test_a1_file, 'w') as f: + f.write('') + + run_and_assert_cppcheck([ + "discarding cached result - no root node found" # TODO: add filename + ]) + + # mismatched root node + with open(test_a1_file, 'w') as f: + f.write('') + + run_and_assert_cppcheck([ + "discarding cached result - unexpected root node" # TODO: add filename + ]) + + # missing 'hash' attribute + with open(test_a1_file, 'w') as f: + f.write('') + + run_and_assert_cppcheck([ + "discarding cached result - no 'hash' attribute found" # TODO: add filename + ]) + + # invalid 'hash' attribute + with open(test_a1_file, 'w') as f: + f.write('') + + run_and_assert_cppcheck([ + "discarding cached result - hash mismatch" # TODO: add filename + ]) + + # TODO: + # - invalid error + # - internalError diff --git a/test/testcmdlineparser.cpp b/test/testcmdlineparser.cpp index 3b343cbc92e..b6690f8d5c7 100644 --- a/test/testcmdlineparser.cpp +++ b/test/testcmdlineparser.cpp @@ -493,6 +493,7 @@ class TestCmdlineParser : public TestFixture { TEST_CASE(safetyOverride); TEST_CASE(noSafety); TEST_CASE(noSafetyOverride); + TEST_CASE(debugAnalyzerinfo); TEST_CASE(ignorepaths1); TEST_CASE(ignorepaths2); @@ -3424,6 +3425,13 @@ class TestCmdlineParser : public TestFixture { ASSERT_EQUALS(false, settings->safety); } + void debugAnalyzerinfo() { + REDIRECT; + const char * const argv[] = {"cppcheck", "--debug-analyzerinfo", "file.cpp"}; + ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parseFromArgs(argv)); + ASSERT_EQUALS(true, settings->debugainfo); + } + void ignorepaths1() { REDIRECT; const char * const argv[] = {"cppcheck", "-isrc", "file.cpp"};