Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cli/cmdlineparser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
132 changes: 93 additions & 39 deletions lib/analyzerinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

#include <cstring>
#include <exception>
#include <iostream>
#include <map>
#include <sstream>
#include <utility>
Expand Down Expand Up @@ -84,45 +85,69 @@ void AnalyzerInformation::close()
}
}

bool AnalyzerInformation::skipAnalysis(const tinyxml2::XMLDocument &analyzerInfoDoc, std::size_t hash, std::list<ErrorMessage> &errors)
bool AnalyzerInformation::skipAnalysis(const tinyxml2::XMLDocument &analyzerInfoDoc, std::size_t hash, std::list<ErrorMessage> &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<const char*, 3> 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;
}

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 (endsWith(sourcefile, filesTxtInfo.sourceFile) && filesTxtInfo.cfg == cfg && filesTxtInfo.fileIndex == fileIndex)
return filesTxtInfo.afile;
}
return "";
}
Expand All @@ -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<ErrorMessage> &errors)
bool AnalyzerInformation::analyzeFile(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, int fileIndex, std::size_t hash, std::list<ErrorMessage> &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 << "<?xml version=\"1.0\"?>\n";
mOutputStream << "<analyzerinfo hash=\"" << hash << "\">\n";
}
if (!mOutputStream.is_open())
throw std::runtime_error("failed to open '" + analyzerInfoFile + "'");
mOutputStream << "<?xml version=\"1.0\"?>\n";
mOutputStream << "<analyzerinfo hash=\"" << hash << "\">\n";

return true;
}
Expand All @@ -179,6 +219,7 @@ void AnalyzerInformation::setFileInfo(const std::string &check, const std::strin
mOutputStream << " <FileInfo check=\"" << check << "\">\n" << fileInfo << " </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)
Expand Down Expand Up @@ -206,37 +247,50 @@ 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<void(const char* checkattr, const tinyxml2::XMLElement* e, const Info& filesTxtInfo)>& handler)
std::string AnalyzerInformation::processFilesTxt(const std::string& buildDir, const std::function<void(const char* checkattr, const tinyxml2::XMLElement* e, const Info& filesTxtInfo)>& 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 "";
}

9 changes: 6 additions & 3 deletions lib/analyzerinfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ 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<ErrorMessage> &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<ErrorMessage> &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);
Expand All @@ -77,14 +80,14 @@ class CPPCHECKLIB AnalyzerInformation {
std::string sourceFile;
};

static void processFilesTxt(const std::string& buildDir, const std::function<void(const char* checkattr, const tinyxml2::XMLElement* e, const Info& filesTxtInfo)>& handler);
static std::string processFilesTxt(const std::string& buildDir, const std::function<void(const char* checkattr, const tinyxml2::XMLElement* e, const Info& filesTxtInfo)>& handler, bool debug = false);

protected:
static std::string getFilesTxt(const std::list<std::string> &sourcefiles, const std::string &userDefines, const std::list<FileSettings> &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<ErrorMessage> &errors);
static bool skipAnalysis(const tinyxml2::XMLDocument &analyzerInfoDoc, std::size_t hash, std::list<ErrorMessage> &errors, bool debug = false);

private:
std::ofstream mOutputStream;
Expand Down
7 changes: 6 additions & 1 deletion lib/checkunusedfunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
21 changes: 13 additions & 8 deletions lib/cppcheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -974,7 +974,7 @@ unsigned int CppCheck::checkInternal(const FileWithDetails& file, const std::str
mLogger->setAnalyzerInfo(nullptr);

std::list<ErrorMessage> 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();
}
Expand Down Expand Up @@ -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<ErrorMessage> 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();
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions lib/errorlogger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
3 changes: 3 additions & 0 deletions lib/settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -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{};

Expand Down
5 changes: 5 additions & 0 deletions lib/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ bool endsWith(const std::string& str, const char (&end)[N])
return endsWith(str, end, N - 1);
}

inline bool endsWith(const std::string& str, const std::string& end)
{
return endsWith(str, end.c_str(), end.length());
}

inline static bool isPrefixStringCharLiteral(const std::string &str, char q, const std::string& p)
{
// str must be at least the prefix plus the start and end quote
Expand Down
1 change: 1 addition & 0 deletions test/cli/clang-import_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ def test_cmd_std_c(tmp_path): # #13129


# TODO: remove when we inject the build-dir into all tests
@pytest.mark.xfail(strict=True) # TODO: no analyzer information generated with --clang - see #14456
def test_cmd_std_c_builddir(tmp_path): # #13129
build_dir = tmp_path / 'b1'
os.makedirs(build_dir)
Expand Down
Loading
Loading