Skip to content

Commit aa7629d

Browse files
authored
aligned and optimized unique error handling (#5280)
The handling in `CppCheck::reportErr()` and `Executor::hasToLog()` was slightly different. I hope this can somehow be shared after the executor reworking. We were also using a very inappropriate container for the error list which caused a lot of overhead. `-D__GNUC__ --debug-warnings --template=daca2 --check-library -j2 ../test/testsymboldatabase.cpp` Clang 15 main process `284,218,587` -> `175,691,241` worker process `9,123,697,183` -> `8,951,903,360`
1 parent 34fb24d commit aa7629d

12 files changed

Lines changed: 243 additions & 26 deletions

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -740,7 +740,7 @@ test/testcondition.o: test/testcondition.cpp externals/simplecpp/simplecpp.h lib
740740
test/testconstructors.o: test/testconstructors.cpp lib/addoninfo.h lib/check.h lib/checkclass.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/sourcelocation.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h
741741
$(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testconstructors.cpp
742742

743-
test/testcppcheck.o: test/testcppcheck.cpp lib/addoninfo.h lib/analyzerinfo.h lib/check.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/utils.h test/fixture.h
743+
test/testcppcheck.o: test/testcppcheck.cpp lib/addoninfo.h lib/analyzerinfo.h lib/check.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/platform.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
744744
$(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testcppcheck.cpp
745745

746746
test/testerrorlogger.o: test/testerrorlogger.cpp externals/tinyxml2/tinyxml2.h lib/addoninfo.h lib/analyzerinfo.h lib/check.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/utils.h lib/xml.h test/fixture.h

cli/cppcheckexecutor.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,8 @@ class CppCheckExecutor::StdLogger : public ErrorLogger
140140
/**
141141
* Used to filter out duplicate error messages.
142142
*/
143-
std::set<std::string> mShownErrors;
143+
// TODO: store hashes instead of the full messages
144+
std::unordered_set<std::string> mShownErrors;
144145

145146
/**
146147
* Report progress time
@@ -390,6 +391,8 @@ void CppCheckExecutor::StdLogger::reportErr(const ErrorMessage &msg)
390391
return;
391392
}
392393

394+
// TODO: we generate a different message here then we log below
395+
// TODO: there should be no need for verbose and default messages here
393396
// Alert only about unique errors
394397
if (!mShownErrors.insert(msg.toString(mSettings.verbose)).second)
395398
return;

cli/executor.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,21 @@ Executor::Executor(const std::list<std::pair<std::string, std::size_t>> &files,
3737
assert(!(!files.empty() && !fileSettings.empty()));
3838
}
3939

40+
// TODO: this logic is duplicated in CppCheck::reportErr()
4041
bool Executor::hasToLog(const ErrorMessage &msg)
4142
{
43+
if (!mSettings.library.reportErrors(msg.file0))
44+
return false;
45+
4246
if (!mSuppressions.isSuppressed(msg, {}))
4347
{
48+
// TODO: there should be no need for verbose and default messages here
4449
std::string errmsg = msg.toString(mSettings.verbose);
50+
if (errmsg.empty())
51+
return false;
4552

4653
std::lock_guard<std::mutex> lg(mErrorListSync);
47-
if (std::find(mErrorList.cbegin(), mErrorList.cend(), errmsg) == mErrorList.cend()) {
48-
mErrorList.emplace_back(std::move(errmsg));
54+
if (mErrorList.emplace(std::move(errmsg)).second) {
4955
return true;
5056
}
5157
}

cli/executor.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <list>
2424
#include <mutex>
2525
#include <string>
26+
#include <unordered_set>
2627
#include <utility>
2728

2829
class Settings;
@@ -74,7 +75,8 @@ class Executor {
7475

7576
private:
7677
std::mutex mErrorListSync;
77-
std::list<std::string> mErrorList;
78+
// TODO: store hashes instead of the full messages
79+
std::unordered_set<std::string> mErrorList;
7880
};
7981

8082
/// @}

lib/cppcheck.cpp

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1565,6 +1565,7 @@ void CppCheck::purgedConfigurationMessage(const std::string &file, const std::st
15651565

15661566
//---------------------------------------------------------------------------
15671567

1568+
// TODO: part of this logic is duplicated in Executor::hasToLog()
15681569
void CppCheck::reportErr(const ErrorMessage &msg)
15691570
{
15701571
if (msg.severity == Severity::none && (msg.id == "logChecker" || endsWith(msg.id, "-logChecker"))) {
@@ -1575,17 +1576,6 @@ void CppCheck::reportErr(const ErrorMessage &msg)
15751576
if (!mSettings.library.reportErrors(msg.file0))
15761577
return;
15771578

1578-
const std::string errmsg = msg.toString(mSettings.verbose);
1579-
if (errmsg.empty())
1580-
return;
1581-
1582-
// Alert only about unique errors
1583-
if (std::find(mErrorList.cbegin(), mErrorList.cend(), errmsg) != mErrorList.cend())
1584-
return;
1585-
1586-
if (!mSettings.buildDir.empty())
1587-
mAnalyzerInformation.reportErr(msg);
1588-
15891579
std::set<std::string> macroNames;
15901580
if (!msg.callStack.empty()) {
15911581
const std::string &file = msg.callStack.back().getfile(false);
@@ -1602,12 +1592,24 @@ void CppCheck::reportErr(const ErrorMessage &msg)
16021592
return;
16031593
}
16041594

1595+
// TODO: there should be no need for the verbose and default messages here
1596+
std::string errmsg = msg.toString(mSettings.verbose);
1597+
if (errmsg.empty())
1598+
return;
1599+
1600+
// Alert only about unique errors.
1601+
// This makes sure the errors of a single check() call are unique.
1602+
// TODO: get rid of this? This is forwarded to another ErrorLogger which is also doing this
1603+
if (!mErrorList.emplace(std::move(errmsg)).second)
1604+
return;
1605+
1606+
if (!mSettings.buildDir.empty())
1607+
mAnalyzerInformation.reportErr(msg);
1608+
16051609
if (!mSettings.nofail.isSuppressed(errorMessage) && !mSettings.nomsg.isSuppressed(errorMessage)) {
16061610
mExitCode = 1;
16071611
}
16081612

1609-
mErrorList.push_back(errmsg);
1610-
16111613
mErrorLogger.reportErr(msg);
16121614
// check if plistOutput should be populated and the current output file is open and the error is not suppressed
16131615
if (!mSettings.plistOutput.empty() && mPlistFile.is_open() && !mSettings.nomsg.isSuppressed(errorMessage)) {

lib/cppcheck.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include <map>
3636
#include <set>
3737
#include <string>
38+
#include <unordered_set>
3839
#include <utility>
3940
#include <vector>
4041

@@ -215,7 +216,8 @@ class CPPCHECKLIB CppCheck : ErrorLogger {
215216
*/
216217
void reportOut(const std::string &outmsg, Color c = Color::Reset) override;
217218

218-
std::list<std::string> mErrorList;
219+
// TODO: store hashes instead of the full messages
220+
std::unordered_set<std::string> mErrorList;
219221
Settings mSettings;
220222

221223
void reportProgress(const std::string &filename, const char stage[], const std::size_t value) override;

lib/errorlogger.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,11 +618,14 @@ static void replaceColors(std::string& source) {
618618
replace(source, substitutionMap);
619619
}
620620

621+
// TODO: remove default parameters
621622
std::string ErrorMessage::toString(bool verbose, const std::string &templateFormat, const std::string &templateLocation) const
622623
{
623624
// Save this ErrorMessage in plain text.
624625

626+
// TODO: should never happen - remove this
625627
// No template is given
628+
// (not 100%) equivalent templateFormat: {callstack} ({severity}{inconclusive:, inconclusive}) {message}
626629
if (templateFormat.empty()) {
627630
std::string text;
628631
if (!callStack.empty()) {

test/helpers.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ class ScopedFile {
7070
return mFullPath;
7171
}
7272

73+
const std::string& name() const {
74+
return mName;
75+
}
76+
7377
ScopedFile(const ScopedFile&) = delete;
7478
ScopedFile(ScopedFile&&) = delete;
7579
ScopedFile& operator=(const ScopedFile&) = delete;

test/testcppcheck.cpp

Lines changed: 112 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
#include "color.h"
2020
#include "cppcheck.h"
2121
#include "errorlogger.h"
22+
#include "filesettings.h"
2223
#include "fixture.h"
24+
#include "helpers.h"
2325

2426
#include <algorithm>
2527
#include <list>
@@ -34,30 +36,37 @@ class TestCppcheck : public TestFixture {
3436

3537
class ErrorLogger2 : public ErrorLogger {
3638
public:
37-
std::list<std::string> id;
39+
std::list<std::string> ids;
40+
std::list<ErrorMessage> errmsgs;
3841

42+
private:
3943
void reportOut(const std::string & /*outmsg*/, Color /*c*/ = Color::Reset) override {}
4044

4145
void reportErr(const ErrorMessage &msg) override {
42-
id.push_back(msg.id);
46+
ids.push_back(msg.id);
47+
errmsgs.push_back(msg);
4348
}
4449
};
4550

4651
void run() override {
4752
TEST_CASE(getErrorMessages);
53+
TEST_CASE(checkWithFile);
54+
TEST_CASE(checkWithFS);
55+
TEST_CASE(suppress_error_library);
56+
TEST_CASE(unique_errors);
4857
}
4958

5059
void getErrorMessages() const {
5160
ErrorLogger2 errorLogger;
5261
CppCheck::getErrorMessages(errorLogger);
53-
ASSERT(!errorLogger.id.empty());
62+
ASSERT(!errorLogger.ids.empty());
5463

5564
// Check if there are duplicate error ids in errorLogger.id
5665
std::string duplicate;
57-
for (std::list<std::string>::const_iterator it = errorLogger.id.cbegin();
58-
it != errorLogger.id.cend();
66+
for (std::list<std::string>::const_iterator it = errorLogger.ids.cbegin();
67+
it != errorLogger.ids.cend();
5968
++it) {
60-
if (std::find(errorLogger.id.cbegin(), it, *it) != it) {
69+
if (std::find(errorLogger.ids.cbegin(), it, *it) != it) {
6170
duplicate = "Duplicate ID: " + *it;
6271
break;
6372
}
@@ -67,7 +76,7 @@ class TestCppcheck : public TestFixture {
6776
// Check for error ids from this class.
6877
bool foundPurgedConfiguration = false;
6978
bool foundTooManyConfigs = false;
70-
for (const std::string & it : errorLogger.id) {
79+
for (const std::string & it : errorLogger.ids) {
7180
if (it == "purgedConfiguration")
7281
foundPurgedConfiguration = true;
7382
else if (it == "toomanyconfigs")
@@ -76,6 +85,102 @@ class TestCppcheck : public TestFixture {
7685
ASSERT(foundPurgedConfiguration);
7786
ASSERT(foundTooManyConfigs);
7887
}
88+
89+
void checkWithFile() const
90+
{
91+
ScopedFile file("test.cpp",
92+
"int main()\n"
93+
"{\n"
94+
" int i = *((int*)0);\n"
95+
" return 0;\n"
96+
"}");
97+
98+
ErrorLogger2 errorLogger;
99+
CppCheck cppcheck(errorLogger, false, {});
100+
ASSERT_EQUALS(1, cppcheck.check(file.path()));
101+
// TODO: how to properly disable these warnings?
102+
errorLogger.ids.erase(std::remove_if(errorLogger.ids.begin(), errorLogger.ids.end(), [](const std::string& id) {
103+
return id == "logChecker";
104+
}), errorLogger.ids.end());
105+
ASSERT_EQUALS(1, errorLogger.ids.size());
106+
ASSERT_EQUALS("nullPointer", *errorLogger.ids.cbegin());
107+
}
108+
109+
void checkWithFS() const
110+
{
111+
ScopedFile file("test.cpp",
112+
"int main()\n"
113+
"{\n"
114+
" int i = *((int*)0);\n"
115+
" return 0;\n"
116+
"}");
117+
118+
ErrorLogger2 errorLogger;
119+
CppCheck cppcheck(errorLogger, false, {});
120+
FileSettings fs;
121+
fs.filename = file.path();
122+
ASSERT_EQUALS(1, cppcheck.check(fs));
123+
// TODO: how to properly disable these warnings?
124+
errorLogger.ids.erase(std::remove_if(errorLogger.ids.begin(), errorLogger.ids.end(), [](const std::string& id) {
125+
return id == "logChecker";
126+
}), errorLogger.ids.end());
127+
ASSERT_EQUALS(1, errorLogger.ids.size());
128+
ASSERT_EQUALS("nullPointer", *errorLogger.ids.cbegin());
129+
}
130+
131+
void suppress_error_library() const
132+
{
133+
ScopedFile file("test.cpp",
134+
"int main()\n"
135+
"{\n"
136+
" int i = *((int*)0);\n"
137+
" return 0;\n"
138+
"}");
139+
140+
ErrorLogger2 errorLogger;
141+
CppCheck cppcheck(errorLogger, false, {});
142+
const char xmldata[] = R"(<def format="2"><markup ext=".cpp" reporterrors="false"/></def>)";
143+
const Settings s = settingsBuilder().libraryxml(xmldata, sizeof(xmldata)).build();
144+
cppcheck.settings() = s;
145+
ASSERT_EQUALS(0, cppcheck.check(file.path()));
146+
// TODO: how to properly disable these warnings?
147+
errorLogger.ids.erase(std::remove_if(errorLogger.ids.begin(), errorLogger.ids.end(), [](const std::string& id) {
148+
return id == "logChecker";
149+
}), errorLogger.ids.end());
150+
ASSERT_EQUALS(0, errorLogger.ids.size());
151+
}
152+
153+
// TODO: hwo to actually get duplicated findings
154+
void unique_errors() const
155+
{
156+
ScopedFile file("inc.h",
157+
"inline void f()\n"
158+
"{\n"
159+
" (void)*((int*)0);\n"
160+
"}");
161+
ScopedFile test_file_a("a.cpp",
162+
"#include \"inc.h\"");
163+
ScopedFile test_file_b("b.cpp",
164+
"#include \"inc.h\"");
165+
166+
ErrorLogger2 errorLogger;
167+
CppCheck cppcheck(errorLogger, false, {});
168+
ASSERT_EQUALS(1, cppcheck.check(test_file_a.path()));
169+
ASSERT_EQUALS(1, cppcheck.check(test_file_b.path()));
170+
// TODO: how to properly disable these warnings?
171+
errorLogger.errmsgs.erase(std::remove_if(errorLogger.errmsgs.begin(), errorLogger.errmsgs.end(), [](const ErrorMessage& errmsg) {
172+
return errmsg.id == "logChecker";
173+
}), errorLogger.errmsgs.end());
174+
// the internal errorlist is cleared after each check() call
175+
ASSERT_EQUALS(2, errorLogger.errmsgs.size());
176+
auto it = errorLogger.errmsgs.cbegin();
177+
ASSERT_EQUALS("nullPointer", it->id);
178+
++it;
179+
ASSERT_EQUALS("nullPointer", it->id);
180+
}
181+
182+
// TODO: test suppressions
183+
// TODO: test all with FS
79184
};
80185

81186
REGISTER_TEST(TestCppcheck)

test/testprocessexecutor.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ class TestProcessExecutorBase : public TestFixture {
149149
TEST_CASE(showtime_file);
150150
TEST_CASE(showtime_summary);
151151
TEST_CASE(showtime_file_total);
152+
TEST_CASE(suppress_error_library);
152153
#endif // !WIN32
153154
}
154155

@@ -329,6 +330,34 @@ class TestProcessExecutorBase : public TestFixture {
329330
TODO_ASSERT(output_s.find("Check time: " + fprefix() + "_2.cpp: ") != std::string::npos);
330331
}
331332

333+
void suppress_error_library() {
334+
SUPPRESS;
335+
const Settings settingsOld = settings;
336+
const char xmldata[] = R"(<def format="2"><markup ext=".cpp" reporterrors="false"/></def>)";
337+
settings = settingsBuilder().libraryxml(xmldata, sizeof(xmldata)).build();
338+
check(2, 1, 0,
339+
"int main()\n"
340+
"{\n"
341+
" int i = *((int*)0);\n"
342+
" return 0;\n"
343+
"}");
344+
ASSERT_EQUALS("", errout.str());
345+
settings = settingsOld;
346+
}
347+
348+
void unique_errors() {
349+
SUPPRESS;
350+
ScopedFile inc_h(fprefix() + ".h",
351+
"inline void f()\n"
352+
"{\n"
353+
" (void)*((int*)0);\n"
354+
"}");
355+
check(2, 2, 2,
356+
"#include \"" + inc_h.name() +"\"");
357+
// this is made unique by the executor
358+
ASSERT_EQUALS("[" + inc_h.name() + ":3]: (error) Null pointer dereference: (int*)0\n", errout.str());
359+
}
360+
332361
// TODO: test whole program analysis
333362
};
334363

0 commit comments

Comments
 (0)