Skip to content

Commit 6d2b6f4

Browse files
authored
Fix #14974: [regression] inline supression does not work with build dir anymore (#8801)
Also fixes #14993. The hash in the AnalyzerInformation file has been changed from an attribute to a node, this is because the hash is not known at the time the root node is written. An alternative would be to delay writing anything at all until checking is complete. An `includes` node has been added to the AnalyzerInformation file to keep track of which files were included during the last check, this allows calculating the correct hash and processing inline suppressions before any checking is done.
1 parent 3a3184d commit 6d2b6f4

17 files changed

Lines changed: 322 additions & 50 deletions

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -792,7 +792,7 @@ test/testcondition.o: test/testcondition.cpp lib/check.h lib/checkcondition.h li
792792
test/testconstructors.o: test/testconstructors.cpp lib/check.h lib/checkclass.h lib/checkers.h lib/checkimpl.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
793793
$(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testconstructors.cpp
794794

795-
test/testcppcheck.o: test/testcppcheck.cpp externals/simplecpp/simplecpp.h lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/preprocessor.h lib/settings.h lib/standards.h lib/suppressions.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h test/redirect.h
795+
test/testcppcheck.o: test/testcppcheck.cpp externals/simplecpp/simplecpp.h lib/addoninfo.h lib/analyzerinfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/preprocessor.h lib/settings.h lib/standards.h lib/suppressions.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h test/redirect.h
796796
$(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testcppcheck.cpp
797797

798798
test/testerrorlogger.o: test/testerrorlogger.cpp externals/tinyxml2/tinyxml2.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/cppcheck.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/suppressions.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/xml.h test/fixture.h test/helpers.h

lib/analyzerinfo.cpp

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,68 @@ void AnalyzerInformation::writeFilesTxt(const std::string &buildDir, const std::
5858
fout << getFilesTxt(sourcefiles, fileSettings);
5959
}
6060

61+
void AnalyzerInformation::writeIncludes(const std::set<std::string> &files)
62+
{
63+
if (!files.empty() && mOutputStream.is_open()) {
64+
mOutputStream << " <includes>\n";
65+
for (const std::string &file : files) {
66+
mOutputStream << " <filename>" << file << "</filename>\n";
67+
}
68+
mOutputStream << " </includes>\n";
69+
}
70+
}
71+
72+
std::set<std::string> AnalyzerInformation::getIncludes(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, std::size_t fsFileId) const
73+
{
74+
if (mOutputStream.is_open())
75+
throw std::runtime_error("analyzer information file is already open");
76+
77+
std::set<std::string> files;
78+
79+
if (buildDir.empty() || sourcefile.empty())
80+
return files;
81+
82+
const std::string analyzerInfoFile = AnalyzerInformation::getAnalyzerInfoFile(buildDir, sourcefile, cfg, fsFileId);
83+
84+
tinyxml2::XMLDocument analyzerInfoDoc;
85+
if (analyzerInfoDoc.LoadFile(analyzerInfoFile.c_str()) != tinyxml2::XML_SUCCESS)
86+
return files;
87+
88+
const tinyxml2::XMLElement *const rootNode = analyzerInfoDoc.FirstChildElement();
89+
if (rootNode == nullptr)
90+
return files;
91+
92+
if (strcmp(rootNode->Name(), "analyzerinfo") != 0)
93+
return files;
94+
95+
const tinyxml2::XMLElement *includesNode = nullptr;
96+
for (const tinyxml2::XMLElement *e = rootNode->FirstChildElement(); e; e = e->NextSiblingElement()) {
97+
if (strcmp(e->Name(), "includes") == 0) {
98+
includesNode = e;
99+
break;
100+
}
101+
}
102+
103+
if (includesNode == nullptr)
104+
return files;
105+
106+
for (const tinyxml2::XMLElement *e = includesNode->FirstChildElement(); e; e = e->NextSiblingElement()) {
107+
if (strcmp(e->Name(), "filename") != 0)
108+
continue;
109+
110+
files.insert(e->GetText());
111+
}
112+
113+
return files;
114+
}
115+
116+
void AnalyzerInformation::writeHash(std::size_t hash)
117+
{
118+
if (mOutputStream.is_open()) {
119+
mOutputStream << " <hash>" << hash << "</hash>\n";
120+
}
121+
}
122+
61123
std::string AnalyzerInformation::getFilesTxt(const std::list<std::string> &sourcefiles, const std::list<FileSettings> &fileSettings) {
62124
std::ostringstream ret;
63125

@@ -94,10 +156,17 @@ std::string AnalyzerInformation::skipAnalysis(const tinyxml2::XMLDocument &analy
94156
if (strcmp(rootNode->Name(), "analyzerinfo") != 0)
95157
return "unexpected root node";
96158

97-
const char * const attr = rootNode->Attribute("hash");
98-
if (!attr)
99-
return "no 'hash' attribute found";
100-
if (attr != std::to_string(hash))
159+
const tinyxml2::XMLElement *hashNode = nullptr;
160+
for (const tinyxml2::XMLElement *e = rootNode->FirstChildElement(); e; e = e->NextSiblingElement()) {
161+
if (strcmp(e->Name(), "hash") == 0) {
162+
hashNode = e;
163+
break;
164+
}
165+
}
166+
167+
if (!hashNode)
168+
return "no 'hash' node found";
169+
if (hashNode->GetText() != std::to_string(hash))
101170
return "hash mismatch";
102171

103172
for (const tinyxml2::XMLElement *e = rootNode->FirstChildElement(); e; e = e->NextSiblingElement()) {
@@ -194,7 +263,7 @@ bool AnalyzerInformation::analyzeFile(const std::string &buildDir, const std::st
194263
if (!mOutputStream.is_open())
195264
throw std::runtime_error("failed to open '" + analyzerInfoFile + "'");
196265
mOutputStream << "<?xml version=\"1.0\"?>\n";
197-
mOutputStream << "<analyzerinfo hash=\"" << hash << "\">\n";
266+
mOutputStream << "<analyzerinfo>\n";
198267

199268
return true;
200269
}

lib/analyzerinfo.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include <fstream>
2828
#include <functional>
2929
#include <list>
30+
#include <set>
3031
#include <string>
3132

3233
class ErrorMessage;
@@ -67,6 +68,9 @@ class CPPCHECKLIB AnalyzerInformation {
6768
bool analyzeFile(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, std::size_t fsFileId, std::size_t hash, std::list<ErrorMessage> &errors, bool debug = false);
6869
void reportErr(const ErrorMessage &msg);
6970
void setFileInfo(const std::string &check, const std::string &fileInfo);
71+
void writeIncludes(const std::set<std::string> &files);
72+
std::set<std::string> getIncludes(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, std::size_t fsFileId) const;
73+
void writeHash(std::size_t hash);
7074
static std::string getAnalyzerInfoFile(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, std::size_t fsFileId);
7175

7276
void reopen(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, std::size_t fsFileId);

lib/cppcheck.cpp

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,25 +1026,6 @@ unsigned int CppCheck::checkInternal(const FileWithDetails& file, const std::str
10261026
preprocessor.inlineSuppressions(mSuppressions.nomsg);
10271027
preprocessor.removeComments();
10281028

1029-
if (!mSettings.buildDir.empty()) {
1030-
analyzerInformation.reset(new AnalyzerInformation);
1031-
mLogger->setAnalyzerInfo(analyzerInformation.get());
1032-
}
1033-
1034-
if (analyzerInformation) {
1035-
// Calculate hash so it can be compared with old hash / future hashes
1036-
const std::size_t hash = calculateHash(preprocessor, file.spath());
1037-
std::list<ErrorMessage> errors;
1038-
if (!analyzerInformation->analyzeFile(mSettings.buildDir, file.spath(), cfgname, file.fsFileId(), hash, errors, mSettings.debugainfo)) {
1039-
while (!errors.empty()) {
1040-
mErrorLogger.reportErr(errors.front());
1041-
errors.pop_front();
1042-
}
1043-
mLogger->setAnalyzerInfo(nullptr);
1044-
return mLogger->exitcode(); // known results => no need to reanalyze file
1045-
}
1046-
}
1047-
10481029
// Get directives
10491030
std::list<Directive> directives;
10501031
preprocessor.createDirectives(directives);
@@ -1062,7 +1043,13 @@ unsigned int CppCheck::checkInternal(const FileWithDetails& file, const std::str
10621043
std::inserter(configDefines, configDefines.end()),
10631044
getDefineName);
10641045

1046+
// Keep track of all included files when using build dir
1047+
std::set<std::string> includedFiles;
1048+
10651049
preprocessor.setLoadCallback([&](simplecpp::FileData &data, bool loaded) {
1050+
if (analyzerInformation) {
1051+
includedFiles.insert(data.filename);
1052+
}
10661053
if (loaded) {
10671054
// Do preprocessing on included file
10681055
mLogger->addRemarkComments(preprocessor.getRemarkComments(data.tokens));
@@ -1078,12 +1065,37 @@ unsigned int CppCheck::checkInternal(const FileWithDetails& file, const std::str
10781065

10791066
preprocessor.setPlatformInfo();
10801067

1068+
if (!mSettings.buildDir.empty()) {
1069+
analyzerInformation.reset(new AnalyzerInformation);
1070+
mLogger->setAnalyzerInfo(analyzerInformation.get());
1071+
}
1072+
1073+
if (analyzerInformation) {
1074+
// Load all included files to get correct hashes and suppressions
1075+
for (const std::string &filename : analyzerInformation->getIncludes(mSettings.buildDir, file.spath(), cfgname, file.fsFileId()))
1076+
preprocessor.loadFile(files, filename);
1077+
// Calculate hash so it can be compared with old hash / future hashes
1078+
const std::size_t hash = calculateHash(preprocessor, file.spath());
1079+
std::list<ErrorMessage> errors;
1080+
if (!analyzerInformation->analyzeFile(mSettings.buildDir, file.spath(), cfgname, file.fsFileId(), hash, errors, mSettings.debugainfo)) {
1081+
while (!errors.empty()) {
1082+
mErrorLogger.reportErr(errors.front());
1083+
errors.pop_front();
1084+
}
1085+
mLogger->setAnalyzerInfo(nullptr);
1086+
return mLogger->exitcode(); // known results => no need to reanalyze file
1087+
}
1088+
// Clear included file list; we don't want to keep includes that have been removed from the source
1089+
// Any includes that are still present will be readded
1090+
includedFiles.clear();
1091+
}
1092+
10811093
// Get configurations..
10821094
if (maxConfigs > 1) {
10831095
Timer::run("Preprocessor::getConfigs", mTimerResults, [&]() {
10841096
configurations = { "" };
10851097
preprocessor.getConfigs(configDefines, configurations);
1086-
preprocessor.loadFiles(files);
1098+
preprocessor.loadAllIncludes(files);
10871099
});
10881100
} else {
10891101
configurations = { mSettings.userDefines };
@@ -1306,6 +1318,11 @@ unsigned int CppCheck::checkInternal(const FileWithDetails& file, const std::str
13061318
mLogger->setPlistFilenames(std::move(files));
13071319
}
13081320

1321+
if (analyzerInformation) {
1322+
analyzerInformation->writeIncludes(includedFiles);
1323+
analyzerInformation->writeHash(calculateHash(preprocessor, file.spath()));
1324+
}
1325+
13091326
executeAddons(dumpFile, file);
13101327
} catch (const TerminateException &) {
13111328
// Analysis is terminated

lib/preprocessor.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -833,7 +833,7 @@ const simplecpp::Output* Preprocessor::handleErrors(const simplecpp::OutputList&
833833
return reportOutput(outputList, showerror);
834834
}
835835

836-
bool Preprocessor::loadFiles(std::vector<std::string> &files)
836+
bool Preprocessor::loadAllIncludes(std::vector<std::string> &files)
837837
{
838838
const simplecpp::DUI dui = createDUI(mSettings, "", mLang);
839839

@@ -842,6 +842,13 @@ bool Preprocessor::loadFiles(std::vector<std::string> &files)
842842
return !handleErrors(outputList);
843843
}
844844

845+
simplecpp::FileData *Preprocessor::loadFile(std::vector<std::string> &files, const std::string &file)
846+
{
847+
const simplecpp::DUI dui = createDUI(mSettings, "", mLang);
848+
849+
return mFileCache.get("", file, dui, false, files, nullptr).first;
850+
}
851+
845852
void Preprocessor::removeComments()
846853
{
847854
removeComments(mTokens);

lib/preprocessor.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,9 @@ class CPPCHECKLIB WARN_UNUSED Preprocessor {
122122

123123
std::vector<RemarkComment> getRemarkComments(const simplecpp::TokenList &tokens) const;
124124

125-
bool loadFiles(std::vector<std::string> &files);
125+
bool loadAllIncludes(std::vector<std::string> &files);
126+
127+
simplecpp::FileData *loadFile(std::vector<std::string> &files, const std::string &file);
126128

127129
void removeComments();
128130

test/cli/inline-suppress_test.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,30 @@ def test_build_dir(tmpdir):
248248
assert stdout == ''
249249
assert ret == 0, stdout
250250

251+
252+
def test_build_dir_include(tmpdir):
253+
args = [
254+
'-q',
255+
'--template=simple',
256+
'--cppcheck-build-dir={}'.format(tmpdir),
257+
'--enable=all',
258+
'--inline-suppr',
259+
'{}5.cpp'.format(__proj_inline_suppres_path)
260+
]
261+
262+
ret, stdout, stderr = cppcheck(args, cwd=__script_dir)
263+
lines = stderr.splitlines()
264+
assert lines == []
265+
assert stdout == ''
266+
assert ret == 0, stdout
267+
268+
ret, stdout, stderr = cppcheck(args, cwd=__script_dir)
269+
lines = stderr.splitlines()
270+
assert lines == []
271+
assert stdout == ''
272+
assert ret == 0, stdout
273+
274+
251275
def test_build_dir_jobs_suppressions(tmpdir): #14064
252276
args = [
253277
'-q',

test/cli/other_test.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2320,7 +2320,7 @@ def test_builddir_hash_check_level(tmp_path): # #13376
23202320
cache_file = (build_dir / 'test.a1')
23212321

23222322
root = ElementTree.fromstring(cache_file.read_text())
2323-
hash_1 = root.get('hash')
2323+
hash_1 = root.findtext('hash')
23242324

23252325
args += ['--check-level=exhaustive']
23262326

@@ -2329,7 +2329,7 @@ def test_builddir_hash_check_level(tmp_path): # #13376
23292329
assert stderr == ''
23302330

23312331
root = ElementTree.fromstring(cache_file.read_text())
2332-
hash_2 = root.get('hash')
2332+
hash_2 = root.findtext('hash')
23332333

23342334
assert hash_1 != hash_2
23352335

@@ -4531,17 +4531,17 @@ def run_and_assert_cppcheck(stdout_exp):
45314531
"discarding cached result from '{}' for '{}' - unexpected root node".format(test_a1_file_s, test_file_s)
45324532
])
45334533

4534-
# missing 'hash' attribute
4534+
# missing 'hash' node
45354535
with open(test_a1_file, 'w') as f:
45364536
f.write('<?xml version="1.0"?><analyzerinfo/>')
45374537

45384538
run_and_assert_cppcheck([
4539-
"discarding cached result from '{}' for '{}' - no 'hash' attribute found".format(test_a1_file_s, test_file_s)
4539+
"discarding cached result from '{}' for '{}' - no 'hash' node found".format(test_a1_file_s, test_file_s)
45404540
])
45414541

4542-
# invalid 'hash' attribute
4542+
# invalid 'hash' node
45434543
with open(test_a1_file, 'w') as f:
4544-
f.write('<?xml version="1.0"?><analyzerinfo hash="hash"/>')
4544+
f.write('<?xml version="1.0"?><analyzerinfo><hash>hash</hash></analyzerinfo>')
45454545

45464546
run_and_assert_cppcheck([
45474547
"discarding cached result from '{}' for '{}' - hash mismatch".format(test_a1_file_s, test_file_s)

test/cli/premium_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,11 @@ def test_build_dir_hash_cppcheck_product(tmpdir):
8888
assert exitcode == 0
8989

9090
def _get_hash(s:str):
91-
i = s.find(' hash="')
91+
i = s.find('<hash>')
9292
if i <= -1:
9393
return ''
9494
i += 7
95-
return s[i:s.find('"', i)]
95+
return s[i:s.find('</hash>', i)]
9696

9797
with open(build_dir.join('test.a1'), 'rt') as f:
9898
f1 = f.read()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#include "5.h"

0 commit comments

Comments
 (0)