Skip to content
Open
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
2 changes: 2 additions & 0 deletions cli/cmdlineparser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2233,6 +2233,8 @@ bool CmdLineParser::loadAddons(Settings& settings)
result = false;
continue;
}
if (!settings.buildDir.empty())
addonInfo.initContentHash();
settings.addonInfos.emplace_back(std::move(addonInfo));
}
return result;
Expand Down
5 changes: 4 additions & 1 deletion gui/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1259,8 +1259,11 @@ bool MainWindow::getCppcheckSettings(Settings& settings, Suppressions& supprs)
settings.severity.enable(Severity::portability);
settings.severity.enable(Severity::information);
settings.checks.enable(Checks::missingInclude);
if (!settings.buildDir.empty())
if (!settings.buildDir.empty()) {
settings.checks.enable(Checks::unusedFunction);
for (AddonInfo& addonInfo : settings.addonInfos)
addonInfo.initContentHash();
}
settings.debugwarnings = mSettings->value(SETTINGS_SHOW_DEBUG_WARNINGS, false).toBool();
settings.quiet = false;
settings.verbose = true;
Expand Down
44 changes: 44 additions & 0 deletions lib/addoninfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <iostream>
#include <string>
#include <utility>
#include <vector>

#include "json.h"

Expand Down Expand Up @@ -209,6 +210,7 @@ std::string AddonInfo::getAddonInfo(const std::string &fileName, const std::stri
std::ifstream fin(fileName);
if (!fin.is_open())
return "Failed to open " + fileName;
jsonFile = Path::fromNativeSeparators(fileName);
if (name.empty()) {
name = Path::fromNativeSeparators(fileName);
if (name.find('/') != std::string::npos)
Expand All @@ -218,3 +220,45 @@ std::string AddonInfo::getAddonInfo(const std::string &fileName, const std::stri
fin >> json;
return parseAddonInfo(*this, json, fileName, exename);
}

// split the args as the shell running the addon does

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review

Minor: /bin/sh also removes backslash escapes, and splitArgs does not. With "args": ["--rules=r\\ s.txt"], the addon receives --rules=r s.txt, but splitArgs gives --rules=r\ and s.txt. The file is then not hashed, and editing it still shows the cached results (reproduced: V1, then V1). Either handle \ outside of single quotes on non-Windows, or make the comment less absolute. Environment variables such as $PWD/rules.txt are not covered either.

static std::vector<std::string> splitArgs(const std::string &args) {
#ifdef _WIN32
const std::string quotes = "\"";
#else
// popen() runs /bin/sh, which also removes single quotes
const std::string quotes = "\"\'";
#endif
std::vector<std::string> ret(1);
char quote = '\0';
for (const char c : args) {
if (quote == '\0' && quotes.find(c) != std::string::npos)
quote = c;
else if (c == quote)
quote = '\0';
else if (c == ' ' && quote == '\0')
ret.emplace_back();
else
ret.back() += c;
}
return ret;
}

void AddonInfo::initContentHash() {
contentHash.clear();
if (!jsonFile.empty())
contentHash += hashFileContent(jsonFile);
if (!scriptFile.empty())
contentHash += hashFileContent(scriptFile);
if (!executable.empty())
contentHash += hashFileContent(executable);
// files referenced as OPTION=FILE in args, e.g. --rule-texts=FILE
for (const std::string &arg : splitArgs(args)) {
const std::string::size_type pos = arg.find('=');
if (pos == std::string::npos)
continue;
const std::string path = arg.substr(pos + 1);
if (Path::isFile(path))
contentHash += hashFileContent(path);
}
}
4 changes: 4 additions & 0 deletions lib/addoninfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

struct CPPCHECKLIB AddonInfo {
std::string name;
std::string jsonFile; // addon json file
std::string scriptFile; // addon script
std::string executable; // addon executable
std::string args; // special extra arguments
Expand All @@ -34,7 +35,10 @@ struct CPPCHECKLIB AddonInfo {
std::string runScript;
std::map<std::string, std::string> checkers; // checker name and its requirement

std::string contentHash; // hash of the addon files

std::string getAddonInfo(const std::string &fileName, const std::string &exename, bool debug = false);
void initContentHash();
};

#endif // addonInfoH
1 change: 1 addition & 0 deletions lib/cppcheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -888,6 +888,7 @@ std::size_t CppCheck::calculateHash(const Preprocessor& preprocessor, const std:
for (const auto &a : mSettings.addonInfos) {
toolinfo << a.name;
toolinfo << a.args;
toolinfo << a.contentHash;
}
toolinfo << mSettings.premiumArgs;
// TODO: do we need to add more options?
Expand Down
14 changes: 14 additions & 0 deletions lib/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@

#include <algorithm>
#include <cctype>
#include <fstream>
#include <functional>
#include <ios>
#include <sstream>
#include <stack>
#include <utility>

Expand Down Expand Up @@ -198,3 +202,13 @@ std::vector<std::string> splitString(const std::string& str, char sep)
}
return l;
}

std::string hashFileContent(const std::string& path)
{
std::ifstream fin(path, std::ios::binary);
if (!fin.is_open())
return "";
std::ostringstream ostr;
ostr << fin.rdbuf();
return std::to_string(std::hash<std::string> {}(ostr.str()));
}
7 changes: 7 additions & 0 deletions lib/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,13 @@ static inline T* empty_if_null(T* p)
*/
CPPCHECKLIB std::vector<std::string> splitString(const std::string& str, char sep);

/**
* Hash the content of a file.
* @param path The file to read
* @return The hash, empty if the file cannot be opened.
*/
CPPCHECKLIB std::string hashFileContent(const std::string& path);

namespace utils {
/**
* Drop-in replacement for C++17's std::as_const
Expand Down
53 changes: 53 additions & 0 deletions test/cli/other_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2355,6 +2355,59 @@ def test_builddir_hash_check_level(tmp_path): # #13376
assert hash_1 != hash_2


def test_builddir_hash_addon(tmp_path):
test_file = tmp_path / 'test.c'
with open(test_file, 'wt') as f:
f.write('void f() {}\n')

addon_script = tmp_path / 'addon1.py'
with open(addon_script, 'wt') as f:
f.write('# addon1.py\n')

rule_texts = tmp_path / 'rule texts.txt'
with open(rule_texts, 'wt') as f:
f.write('# rule texts.txt\n')

extra_file = tmp_path / 'extra file.txt'
with open(extra_file, 'wt') as f:
f.write('# extra file.txt\n')

addon_file = tmp_path / 'addon1.json'
with open(addon_file, 'wt') as f:
f.write(json.dumps({
'script': str(addon_script),
'args': ['"--rule-texts={}"'.format(rule_texts), '--extra="{}"'.format(extra_file)]
}))

build_dir = tmp_path / 'b1'
os.mkdir(build_dir)

args = [
'--addon={}'.format(addon_file),
'--cppcheck-build-dir={}'.format(build_dir),
str(test_file)
]

cache_file = build_dir / 'test.a1'

def get_hash():
exitcode, stdout, stderr = cppcheck(args)
assert exitcode == 0, stdout
assert stderr == ''
root = ElementTree.fromstring(cache_file.read_text())
return root.findtext('hash')

hash_1 = get_hash()
assert get_hash() == hash_1

for changed_file in (addon_script, rule_texts, extra_file, addon_file):
with open(changed_file, 'a') as f:
f.write('\n')
hash_2 = get_hash()
assert hash_2 != hash_1
hash_1 = hash_2


def test_def_undef(tmp_path):
test_file = tmp_path / 'test.c'
with open(test_file, 'wt') as f:
Expand Down
79 changes: 79 additions & 0 deletions test/testcppcheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <list>
#include <string>
#include <unordered_set>
Expand Down Expand Up @@ -100,6 +101,13 @@ class TestCppcheck : public TestFixture {
TEST_CASE(getDumpFileContentsLibrary);
TEST_CASE(checkPlistOutput);
TEST_CASE(premiumResultsCache);
TEST_CASE(addonScriptHash);
TEST_CASE(addonJsonHash);
TEST_CASE(addonArgsHash);
TEST_CASE(addonQuotedArgsHash);
#ifndef _WIN32
TEST_CASE(addonSingleQuotedArgsHash);
#endif
TEST_CASE(purgedConfiguration);
TEST_CASE(recheckInclude);
}
Expand Down Expand Up @@ -612,6 +620,77 @@ class TestCppcheck : public TestFixture {
ASSERT(hash1 != hash2);
}

void addonHashInternal(AddonInfo addon, const ScopedFile& file) const
{
Settings settings;
Suppressions supprs;
ErrorLogger2 errorLogger;

std::vector<std::string> files;

const char code[] = "void f();\nint x;\n";
simplecpp::TokenList tokens(code, files, "m1.c");

Preprocessor preprocessor(tokens, settings, errorLogger, Standards::Language::C);
ASSERT(preprocessor.loadAllIncludes(files));

addon.name = "testaddon";
settings.addons.insert(addon.name);
settings.addonInfos.push_back(std::move(addon));
settings.addonInfos.back().initContentHash();

CppCheck check(settings, supprs, errorLogger, nullptr, false, {});
const size_t hash1 = check.calculateHash(preprocessor);

settings.addonInfos.back().initContentHash();
ASSERT_EQUALS(hash1, check.calculateHash(preprocessor));

{
std::ofstream of(file.path());
of << "changed content\n";
}
settings.addonInfos.back().initContentHash();
// cppcheck-suppress knownConditionTrueFalse
ASSERT(hash1 != check.calculateHash(preprocessor));
}

void addonScriptHash() const {
ScopedFile addon_script("testaddon.py", "# addon v1\n");
AddonInfo addon;
addon.scriptFile = addon_script.path();
addonHashInternal(addon, addon_script);
}

void addonJsonHash() const {
ScopedFile addon_config("testaddon.json", "{ \"script\": \"x.py\" }\n");
AddonInfo addon;
addon.jsonFile = addon_config.path();
addonHashInternal(addon, addon_config);
}

void addonArgsHash() const {
ScopedFile rule_texts("testrules.txt", "rule v1\n");
AddonInfo addon;
addon.args = "--rule-texts=" + rule_texts.path();
addonHashInternal(addon, rule_texts);
}

void addonQuotedArgsHash() const {
ScopedFile rule_texts("test rules.txt", "rule v1\n");
AddonInfo addon;
addon.args = "--rule-texts=\"" + rule_texts.path() + "\"";
addonHashInternal(addon, rule_texts);
}

#ifndef _WIN32
void addonSingleQuotedArgsHash() const {
ScopedFile rule_texts("test rules.txt", "rule v1\n");
AddonInfo addon;
addon.args = "--rule-texts='" + rule_texts.path() + "'";
addonHashInternal(addon, rule_texts);
}
#endif

void purgedConfiguration() const
{
ScopedFile test_file("test.cpp",
Expand Down
Loading