From caabe56f14ad1daf2fa8eadeb61629d3a1d1c939 Mon Sep 17 00:00:00 2001
From: Paul
Date: Sun, 12 Jul 2020 21:31:53 -0500
Subject: [PATCH 01/58] Handle FPs: mutexes being locked at different scopes
---
lib/checkstl.cpp | 9 +++++++++
test/teststl.cpp | 30 ++++++++++++++++++++++++++++++
2 files changed, 39 insertions(+)
diff --git a/lib/checkstl.cpp b/lib/checkstl.cpp
index ebeb7f44f..15bf0ac70 100644
--- a/lib/checkstl.cpp
+++ b/lib/checkstl.cpp
@@ -2430,19 +2430,28 @@ void CheckStl::localMutexError(const Token* tok)
void CheckStl::checkMutexes()
{
for (const Scope *function : mTokenizer->getSymbolDatabase()->functionScopes) {
+ std::set checkedVars;
for (const Token *tok = function->bodyStart; tok != function->bodyEnd; tok = tok->next()) {
+ if (!Token::Match(tok, "%var%"))
+ continue;
const Variable* var = tok->variable();
if (!var)
continue;
if (Token::Match(tok, "%var% . lock ( )")) {
if (!isMutex(var))
continue;
+ if (!checkedVars.insert(var->declarationId()).second)
+ continue;
if (isLocalMutex(var, tok->scope()))
localMutexError(tok);
} else if (Token::Match(tok, "%var% (|{ %var% )|}|,")) {
if (!isLockGuard(var))
continue;
const Variable* mvar = tok->tokAt(2)->variable();
+ if (!mvar)
+ continue;
+ if (!checkedVars.insert(mvar->declarationId()).second)
+ continue;
if (var->isStatic() || var->isGlobal())
globalLockGuardError(tok);
else if (isLocalMutex(mvar, tok->scope()))
diff --git a/test/teststl.cpp b/test/teststl.cpp
index 240e20709..931fdc73f 100644
--- a/test/teststl.cpp
+++ b/test/teststl.cpp
@@ -4554,6 +4554,36 @@ private:
"}\n",
true);
ASSERT_EQUALS("[test.cpp:5]: (warning) The lock is ineffective because the mutex is locked at the same scope as the mutex itself.\n", errout.str());
+
+ check("void foo();\n"
+ "void bar();\n"
+ "void f() {\n"
+ " std::mutex m;\n"
+ " std::thread t([&m](){\n"
+ " m.lock();\n"
+ " foo();\n"
+ " m.unlock();\n"
+ " });\n"
+ " m.lock();\n"
+ " bar();\n"
+ " m.unlock();\n"
+ "}\n",
+ true);
+ ASSERT_EQUALS("", errout.str());
+
+ check("void foo();\n"
+ "void bar();\n"
+ "void f() {\n"
+ " std::mutex m;\n"
+ " std::thread t([&m](){\n"
+ " std::unique_lock g{m};\n"
+ " foo();\n"
+ " });\n"
+ " std::unique_lock g{m};\n"
+ " bar();\n"
+ "}\n",
+ true);
+ ASSERT_EQUALS("", errout.str());
}
};
From 4373404238a785bcb5b73d96a82afd0111086cfa Mon Sep 17 00:00:00 2001
From: Paul
Date: Tue, 14 Jul 2020 13:04:59 -0500
Subject: [PATCH 02/58] Revert "Fixed #9795 (False positive: Local lock is not
ineffective, mutex is locked in thread also.)"
This reverts commit 27841d6b811a9a7537efe943af23f99f5afb68ce.
---
lib/checkstl.cpp | 29 ++++++++++++++++++++++++++-
lib/checkstl.h | 2 ++
test/teststl.cpp | 51 ++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 81 insertions(+), 1 deletion(-)
diff --git a/lib/checkstl.cpp b/lib/checkstl.cpp
index 58706e2ae..ebeb7f44f 100644
--- a/lib/checkstl.cpp
+++ b/lib/checkstl.cpp
@@ -2394,12 +2394,25 @@ void CheckStl::useStlAlgorithm()
}
}
+static bool isMutex(const Variable* var)
+{
+ const Token* tok = Token::typeDecl(var->nameToken()).first;
+ return Token::Match(tok, "std :: mutex|recursive_mutex|timed_mutex|recursive_timed_mutex|shared_mutex");
+}
+
static bool isLockGuard(const Variable* var)
{
const Token* tok = Token::typeDecl(var->nameToken()).first;
return Token::Match(tok, "std :: lock_guard|unique_lock|scoped_lock");
}
+static bool isLocalMutex(const Variable* var, const Scope* scope)
+{
+ if (!var)
+ return false;
+ return !var->isReference() && !var->isRValueReference() && !var->isStatic() && var->scope() == scope;
+}
+
void CheckStl::globalLockGuardError(const Token* tok)
{
reportError(tok, Severity::warning,
@@ -2407,6 +2420,13 @@ void CheckStl::globalLockGuardError(const Token* tok)
"Lock guard is defined globally. Lock guards are intended to be local. A global lock guard could lead to a deadlock since it won't unlock until the end of the program.", CWE833, false);
}
+void CheckStl::localMutexError(const Token* tok)
+{
+ reportError(tok, Severity::warning,
+ "localMutex",
+ "The lock is ineffective because the mutex is locked at the same scope as the mutex itself.", CWE667, false);
+}
+
void CheckStl::checkMutexes()
{
for (const Scope *function : mTokenizer->getSymbolDatabase()->functionScopes) {
@@ -2414,12 +2434,19 @@ void CheckStl::checkMutexes()
const Variable* var = tok->variable();
if (!var)
continue;
- if (Token::Match(tok, "%var% (|{ %var% )|}|,")) {
+ if (Token::Match(tok, "%var% . lock ( )")) {
+ if (!isMutex(var))
+ continue;
+ if (isLocalMutex(var, tok->scope()))
+ localMutexError(tok);
+ } else if (Token::Match(tok, "%var% (|{ %var% )|}|,")) {
if (!isLockGuard(var))
continue;
const Variable* mvar = tok->tokAt(2)->variable();
if (var->isStatic() || var->isGlobal())
globalLockGuardError(tok);
+ else if (isLocalMutex(mvar, tok->scope()))
+ localMutexError(tok);
}
}
}
diff --git a/lib/checkstl.h b/lib/checkstl.h
index a0ae7d1e5..9dd5e07d5 100644
--- a/lib/checkstl.h
+++ b/lib/checkstl.h
@@ -233,6 +233,7 @@ private:
void useStlAlgorithmError(const Token *tok, const std::string &algoName);
void globalLockGuardError(const Token *tok);
+ void localMutexError(const Token *tok);
void getErrorMessages(ErrorLogger* errorLogger, const Settings* settings) const OVERRIDE {
ErrorPath errorPath;
@@ -271,6 +272,7 @@ private:
c.readingEmptyStlContainerError(nullptr);
c.useStlAlgorithmError(nullptr, "");
c.globalLockGuardError(nullptr);
+ c.localMutexError(nullptr);
}
static std::string myName() {
diff --git a/test/teststl.cpp b/test/teststl.cpp
index e9e929a92..240e20709 100644
--- a/test/teststl.cpp
+++ b/test/teststl.cpp
@@ -4445,6 +4445,28 @@ private:
true);
ASSERT_EQUALS("", errout.str());
+ check("void f() {\n"
+ " std::mutex m;\n"
+ " std::lock_guard g(m);\n"
+ "}\n",
+ true);
+ ASSERT_EQUALS("[test.cpp:3]: (warning) The lock is ineffective because the mutex is locked at the same scope as the mutex itself.\n", errout.str());
+
+ check("void f() {\n"
+ " std::mutex m;\n"
+ " std::unique_lock g(m);\n"
+ "}\n",
+ true);
+ ASSERT_EQUALS("[test.cpp:3]: (warning) The lock is ineffective because the mutex is locked at the same scope as the mutex itself.\n", errout.str());
+
+ check("void f() {\n"
+ " std::mutex m;\n"
+ " std::unique_lock g(m, std::defer_lock);\n"
+ " std::lock(g);\n"
+ "}\n",
+ true);
+ ASSERT_EQUALS("[test.cpp:3]: (warning) The lock is ineffective because the mutex is locked at the same scope as the mutex itself.\n", errout.str());
+
check("void g();\n"
"void f() {\n"
" static std::mutex m;\n"
@@ -4455,6 +4477,16 @@ private:
true);
ASSERT_EQUALS("", errout.str());
+ check("void g();\n"
+ "void f() {\n"
+ " std::mutex m;\n"
+ " m.lock();\n"
+ " g();\n"
+ " m.unlock();\n"
+ "}\n",
+ true);
+ ASSERT_EQUALS("[test.cpp:4]: (warning) The lock is ineffective because the mutex is locked at the same scope as the mutex itself.\n", errout.str());
+
check("class A {\n"
" std::mutex m;\n"
" void f() {\n"
@@ -4503,6 +4535,25 @@ private:
"}\n",
true);
ASSERT_EQUALS("", errout.str());
+
+ check("std::mutex& h();\n"
+ "void f() {\n"
+ " auto m = h();\n"
+ " std::lock_guard g(m);\n"
+ "}\n",
+ true);
+ ASSERT_EQUALS("[test.cpp:4]: (warning) The lock is ineffective because the mutex is locked at the same scope as the mutex itself.\n", errout.str());
+
+ check("void g();\n"
+ "std::mutex& h();\n"
+ "void f() {\n"
+ " auto m = h();\n"
+ " m.lock();\n"
+ " g();\n"
+ " m.unlock();\n"
+ "}\n",
+ true);
+ ASSERT_EQUALS("[test.cpp:5]: (warning) The lock is ineffective because the mutex is locked at the same scope as the mutex itself.\n", errout.str());
}
};
From af0db3cc2176079fc752402a083ee09604cef4ec Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Tue, 14 Jul 2020 22:30:42 +0200
Subject: [PATCH 03/58] Add cppcheck-id for warnings. To start with it's a
simple id that changes when file is changed.
---
lib/errorlogger.cpp | 46 +++++++++++++++++++++++++++-------------
lib/errorlogger.h | 3 +++
test/testerrorlogger.cpp | 2 ++
3 files changed, 36 insertions(+), 15 deletions(-)
diff --git a/lib/errorlogger.cpp b/lib/errorlogger.cpp
index 97f8f5ab3..b3d25952b 100644
--- a/lib/errorlogger.cpp
+++ b/lib/errorlogger.cpp
@@ -32,6 +32,7 @@
#include
#include
#include
+#include // std::hash
InternalError::InternalError(const Token *tok, const std::string &errorMsg, Type type) :
token(tok), errorMessage(errorMsg), type(type)
@@ -58,8 +59,15 @@ InternalError::InternalError(const Token *tok, const std::string &errorMsg, Type
}
}
+static std::size_t calculateCppcheckId(const TokenList *tokenList, const std::string &msg)
+{
+ if (!tokenList)
+ return 0;
+ return std::hash {}(msg + "\n" + tokenList->front()->stringifyList(false, true, false, false, false));
+}
+
ErrorMessage::ErrorMessage()
- : incomplete(false), severity(Severity::none), cwe(0U), inconclusive(false)
+ : incomplete(false), severity(Severity::none), cwe(0U), inconclusive(false), cppcheckId(0)
{
}
@@ -70,7 +78,8 @@ ErrorMessage::ErrorMessage(const std::list &callStack, const std::
incomplete(false),
severity(severity), // severity for this error message
cwe(0U),
- inconclusive(inconclusive)
+ inconclusive(inconclusive),
+ cppcheckId(0)
{
// set the summary and verbose messages
setmsg(msg);
@@ -85,14 +94,15 @@ ErrorMessage::ErrorMessage(const std::list &callStack, const std::
incomplete(false),
severity(severity), // severity for this error message
cwe(cwe.id),
- inconclusive(inconclusive)
+ inconclusive(inconclusive),
+ cppcheckId(0)
{
// set the summary and verbose messages
setmsg(msg);
}
ErrorMessage::ErrorMessage(const std::list& callstack, const TokenList* list, Severity::SeverityType severity, const std::string& id, const std::string& msg, bool inconclusive)
- : id(id), incomplete(false), severity(severity), cwe(0U), inconclusive(inconclusive)
+ : id(id), incomplete(false), severity(severity), cwe(0U), inconclusive(inconclusive), cppcheckId(0)
{
// Format callstack
for (std::list::const_iterator it = callstack.begin(); it != callstack.end(); ++it) {
@@ -126,6 +136,8 @@ ErrorMessage::ErrorMessage(const std::list& callstack, const Token
file0 = list->getFiles()[0];
setmsg(msg);
+
+ cppcheckId = calculateCppcheckId(list, toString(false));
}
ErrorMessage::ErrorMessage(const ErrorPath &errorPath, const TokenList *tokenList, Severity::SeverityType severity, const char id[], const std::string &msg, const CWE &cwe, bool inconclusive)
@@ -145,6 +157,8 @@ ErrorMessage::ErrorMessage(const ErrorPath &errorPath, const TokenList *tokenLis
file0 = tokenList->getFiles()[0];
setmsg(msg);
+
+ cppcheckId = calculateCppcheckId(tokenList, toString(false));
}
ErrorMessage::ErrorMessage(const tinyxml2::XMLElement * const errmsg)
@@ -173,6 +187,9 @@ ErrorMessage::ErrorMessage(const tinyxml2::XMLElement * const errmsg)
attr = errmsg->Attribute("verbose");
mVerboseMessage = attr ? attr : "";
+ attr = errmsg->Attribute("cppcheck-id");
+ std::istringstream(attr ? attr : "0") >> cppcheckId;
+
for (const tinyxml2::XMLElement *e = errmsg->FirstChildElement(); e; e = e->NextSiblingElement()) {
if (std::strcmp(e->Name(),"location")==0) {
const char *strfile = e->Attribute("file");
@@ -236,6 +253,7 @@ std::string ErrorMessage::serialize() const
oss << id.length() << " " << id;
oss << Severity::toString(severity).length() << " " << Severity::toString(severity);
oss << MathLib::toString(cwe.id).length() << " " << MathLib::toString(cwe.id);
+ oss << MathLib::toString(cppcheckId).length() << " " << MathLib::toString(cppcheckId);
if (inconclusive) {
const std::string text("inconclusive");
oss << text.length() << " " << text;
@@ -262,9 +280,9 @@ bool ErrorMessage::deserialize(const std::string &data)
inconclusive = false;
callStack.clear();
std::istringstream iss(data);
- std::array results;
+ std::array results;
std::size_t elem = 0;
- while (iss.good()) {
+ while (iss.good() && elem < 6) {
unsigned int len = 0;
if (!(iss >> len))
return false;
@@ -282,19 +300,17 @@ bool ErrorMessage::deserialize(const std::string &data)
}
results[elem++] = temp;
- if (elem == 5)
- break;
}
- if (elem != 5)
+ if (elem != 6)
throw InternalError(nullptr, "Internal Error: Deserialization of error message failed");
id = results[0];
severity = Severity::fromString(results[1]);
- std::istringstream scwe(results[2]);
- scwe >> cwe.id;
- mShortMessage = results[3];
- mVerboseMessage = results[4];
+ std::istringstream(results[2]) >> cwe.id;
+ std::istringstream(results[3]) >> cppcheckId;
+ mShortMessage = results[4];
+ mVerboseMessage = results[5];
unsigned int stackSize = 0;
if (!(iss >> stackSize))
@@ -347,8 +363,6 @@ bool ErrorMessage::deserialize(const std::string &data)
std::string ErrorMessage::getXMLHeader()
{
- // xml_version 1 is the default xml format
-
tinyxml2::XMLPrinter printer;
// standard xml header
@@ -403,6 +417,8 @@ std::string ErrorMessage::toXML() const
printer.PushAttribute("verbose", fixInvalidChars(mVerboseMessage).c_str());
if (cwe.id)
printer.PushAttribute("cwe", cwe.id);
+ if (cppcheckId)
+ printer.PushAttribute("cppcheck-id", MathLib::toString(cppcheckId).c_str());
if (inconclusive)
printer.PushAttribute("inconclusive", "true");
diff --git a/lib/errorlogger.h b/lib/errorlogger.h
index ea03eecce..76d168be5 100644
--- a/lib/errorlogger.h
+++ b/lib/errorlogger.h
@@ -196,6 +196,9 @@ public:
CWE cwe;
bool inconclusive;
+ /** Warning ID */
+ std::size_t cppcheckId;
+
/** set short and verbose messages */
void setmsg(const std::string &msg);
diff --git a/test/testerrorlogger.cpp b/test/testerrorlogger.cpp
index 3fb718371..f827f8723 100644
--- a/test/testerrorlogger.cpp
+++ b/test/testerrorlogger.cpp
@@ -252,6 +252,7 @@ private:
ASSERT_EQUALS("7 errorId"
"5 error"
"1 0"
+ "1 0"
"12 inconclusive"
"17 Programming error"
"17 Programming error"
@@ -278,6 +279,7 @@ private:
ASSERT_EQUALS("7 errorId"
"5 error"
"1 0"
+ "1 0"
"33 Illegal character in \"foo\\001bar\""
"33 Illegal character in \"foo\\001bar\""
"0 ", msg.serialize());
From 260c53ba6f73e7395e11c6232aa78f6a1eb51639 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Wed, 15 Jul 2020 07:59:05 +0200
Subject: [PATCH 04/58] GUI: Write cppcheck-id in xml report
---
gui/erroritem.cpp | 9 +++++++--
gui/erroritem.h | 4 +++-
gui/resultstree.cpp | 4 ++++
gui/resultsview.cpp | 2 +-
gui/xmlreportv2.cpp | 7 ++++++-
5 files changed, 21 insertions(+), 5 deletions(-)
diff --git a/gui/erroritem.cpp b/gui/erroritem.cpp
index acf420204..cccc25911 100644
--- a/gui/erroritem.cpp
+++ b/gui/erroritem.cpp
@@ -37,6 +37,7 @@ ErrorItem::ErrorItem()
, incomplete(false)
, inconclusive(false)
, cwe(-1)
+ , cppcheckId(0)
{
}
@@ -50,6 +51,7 @@ ErrorItem::ErrorItem(const ErrorMessage &errmsg)
, summary(QString::fromStdString(errmsg.shortMessage()))
, message(QString::fromStdString(errmsg.verboseMessage()))
, cwe(errmsg.cwe.id)
+ , cppcheckId(errmsg.cppcheckId)
, symbolNames(QString::fromStdString(errmsg.symbolNames()))
{
for (std::list::const_iterator loc = errmsg.callStack.begin();
@@ -70,7 +72,7 @@ QString ErrorItem::tool() const
return "cppcheck";
}
-QString ErrorItem::ToString() const
+QString ErrorItem::toString() const
{
QString str = errorPath.back().file + " - " + errorId + " - ";
if (inconclusive)
@@ -86,7 +88,10 @@ QString ErrorItem::ToString() const
bool ErrorItem::sameCID(const ErrorItem &errorItem1, const ErrorItem &errorItem2)
{
- // TODO: Implement some better CID calculation
+ if (errorItem1.cppcheckId || errorItem2.cppcheckId)
+ return errorItem1.cppcheckId == errorItem2.cppcheckId;
+
+ // fallback
return errorItem1.errorId == errorItem2.errorId &&
errorItem1.errorPath == errorItem2.errorPath &&
errorItem1.file0 == errorItem2.file0 &&
diff --git a/gui/erroritem.h b/gui/erroritem.h
index 96ed90e7d..7bcb9a74f 100644
--- a/gui/erroritem.h
+++ b/gui/erroritem.h
@@ -76,7 +76,7 @@ public:
* @brief Convert error item to string.
* @return Error item as string.
*/
- QString ToString() const;
+ QString toString() const;
QString tool() const;
QString file0;
@@ -88,6 +88,7 @@ public:
QString summary;
QString message;
int cwe;
+ unsigned long long cppcheckId;
QList errorPath;
QString symbolNames;
@@ -114,6 +115,7 @@ public:
QString errorId;
bool incomplete;
int cwe;
+ unsigned long long cppcheckId;
bool inconclusive;
Severity::SeverityType severity;
QString summary;
diff --git a/gui/resultstree.cpp b/gui/resultstree.cpp
index c3e3d4b22..cbb1e3b93 100644
--- a/gui/resultstree.cpp
+++ b/gui/resultstree.cpp
@@ -168,6 +168,7 @@ bool ResultsTree::addErrorItem(const ErrorItem &item)
line.errorId = item.errorId;
line.incomplete = item.incomplete;
line.cwe = item.cwe;
+ line.cppcheckId = item.cppcheckId;
line.inconclusive = item.inconclusive;
line.summary = item.summary;
line.message = item.message;
@@ -197,6 +198,7 @@ bool ResultsTree::addErrorItem(const ErrorItem &item)
data["id"] = item.errorId;
data["incomplete"] = item.incomplete;
data["cwe"] = item.cwe;
+ data["cppcheckId"] = item.cppcheckId;
data["inconclusive"] = item.inconclusive;
data["file0"] = stripPath(item.file0, true);
data["function"] = item.function;
@@ -232,6 +234,7 @@ bool ResultsTree::addErrorItem(const ErrorItem &item)
child_data["id"] = line.errorId;
child_data["incomplete"] = line.incomplete;
child_data["cwe"] = line.cwe;
+ child_data["cppcheckId"] = line.cppcheckId;
child_data["inconclusive"] = line.inconclusive;
child_item->setData(QVariant(child_data));
}
@@ -1221,6 +1224,7 @@ void ResultsTree::readErrorItem(const QStandardItem *error, ErrorItem *item) con
item->errorId = data["id"].toString();
item->incomplete = data["incomplete"].toBool();
item->cwe = data["cwe"].toInt();
+ item->cppcheckId = data["cppcheckId"].toULongLong();
item->inconclusive = data["inconclusive"].toBool();
item->file0 = data["file0"].toString();
item->sinceDate = data["sinceDate"].toString();
diff --git a/gui/resultsview.cpp b/gui/resultsview.cpp
index 1dc0a294c..fcc88d73b 100644
--- a/gui/resultsview.cpp
+++ b/gui/resultsview.cpp
@@ -448,7 +448,7 @@ void ResultsView::log(const QString &str)
void ResultsView::debugError(const ErrorItem &item)
{
- mUI.mListLog->addItem(item.ToString());
+ mUI.mListLog->addItem(item.toString());
}
void ResultsView::bughuntingReportLine(const QString& line)
diff --git a/gui/xmlreportv2.cpp b/gui/xmlreportv2.cpp
index f70053de5..5fac01c9e 100644
--- a/gui/xmlreportv2.cpp
+++ b/gui/xmlreportv2.cpp
@@ -34,6 +34,7 @@ static const QString ErrorElementName = "error";
static const QString ErrorsElementName = "errors";
static const QString LocationElementName = "location";
static const QString CWEAttribute = "cwe";
+static const QString CppcheckIdAttribute = "cppcheck-id";
static const QString SinceDateAttribute = "sinceDate";
static const QString TagsAttribute = "tag";
static const QString FilenameAttribute = "file";
@@ -122,6 +123,8 @@ void XmlReportV2::writeError(const ErrorItem &error)
mXmlWriter->writeAttribute(InconclusiveAttribute, "true");
if (error.cwe > 0)
mXmlWriter->writeAttribute(CWEAttribute, QString::number(error.cwe));
+ if (error.cppcheckId > 0)
+ mXmlWriter->writeAttribute(CppcheckIdAttribute, QString::number(error.cppcheckId));
if (!error.sinceDate.isEmpty())
mXmlWriter->writeAttribute(SinceDateAttribute, error.sinceDate);
if (!error.tags.isEmpty())
@@ -214,7 +217,9 @@ ErrorItem XmlReportV2::readError(QXmlStreamReader *reader)
if (attribs.hasAttribute(QString(), InconclusiveAttribute))
item.inconclusive = true;
if (attribs.hasAttribute(QString(), CWEAttribute))
- item.cwe = attribs.value(QString(), CWEAttribute).toString().toInt();
+ item.cwe = attribs.value(QString(), CWEAttribute).toInt();
+ if (attribs.hasAttribute(QString(), CppcheckIdAttribute))
+ item.cppcheckId = attribs.value(QString(), CWEAttribute).toULongLong();
if (attribs.hasAttribute(QString(), SinceDateAttribute))
item.sinceDate = attribs.value(QString(), SinceDateAttribute).toString();
if (attribs.hasAttribute(QString(), TagsAttribute))
From 6ab4f39f523f3e55a4e001a3e3468a8b2c549d4c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Wed, 15 Jul 2020 13:03:07 +0200
Subject: [PATCH 05/58] GUI: Suppress cppcheck-id
---
gui/mainwindow.cpp | 3 +++
gui/projectfile.cpp | 15 +++++++++++++++
gui/projectfile.h | 14 ++++++++++++++
gui/resultstree.cpp | 27 +++++++++++++++++++++++++++
gui/resultstree.h | 3 +++
gui/xmlreportv2.cpp | 2 +-
lib/errorlogger.cpp | 1 +
lib/suppressions.cpp | 10 ++++++++--
lib/suppressions.h | 9 +++++++--
9 files changed, 79 insertions(+), 5 deletions(-)
diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp
index 0a78fc653..e721b605c 100644
--- a/gui/mainwindow.cpp
+++ b/gui/mainwindow.cpp
@@ -341,6 +341,7 @@ void MainWindow::loadSettings()
if (inf.exists() && inf.isReadable()) {
setPath(SETTINGS_LAST_PROJECT_PATH, projectFile);
mProjectFile = new ProjectFile(this);
+ mProjectFile->setActiveProject();
mProjectFile->read(projectFile);
loadLastResults();
QDir::setCurrent(inf.absolutePath());
@@ -1508,6 +1509,7 @@ void MainWindow::loadProjectFile(const QString &filePath)
mUI.mActionEditProjectFile->setEnabled(true);
delete mProjectFile;
mProjectFile = new ProjectFile(filePath, this);
+ mProjectFile->setActiveProject();
updateContractsTab();
if (!loadLastResults())
analyzeProject(mProjectFile);
@@ -1630,6 +1632,7 @@ void MainWindow::newProjectFile()
delete mProjectFile;
mProjectFile = new ProjectFile(this);
+ mProjectFile->setActiveProject();
mProjectFile->setFilename(filepath);
mProjectFile->setBuildDir(filename.left(filename.indexOf(".")) + "-cppcheck-build-dir");
diff --git a/gui/projectfile.cpp b/gui/projectfile.cpp
index 01bdd28fa..fce386ee8 100644
--- a/gui/projectfile.cpp
+++ b/gui/projectfile.cpp
@@ -28,6 +28,8 @@
#include "settings.h"
+ProjectFile *ProjectFile::mActiveProject;
+
ProjectFile::ProjectFile(QObject *parent) :
QObject(parent)
{
@@ -603,6 +605,8 @@ void ProjectFile::readSuppressions(QXmlStreamReader &reader)
suppression.lineNumber = reader.attributes().value(QString(),"lineNumber").toInt();
if (reader.attributes().hasAttribute(QString(),"symbolName"))
suppression.symbolName = reader.attributes().value(QString(),"symbolName").toString().toStdString();
+ if (reader.attributes().hasAttribute(QString(),"cppcheck-id"))
+ suppression.cppcheckId = reader.attributes().value(QString(),"cppcheck-id").toULongLong();
type = reader.readNext();
if (type == QXmlStreamReader::Characters) {
suppression.errorId = reader.text().toString().toStdString();
@@ -873,6 +877,8 @@ bool ProjectFile::write(const QString &filename)
xmlWriter.writeAttribute("lineNumber", QString::number(suppression.lineNumber));
if (!suppression.symbolName.empty())
xmlWriter.writeAttribute("symbolName", QString::fromStdString(suppression.symbolName));
+ if (suppression.cppcheckId > 0)
+ xmlWriter.writeAttribute("cppcheck-id", QString::number(suppression.cppcheckId));
if (!suppression.errorId.empty())
xmlWriter.writeCharacters(QString::fromStdString(suppression.errorId));
xmlWriter.writeEndElement();
@@ -1022,3 +1028,12 @@ QString ProjectFile::getAddonFilePath(QString filesDir, const QString &addon)
return QString();
}
+void ProjectFile::suppressCppcheckId(std::size_t cppcheckId)
+{
+ if (cppcheckId > 0) {
+ Suppressions::Suppression s;
+ s.cppcheckId = cppcheckId;
+ mSuppressions.append(s);
+ write();
+ }
+}
diff --git a/gui/projectfile.h b/gui/projectfile.h
index 18772dcd8..fc1effc00 100644
--- a/gui/projectfile.h
+++ b/gui/projectfile.h
@@ -44,6 +44,19 @@ class ProjectFile : public QObject {
public:
explicit ProjectFile(QObject *parent = nullptr);
explicit ProjectFile(const QString &filename, QObject *parent = nullptr);
+ ~ProjectFile() {
+ if (this == mActiveProject) mActiveProject = nullptr;
+ }
+
+ static ProjectFile* getActiveProject() {
+ return mActiveProject;
+ }
+ void setActiveProject() {
+ mActiveProject = this;
+ }
+
+ /** Suppress warning with Cppcheck-ID */
+ void suppressCppcheckId(std::size_t cppcheckId);
/**
* @brief Read the project file.
@@ -551,6 +564,7 @@ private:
QStringList mCheckUnknownFunctionReturn;
+ static ProjectFile *mActiveProject;
};
/// @}
#endif // PROJECT_FILE_H
diff --git a/gui/resultstree.cpp b/gui/resultstree.cpp
index cbb1e3b93..7233a4d15 100644
--- a/gui/resultstree.cpp
+++ b/gui/resultstree.cpp
@@ -43,6 +43,7 @@
#include "resultstree.h"
#include "report.h"
#include "application.h"
+#include "projectfile.h"
#include "showtypes.h"
#include "threadhandler.h"
#include "path.h"
@@ -638,6 +639,7 @@ void ResultsTree::contextMenuEvent(QContextMenuEvent * e)
QAction *copy = new QAction(tr("Copy"), &menu);
QAction *hide = new QAction(tr("Hide"), &menu);
QAction *hideallid = new QAction(tr("Hide all with id"), &menu);
+ QAction *suppressCppcheckID = new QAction(tr("Suppress cppcheck-id"), &menu);
QAction *opencontainingfolder = new QAction(tr("Open containing folder"), &menu);
if (multipleSelection) {
@@ -655,6 +657,7 @@ void ResultsTree::contextMenuEvent(QContextMenuEvent * e)
menu.addSeparator();
menu.addAction(hide);
menu.addAction(hideallid);
+ menu.addAction(suppressCppcheckID);
if (!bughunting) {
QAction *suppress = new QAction(tr("Suppress selected id(s)"), &menu);
menu.addAction(suppress);
@@ -667,6 +670,7 @@ void ResultsTree::contextMenuEvent(QContextMenuEvent * e)
connect(copy, SIGNAL(triggered()), this, SLOT(copy()));
connect(hide, SIGNAL(triggered()), this, SLOT(hideResult()));
connect(hideallid, SIGNAL(triggered()), this, SLOT(hideAllIdResult()));
+ connect(suppressCppcheckID, &QAction::triggered, this, &ResultsTree::suppressCppcheckID);
connect(opencontainingfolder, SIGNAL(triggered()), this, SLOT(openContainingFolder()));
if (!mTags.isEmpty()) {
@@ -1031,12 +1035,35 @@ void ResultsTree::suppressSelectedIds()
j++;
}
}
+ if (file->rowCount() == 0)
+ mModel.removeRow(file->row());
}
emit suppressIds(selectedIds.toList());
}
+void ResultsTree::suppressCppcheckID()
+{
+ if (!mSelectionModel)
+ return;
+ ProjectFile *projectFile = ProjectFile::getActiveProject();
+ foreach (QModelIndex index, mSelectionModel->selectedRows()) {
+ QStandardItem *item = mModel.itemFromIndex(index);
+ if (!item->parent())
+ continue;
+ while (item->parent()->parent())
+ item = item->parent();
+ const QVariantMap data = item->data().toMap();
+ if (projectFile && data.contains("cppcheckId"))
+ projectFile->suppressCppcheckId(data["cppcheckId"].toULongLong());
+ QStandardItem *fileItem = item->parent();
+ fileItem->removeRow(item->row());
+ if (fileItem->rowCount() == 0)
+ mModel.removeRow(fileItem->row());
+ }
+}
+
void ResultsTree::openContainingFolder()
{
QString filePath = getFilePath(mContextItem, true);
diff --git a/gui/resultstree.h b/gui/resultstree.h
index 55b5b709e..349ea855f 100644
--- a/gui/resultstree.h
+++ b/gui/resultstree.h
@@ -285,6 +285,9 @@ protected slots:
/** Slot for context menu item to suppress all messages with the current message id */
void suppressSelectedIds();
+ /** Slot for context menu item to suppress message with cppcheck ID */
+ void suppressCppcheckID();
+
/**
* @brief Slot for context menu item to open the folder containing the current file.
*/
diff --git a/gui/xmlreportv2.cpp b/gui/xmlreportv2.cpp
index 5fac01c9e..00fd096e8 100644
--- a/gui/xmlreportv2.cpp
+++ b/gui/xmlreportv2.cpp
@@ -219,7 +219,7 @@ ErrorItem XmlReportV2::readError(QXmlStreamReader *reader)
if (attribs.hasAttribute(QString(), CWEAttribute))
item.cwe = attribs.value(QString(), CWEAttribute).toInt();
if (attribs.hasAttribute(QString(), CppcheckIdAttribute))
- item.cppcheckId = attribs.value(QString(), CWEAttribute).toULongLong();
+ item.cppcheckId = attribs.value(QString(), CppcheckIdAttribute).toULongLong();
if (attribs.hasAttribute(QString(), SinceDateAttribute))
item.sinceDate = attribs.value(QString(), SinceDateAttribute).toString();
if (attribs.hasAttribute(QString(), TagsAttribute))
diff --git a/lib/errorlogger.cpp b/lib/errorlogger.cpp
index b3d25952b..06bfbde76 100644
--- a/lib/errorlogger.cpp
+++ b/lib/errorlogger.cpp
@@ -235,6 +235,7 @@ void ErrorMessage::setmsg(const std::string &msg)
Suppressions::ErrorMessage ErrorMessage::toSuppressionsErrorMessage() const
{
Suppressions::ErrorMessage ret;
+ ret.cppcheckId = cppcheckId;
ret.errorId = id;
if (!callStack.empty()) {
ret.setFileName(callStack.back().getfile(false));
diff --git a/lib/suppressions.cpp b/lib/suppressions.cpp
index 0f307b834..93bc71531 100644
--- a/lib/suppressions.cpp
+++ b/lib/suppressions.cpp
@@ -204,9 +204,9 @@ std::string Suppressions::addSuppressionLine(const std::string &line)
std::string Suppressions::addSuppression(const Suppressions::Suppression &suppression)
{
// Check that errorId is valid..
- if (suppression.errorId.empty()) {
+ if (suppression.errorId.empty() && suppression.cppcheckId == 0)
return "Failed to add suppression. No id.";
- }
+
if (suppression.errorId != "*") {
for (std::string::size_type pos = 0; pos < suppression.errorId.length(); ++pos) {
if (suppression.errorId[pos] < 0 || !isAcceptedErrorIdChar(suppression.errorId[pos])) {
@@ -271,6 +271,8 @@ bool Suppressions::Suppression::parseComment(std::string comment, std::string *e
bool Suppressions::Suppression::isSuppressed(const Suppressions::ErrorMessage &errmsg) const
{
+ if (cppcheckId > 0 && cppcheckId != errmsg.cppcheckId)
+ return false;
if (!errorId.empty() && !matchglob(errorId, errmsg.errorId))
return false;
if (!fileName.empty() && !matchglob(fileName, errmsg.getFileName()))
@@ -371,6 +373,8 @@ std::list Suppressions::getUnmatchedLocalSuppressions
for (const Suppression &s : mSuppressions) {
if (s.matched)
continue;
+ if (s.cppcheckId > 0)
+ continue;
if (!unusedFunctionChecking && s.errorId == "unusedFunction")
continue;
if (file.empty() || !s.isLocal() || s.fileName != file)
@@ -386,6 +390,8 @@ std::list Suppressions::getUnmatchedGlobalSuppression
for (const Suppression &s : mSuppressions) {
if (s.matched)
continue;
+ if (s.cppcheckId > 0)
+ continue;
if (!unusedFunctionChecking && s.errorId == "unusedFunction")
continue;
if (s.isLocal())
diff --git a/lib/suppressions.h b/lib/suppressions.h
index 014e897df..5c9873e37 100644
--- a/lib/suppressions.h
+++ b/lib/suppressions.h
@@ -35,6 +35,7 @@ class CPPCHECKLIB Suppressions {
public:
struct CPPCHECKLIB ErrorMessage {
+ std::size_t cppcheckId;
std::string errorId;
void setFileName(const std::string &s);
const std::string &getFileName() const {
@@ -48,17 +49,18 @@ public:
};
struct CPPCHECKLIB Suppression {
- Suppression() : lineNumber(NO_LINE), matched(false) {}
+ Suppression() : lineNumber(NO_LINE), cppcheckId(0), matched(false) {}
Suppression(const Suppression &other) {
*this = other;
}
- Suppression(const std::string &id, const std::string &file, int line=NO_LINE) : errorId(id), fileName(file), lineNumber(line), matched(false) {}
+ Suppression(const std::string &id, const std::string &file, int line=NO_LINE) : errorId(id), fileName(file), lineNumber(line), cppcheckId(0), matched(false) {}
Suppression & operator=(const Suppression &other) {
errorId = other.errorId;
fileName = other.fileName;
lineNumber = other.lineNumber;
symbolName = other.symbolName;
+ cppcheckId = other.cppcheckId;
matched = other.matched;
return *this;
}
@@ -72,6 +74,8 @@ public:
return fileName < other.fileName;
if (symbolName != other.symbolName)
return symbolName < other.symbolName;
+ if (cppcheckId != other.cppcheckId)
+ return cppcheckId < other.cppcheckId;
return false;
}
@@ -96,6 +100,7 @@ public:
std::string fileName;
int lineNumber;
std::string symbolName;
+ std::size_t cppcheckId;
bool matched;
enum { NO_LINE = -1 };
From 60399b9321e37d39ec095e8c983f91a86adc275a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Wed, 15 Jul 2020 13:06:17 +0200
Subject: [PATCH 06/58] cppcheck-errors.rng: Update xml schema, cppcheck-id
attribute was added
---
cppcheck-errors.rng | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/cppcheck-errors.rng b/cppcheck-errors.rng
index 57ce4224c..6446d98ce 100644
--- a/cppcheck-errors.rng
+++ b/cppcheck-errors.rng
@@ -24,6 +24,13 @@
+
+
+
+ 1
+
+
+
From 4f83b3618c7c9012ffe61816e975537fa2852941 Mon Sep 17 00:00:00 2001
From: orbitcowboy
Date: Wed, 15 Jul 2020 13:37:01 +0200
Subject: [PATCH 07/58] wxwidgets.cfg: Added support for more interfaces
---
cfg/wxwidgets.cfg | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/cfg/wxwidgets.cfg b/cfg/wxwidgets.cfg
index 6bdb96c43..ef4014114 100644
--- a/cfg/wxwidgets.cfg
+++ b/cfg/wxwidgets.cfg
@@ -13428,6 +13428,13 @@ wxItemKind kind = wxITEM_NORMAL) -->
+
+
+ false
+
+
+
+
false
From bdb08232d7ab6c7713e89c55d1e8e8caaaaccb98 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Wed, 15 Jul 2020 20:55:04 +0200
Subject: [PATCH 08/58] GUI: Fix suppression of multiple cppcheck ids
---
gui/resultstree.cpp | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/gui/resultstree.cpp b/gui/resultstree.cpp
index 7233a4d15..a9202bde0 100644
--- a/gui/resultstree.cpp
+++ b/gui/resultstree.cpp
@@ -1047,17 +1047,24 @@ void ResultsTree::suppressCppcheckID()
{
if (!mSelectionModel)
return;
- ProjectFile *projectFile = ProjectFile::getActiveProject();
+
+ // Extract selected warnings
+ QSet selectedWarnings;
foreach (QModelIndex index, mSelectionModel->selectedRows()) {
QStandardItem *item = mModel.itemFromIndex(index);
if (!item->parent())
continue;
while (item->parent()->parent())
item = item->parent();
+ selectedWarnings.insert(item);
+ }
+
+ ProjectFile *projectFile = ProjectFile::getActiveProject();
+ for (QStandardItem *item: selectedWarnings) {
+ QStandardItem *fileItem = item->parent();
const QVariantMap data = item->data().toMap();
if (projectFile && data.contains("cppcheckId"))
projectFile->suppressCppcheckId(data["cppcheckId"].toULongLong());
- QStandardItem *fileItem = item->parent();
fileItem->removeRow(item->row());
if (fileItem->rowCount() == 0)
mModel.removeRow(fileItem->row());
From a23d1ca6925ebe07bb30b4d421d69f21b4d430c2 Mon Sep 17 00:00:00 2001
From: Simon Large
Date: Wed, 15 Jul 2020 21:49:25 +0200
Subject: [PATCH 09/58] manual.md: Reformatting lists
---
man/manual.md | 36 ++++++++++++++++++++----------------
1 file changed, 20 insertions(+), 16 deletions(-)
diff --git a/man/manual.md b/man/manual.md
index 63fbe9df0..98a2c126d 100644
--- a/man/manual.md
+++ b/man/manual.md
@@ -175,8 +175,9 @@ As you can see Cppcheck has instantiated `a` until `a<101>` was reached
and then it bails out.
To limit template recursion you can;
- * add template specialisation
- * configure cppcheck (in the GUI project file dialog)
+
+- add template specialisation
+- configure cppcheck (in the GUI project file dialog)
Example code with template specialisation:
@@ -348,14 +349,15 @@ Use `--std` on the command line to specify a C/C++ standard.
Cppcheck assumes that the code is compatible with the latest C/C++ standard but you can override this.
The available options are:
- * c89: C code is C89 compatible
- * c99: C code is C99 compatible
- * c11: C code is C11 compatible (default)
- * c++03: C++ code is C++03 compatible
- * c++11: C++ code is C++11 compatible
- * c++14: C++ code is C++14 compatible
- * c++17: C++ code is C++17 compatible
- * c++20: C++ code is C++20 compatible (default)
+
+- c89: C code is C89 compatible
+- c99: C code is C99 compatible
+- c11: C code is C11 compatible (default)
+- c++03: C++ code is C++03 compatible
+- c++11: C++ code is C++11 compatible
+- c++14: C++ code is C++14 compatible
+- c++17: C++ code is C++17 compatible
+- c++20: C++ code is C++20 compatible (default)
# Suppressions
@@ -873,10 +875,11 @@ This analysis is "soundy"; it should diagnose most bugs reported in CVEs and fro
You have to expect false alarms. However Cppcheck tries to limit false alarms. The purpose of the data flow analysis is to limit false alarms.
Some possible use cases;
- * you are writing new code and want to ensure it is safe.
- * you are reviewing code and want to get hints about possible UB.
- * you need extra help troubleshooting a weird bug.
- * you want to check if a release candidate is safe.
+
+- you are writing new code and want to ensure it is safe.
+- you are reviewing code and want to get hints about possible UB.
+- you need extra help troubleshooting a weird bug.
+- you want to check if a release candidate is safe.
The intention is that this will be used primarily in the GUI.
@@ -923,8 +926,9 @@ Cppcheck will warn:
## Adding a contract in the GUI
There are two ways:
- * Open the "Contracts" tab at the bottom of the screen. Find the function in the listbox and double click on it.
- * Right click on a warning and click on "Edit contract.." in the popup menu. This popup menu item is only available if the warning is not inconclusive.
+
+- Open the "Contracts" tab at the bottom of the screen. Find the function in the listbox and double click on it.
+- Right click on a warning and click on "Edit contract.." in the popup menu. This popup menu item is only available if the warning is not inconclusive.
## Incomplete analysis
From dac72beb9e4bd818ef41b47b37c24a9b97388eeb Mon Sep 17 00:00:00 2001
From: Simon Large
Date: Wed, 15 Jul 2020 21:56:27 +0200
Subject: [PATCH 10/58] manual: add css style sheet
---
man/manual.css | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 86 insertions(+)
create mode 100644 man/manual.css
diff --git a/man/manual.css b/man/manual.css
new file mode 100644
index 000000000..06c29597c
--- /dev/null
+++ b/man/manual.css
@@ -0,0 +1,86 @@
+/* stylelint-disable */
+html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}[hidden],template{display:none}
+/* stylelint-enable */
+
+html {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+}
+
+body {
+ background-color: #fff;
+ color: #333;
+ font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;
+ font-size: 16px;
+ font-weight: 400;
+ line-height: 1.5;
+ max-width: 1012px;
+ margin: 20px auto;
+ border: 1px solid #eaecef;
+ padding: 20px 100px;
+ overflow-x: hidden;
+}
+
+a {
+ color: #0366d6;
+ text-decoration: none;
+
+}
+
+a:hover, a:focus {
+ text-decoration: underline;
+
+}
+
+h1, h2, h3, h4, h5, h6 {
+ font-weight: 600;
+ line-height: 1.25;
+ margin-top: 24px;
+ margin-bottom: 16px;
+}
+
+h1 {
+ font-size: 2em;
+}
+
+h2 {
+ font-size: 1.5em;
+}
+
+h3 {
+ font-size: 1.25em;
+}
+
+h4, h5, h6 {
+ font-size: 1.1em;
+}
+
+h1, h2 {
+ padding-bottom: 0.3em;
+ border-bottom: 1px solid #eaecef;
+}
+
+pre {
+ padding: 16px;
+ background-color: #f6f8fa;
+ border-radius: 6px;
+ line-height: 1.45;
+ font-size: 85%;
+ margin-top: 0;
+ margin-bottom: 16px;
+ overflow: auto;
+}
+
+code {
+ font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;
+ background-color: #f6f8fa;
+ font-size: 100%;
+ word-break: normal;
+ white-space: pre;
+}
+
+ol, ul {
+ padding-left: 2em;
+ margin-top: 0;
+ margin-bottom: 16px;
+}
From c02b39a3baca1cc3da6e7cb54764788223ec318c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Wed, 15 Jul 2020 21:57:49 +0200
Subject: [PATCH 11/58] Use manual.css in manual
---
man/buildman.sh | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/man/buildman.sh b/man/buildman.sh
index d7371c03a..f1ff96f31 100755
--- a/man/buildman.sh
+++ b/man/buildman.sh
@@ -2,8 +2,8 @@
# To install required tools in debian:
# sudo apt-get install pandoc texlive-latex-base texlive-fonts-recommended texlive-latex-extra
-pandoc manual.md -o manual.pdf -s --number-sections --toc
-pandoc manual.md -o manual.html -s --number-sections --toc
+pandoc manual.md -o manual.pdf -s --number-sections --toc --css manual.css
+pandoc manual.md -o manual.html -s --number-sections --toc --css manual.css
pandoc reference-cfg-format.md -o reference-cfg-format.pdf -s --number-sections --toc
pandoc reference-cfg-format.md -o reference-cfg-format.html -s --number-sections --toc
From 118e9eb3e2783b1d9682a91247f9c86d83ad14bf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Thu, 16 Jul 2020 15:27:07 +0200
Subject: [PATCH 12/58] Better handling of CppcheckID suppressions
---
lib/importproject.cpp | 22 +++++++++++++++++-----
lib/suppressions.cpp | 10 +++++++---
2 files changed, 24 insertions(+), 8 deletions(-)
diff --git a/lib/importproject.cpp b/lib/importproject.cpp
index 384ad4682..727c6704f 100644
--- a/lib/importproject.cpp
+++ b/lib/importproject.cpp
@@ -1033,7 +1033,7 @@ bool ImportProject::importCppcheckGuiProject(std::istream &istr, Settings *setti
const std::string &path = mPath;
std::list paths;
- std::list suppressions;
+ std::list suppressions;
Settings temp;
guiProject.analyzeAllVsConfigs.clear();
@@ -1072,8 +1072,20 @@ bool ImportProject::importCppcheckGuiProject(std::istream &istr, Settings *setti
guiProject.excludedPaths = readXmlStringList(node, "", CppcheckXml::IgnorePathName, CppcheckXml::IgnorePathNameAttrib);
else if (strcmp(node->Name(), CppcheckXml::LibrariesElementName) == 0)
guiProject.libraries = readXmlStringList(node, "", CppcheckXml::LibraryElementName, nullptr);
- else if (strcmp(node->Name(), CppcheckXml::SuppressionsElementName) == 0)
- suppressions = readXmlStringList(node, "", CppcheckXml::SuppressionElementName, nullptr);
+ else if (strcmp(node->Name(), CppcheckXml::SuppressionsElementName) == 0) {
+ for (const tinyxml2::XMLElement *child = node->FirstChildElement(); child; child = child->NextSiblingElement()) {
+ if (strcmp(child->Name(), CppcheckXml::SuppressionsElementName) != 0)
+ continue;
+ auto read = [](const char *s, const char *def) { return s ? s : def; };
+ Suppressions::Suppression s;
+ s.errorId = read(child->GetText(), "");
+ s.fileName = read(child->Attribute("fileName"), "");
+ s.lineNumber = child->IntAttribute("lineNumber", Suppressions::Suppression::NO_LINE);
+ s.symbolName = read(child->Attribute("symbolName"), "");
+ std::istringstream(read(child->Attribute("cppcheck-id"), "0")) >> s.cppcheckId;
+ suppressions.push_back(s);
+ }
+ }
else if (strcmp(node->Name(), CppcheckXml::VSConfigurationElementName) == 0)
guiProject.checkVsConfigs = readXmlStringList(node, "", CppcheckXml::VSConfigurationName, nullptr);
else if (strcmp(node->Name(), CppcheckXml::PlatformElementName) == 0)
@@ -1130,8 +1142,8 @@ bool ImportProject::importCppcheckGuiProject(std::istream &istr, Settings *setti
for (const std::string &p : paths)
guiProject.pathNames.push_back(p);
- for (const std::string &supp : suppressions)
- settings->nomsg.addSuppressionLine(supp);
+ for (const Suppressions::Suppression &supp : suppressions)
+ settings->nomsg.addSuppression(supp);
settings->checkHeaders = temp.checkHeaders;
settings->checkUnusedTemplates = temp.checkUnusedTemplates;
settings->maxCtuDepth = temp.maxCtuDepth;
diff --git a/lib/suppressions.cpp b/lib/suppressions.cpp
index 93bc71531..e436a7413 100644
--- a/lib/suppressions.cpp
+++ b/lib/suppressions.cpp
@@ -98,8 +98,10 @@ std::string Suppressions::parseXmlFile(const char *filename)
s.lineNumber = std::atoi(text);
else if (std::strcmp(e2->Name(), "symbolName") == 0)
s.symbolName = text;
+ else if (*text && std::strcmp(e2->Name(), "cppcheckId") == 0)
+ std::istringstream(text) >> s.cppcheckId;
else
- return "Unknown suppression element <" + std::string(e2->Name()) + ">, expected ///";
+ return "Unknown suppression element <" + std::string(e2->Name()) + ">, expected ////";
}
const std::string err = addSuppression(s);
@@ -317,6 +319,8 @@ std::string Suppressions::Suppression::getText() const
ret += " lineNumber=" + MathLib::toString(lineNumber);
if (!symbolName.empty())
ret += " symbolName=" + symbolName;
+ if (cppcheckId > 0)
+ ret += " cppcheckId=" + MathLib::toString(cppcheckId);
if (ret.compare(0,1," ")==0)
return ret.substr(1);
return ret;
@@ -360,13 +364,13 @@ void Suppressions::dump(std::ostream & out) const
out << " lineNumber=\"" << suppression.lineNumber << '"';
if (!suppression.symbolName.empty())
out << " symbolName=\"" << ErrorLogger::toxml(suppression.symbolName) << '\"';
+ if (suppression.cppcheckId > 0)
+ out << " cppcheckId=\"" << suppression.cppcheckId << '\"';
out << " />" << std::endl;
}
out << " " << std::endl;
}
-#include
-
std::list Suppressions::getUnmatchedLocalSuppressions(const std::string &file, const bool unusedFunctionChecking) const
{
std::list result;
From 9edbec8594c0d841d488552f35b4b80062abefb9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Thu, 16 Jul 2020 16:36:11 +0200
Subject: [PATCH 13/58] astyle formatting
---
lib/importproject.cpp | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/lib/importproject.cpp b/lib/importproject.cpp
index 727c6704f..ad498d21d 100644
--- a/lib/importproject.cpp
+++ b/lib/importproject.cpp
@@ -1076,7 +1076,9 @@ bool ImportProject::importCppcheckGuiProject(std::istream &istr, Settings *setti
for (const tinyxml2::XMLElement *child = node->FirstChildElement(); child; child = child->NextSiblingElement()) {
if (strcmp(child->Name(), CppcheckXml::SuppressionsElementName) != 0)
continue;
- auto read = [](const char *s, const char *def) { return s ? s : def; };
+ auto read = [](const char *s, const char *def) {
+ return s ? s : def;
+ };
Suppressions::Suppression s;
s.errorId = read(child->GetText(), "");
s.fileName = read(child->Attribute("fileName"), "");
@@ -1085,8 +1087,7 @@ bool ImportProject::importCppcheckGuiProject(std::istream &istr, Settings *setti
std::istringstream(read(child->Attribute("cppcheck-id"), "0")) >> s.cppcheckId;
suppressions.push_back(s);
}
- }
- else if (strcmp(node->Name(), CppcheckXml::VSConfigurationElementName) == 0)
+ } else if (strcmp(node->Name(), CppcheckXml::VSConfigurationElementName) == 0)
guiProject.checkVsConfigs = readXmlStringList(node, "", CppcheckXml::VSConfigurationName, nullptr);
else if (strcmp(node->Name(), CppcheckXml::PlatformElementName) == 0)
guiProject.platform = node->GetText();
From bec78a096004dd5f0f5936dbf46d3621616e5c1b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Thu, 16 Jul 2020 16:36:55 +0200
Subject: [PATCH 14/58] GUI: Adding suppression with cppcheckId; include all
relevant fields
---
gui/projectfile.cpp | 15 +++++----------
gui/projectfile.h | 6 +++---
gui/resultstree.cpp | 15 +++++++++++++--
3 files changed, 21 insertions(+), 15 deletions(-)
diff --git a/gui/projectfile.cpp b/gui/projectfile.cpp
index fce386ee8..60872dac3 100644
--- a/gui/projectfile.cpp
+++ b/gui/projectfile.cpp
@@ -719,6 +719,11 @@ void ProjectFile::setSuppressions(const QList &suppre
mSuppressions = suppressions;
}
+void ProjectFile::addSuppression(const Suppressions::Suppression &suppression)
+{
+ mSuppressions.append(suppression);
+}
+
void ProjectFile::setAddons(const QStringList &addons)
{
mAddons = addons;
@@ -1027,13 +1032,3 @@ QString ProjectFile::getAddonFilePath(QString filesDir, const QString &addon)
return QString();
}
-
-void ProjectFile::suppressCppcheckId(std::size_t cppcheckId)
-{
- if (cppcheckId > 0) {
- Suppressions::Suppression s;
- s.cppcheckId = cppcheckId;
- mSuppressions.append(s);
- write();
- }
-}
diff --git a/gui/projectfile.h b/gui/projectfile.h
index fc1effc00..1a92f994e 100644
--- a/gui/projectfile.h
+++ b/gui/projectfile.h
@@ -55,9 +55,6 @@ public:
mActiveProject = this;
}
- /** Suppress warning with Cppcheck-ID */
- void suppressCppcheckId(std::size_t cppcheckId);
-
/**
* @brief Read the project file.
* @param filename Filename (can be also given to constructor).
@@ -312,6 +309,9 @@ public:
*/
void setSuppressions(const QList &suppressions);
+ /** Add suppression */
+ void addSuppression(const Suppressions::Suppression &suppression);
+
/**
* @brief Set list of addons.
* @param addons List of addons.
diff --git a/gui/resultstree.cpp b/gui/resultstree.cpp
index a9202bde0..27274c176 100644
--- a/gui/resultstree.cpp
+++ b/gui/resultstree.cpp
@@ -1059,16 +1059,27 @@ void ResultsTree::suppressCppcheckID()
selectedWarnings.insert(item);
}
+ bool changed = false;
ProjectFile *projectFile = ProjectFile::getActiveProject();
for (QStandardItem *item: selectedWarnings) {
QStandardItem *fileItem = item->parent();
const QVariantMap data = item->data().toMap();
- if (projectFile && data.contains("cppcheckId"))
- projectFile->suppressCppcheckId(data["cppcheckId"].toULongLong());
+ if (projectFile && data.contains("cppcheckId")) {
+ Suppressions::Suppression suppression;
+ suppression.cppcheckId = data["cppcheckId"].toULongLong();
+ suppression.errorId = data["id"].toString().toStdString();
+ suppression.fileName = data["file"].toString().toStdString();
+ suppression.lineNumber = data["line"].toInt();
+ projectFile->addSuppression(suppression);
+ changed = true;
+ }
fileItem->removeRow(item->row());
if (fileItem->rowCount() == 0)
mModel.removeRow(fileItem->row());
}
+
+ if (changed)
+ projectFile->write();
}
void ResultsTree::openContainingFolder()
From 0632f864491bd1c39ae70479d00c095a84e3375d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Thu, 16 Jul 2020 16:38:22 +0200
Subject: [PATCH 15/58] Bug hunting; avoid bailout false positives when
constructor is called
---
lib/bughuntingchecks.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/bughuntingchecks.cpp b/lib/bughuntingchecks.cpp
index 46879ec04..454a50bc5 100644
--- a/lib/bughuntingchecks.cpp
+++ b/lib/bughuntingchecks.cpp
@@ -248,7 +248,7 @@ static void uninit(const Token *tok, const ExprEngine::Value &value, ExprEngine:
if (!var->isLocal() || var->isStatic())
return;
}
- if (var && (Token::Match(var->nameToken(), "%name% [=:]") || Token::Match(var->nameToken(), "%varid% ; %varid% =", var->declarationId())))
+ if (var && (Token::Match(var->nameToken(), "%name% [=:({)]") || Token::Match(var->nameToken(), "%varid% ; %varid% =", var->declarationId())))
return;
if (var && var->nameToken() == tok)
return;
From b249d9be313dec9fcb7fe58f0dfb1413a39fb7c3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Thu, 16 Jul 2020 20:19:36 +0200
Subject: [PATCH 16/58] GUI: Refactor tags
---
gui/mainwindow.cpp | 4 ----
gui/resultstree.cpp | 9 +++++----
gui/resultstree.h | 6 ------
gui/resultsview.h | 4 ----
4 files changed, 5 insertions(+), 18 deletions(-)
diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp
index e721b605c..eab66d82f 100644
--- a/gui/mainwindow.cpp
+++ b/gui/mainwindow.cpp
@@ -1524,8 +1524,6 @@ QString MainWindow::getLastResults() const
bool MainWindow::loadLastResults()
{
- if (mProjectFile)
- mUI.mResults->setTags(mProjectFile->getTags());
const QString &lastResults = getLastResults();
if (lastResults.isEmpty())
return false;
@@ -1548,7 +1546,6 @@ void MainWindow::analyzeProject(const ProjectFile *projectFile, const bool check
QDir::setCurrent(inf.absolutePath());
mThread->setAddonsAndTools(projectFile->getAddonsAndTools());
- mUI.mResults->setTags(projectFile->getTags());
// If the root path is not given or is not "current dir", use project
// file's location directory as root path
@@ -1652,7 +1649,6 @@ void MainWindow::closeProjectFile()
delete mProjectFile;
mProjectFile = nullptr;
mUI.mResults->clear(true);
- mUI.mResults->setTags(QStringList());
enableProjectActions(false);
enableProjectOpenActions(true);
formatAndSetTitle();
diff --git a/gui/resultstree.cpp b/gui/resultstree.cpp
index 27274c176..48e481b4f 100644
--- a/gui/resultstree.cpp
+++ b/gui/resultstree.cpp
@@ -50,8 +50,8 @@
#include "xmlreportv2.h"
// These must match column headers given in ResultsTree::translate()
-static const unsigned int COLUMN_SINCE_DATE = 6;
-static const unsigned int COLUMN_TAGS = 7;
+static const int COLUMN_SINCE_DATE = 6;
+static const int COLUMN_TAGS = 7;
static QString getFunction(QStandardItem *item)
{
@@ -673,7 +673,8 @@ void ResultsTree::contextMenuEvent(QContextMenuEvent * e)
connect(suppressCppcheckID, &QAction::triggered, this, &ResultsTree::suppressCppcheckID);
connect(opencontainingfolder, SIGNAL(triggered()), this, SLOT(openContainingFolder()));
- if (!mTags.isEmpty()) {
+ const ProjectFile *currentProject = ProjectFile::getActiveProject();
+ if (currentProject && !currentProject->getTags().isEmpty()) {
menu.addSeparator();
QMenu *tagMenu = menu.addMenu(tr("Tag"));
{
@@ -684,7 +685,7 @@ void ResultsTree::contextMenuEvent(QContextMenuEvent * e)
});
}
- foreach (const QString tagstr, mTags) {
+ foreach (const QString tagstr, currentProject->getTags()) {
QAction *action = new QAction(tagstr, tagMenu);
tagMenu->addAction(action);
connect(action, &QAction::triggered, [=]() {
diff --git a/gui/resultstree.h b/gui/resultstree.h
index 349ea855f..cc7dacd35 100644
--- a/gui/resultstree.h
+++ b/gui/resultstree.h
@@ -52,10 +52,6 @@ public:
virtual ~ResultsTree();
void initialize(QSettings *settings, ApplicationList *list, ThreadHandler *checkThreadHandler);
- void setTags(const QStringList &tags) {
- mTags = tags;
- }
-
/**
* @brief Add a new item to the tree
*
@@ -532,8 +528,6 @@ private:
/** @brief Convert GUI error item into data error item */
void readErrorItem(const QStandardItem *error, ErrorItem *item) const;
- QStringList mTags;
-
QStringList mHiddenMessageId;
QItemSelectionModel *mSelectionModel;
diff --git a/gui/resultsview.h b/gui/resultsview.h
index bd623b836..15f98e9cb 100644
--- a/gui/resultsview.h
+++ b/gui/resultsview.h
@@ -50,10 +50,6 @@ public:
virtual ~ResultsView();
ResultsView &operator=(const ResultsView &) = delete;
- void setTags(const QStringList &tags) {
- mUI.mTree->setTags(tags);
- }
-
void setAddedContracts(const QStringList &addedContracts);
/**
From 9af288e1dde6647ee9fea0a393d54e719007f343 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Thu, 16 Jul 2020 23:03:54 +0200
Subject: [PATCH 17/58] Fixed #9724 (GUI: The tags do not work anymore)
---
gui/mainwindow.cpp | 8 --
gui/mainwindow.h | 3 -
gui/projectfile.cpp | 76 ++++++++++++++++-
gui/projectfile.h | 19 ++++-
gui/resultstree.cpp | 203 ++++++++++++++++++++++++--------------------
gui/resultsview.cpp | 1 -
gui/resultsview.h | 5 --
lib/importproject.h | 4 +
8 files changed, 210 insertions(+), 109 deletions(-)
diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp
index eab66d82f..d98ea4b6e 100644
--- a/gui/mainwindow.cpp
+++ b/gui/mainwindow.cpp
@@ -141,7 +141,6 @@ MainWindow::MainWindow(TranslationHandler* th, QSettings* settings) :
connect(mUI.mResults, &ResultsView::gotResults, this, &MainWindow::resultsAdded);
connect(mUI.mResults, &ResultsView::resultsHidden, mUI.mActionShowHidden, &QAction::setEnabled);
connect(mUI.mResults, &ResultsView::checkSelected, this, &MainWindow::performSelectedFilesCheck);
- connect(mUI.mResults, &ResultsView::tagged, this, &MainWindow::tagged);
connect(mUI.mResults, &ResultsView::suppressIds, this, &MainWindow::suppressIds);
connect(mUI.mResults, &ResultsView::editFunctionContract, this, &MainWindow::editFunctionContract);
connect(mUI.mMenuView, &QMenu::aboutToShow, this, &MainWindow::aboutToShowViewMenu);
@@ -1810,13 +1809,6 @@ void MainWindow::selectPlatform()
}
}
-void MainWindow::tagged()
-{
- const QString &lastResults = getLastResults();
- if (!lastResults.isEmpty())
- mUI.mResults->save(lastResults, Report::XMLV2);
-}
-
void MainWindow::suppressIds(QStringList ids)
{
if (!mProjectFile)
diff --git a/gui/mainwindow.h b/gui/mainwindow.h
index d5ccf2b1c..2ea082d83 100644
--- a/gui/mainwindow.h
+++ b/gui/mainwindow.h
@@ -221,9 +221,6 @@ protected slots:
/** @brief Selects the platform as analyzed platform. */
void selectPlatform();
- /** Some results were tagged */
- void tagged();
-
/** Suppress error ids */
void suppressIds(QStringList ids);
diff --git a/gui/projectfile.cpp b/gui/projectfile.cpp
index 60872dac3..8745cf345 100644
--- a/gui/projectfile.cpp
+++ b/gui/projectfile.cpp
@@ -72,6 +72,8 @@ void ProjectFile::clear()
mCheckUnknownFunctionReturn.clear();
safeChecks.clear();
mVsConfigurations.clear();
+ mTags.clear();
+ mWarningTags.clear();
}
bool ProjectFile::read(const QString &filename)
@@ -188,6 +190,9 @@ bool ProjectFile::read(const QString &filename)
if (xmlReader.name() == CppcheckXml::TagsElementName)
readStringList(mTags, xmlReader, CppcheckXml::TagElementName);
+ if (xmlReader.name() == CppcheckXml::TagWarningsElementName)
+ readTagWarnings(xmlReader, xmlReader.attributes().value(QString(), CppcheckXml::TagAttributeName).toString());
+
if (xmlReader.name() == CppcheckXml::MaxCtuDepthElementName)
mMaxCtuDepth = readInt(xmlReader, mMaxCtuDepth);
@@ -636,6 +641,41 @@ void ProjectFile::readSuppressions(QXmlStreamReader &reader)
}
+void ProjectFile::readTagWarnings(QXmlStreamReader &reader, QString tag)
+{
+ QXmlStreamReader::TokenType type;
+ do {
+ type = reader.readNext();
+ switch (type) {
+ case QXmlStreamReader::StartElement:
+ // Read library-elements
+ if (reader.name().toString() == CppcheckXml::WarningElementName) {
+ std::size_t cppcheckId = reader.attributes().value(QString(), CppcheckXml::CppcheckIdAttributeName).toULongLong();
+ mWarningTags[cppcheckId] = tag;
+ }
+ break;
+
+ case QXmlStreamReader::EndElement:
+ if (reader.name().toString() != CppcheckXml::WarningElementName)
+ return;
+ break;
+
+ // Not handled
+ case QXmlStreamReader::NoToken:
+ case QXmlStreamReader::Invalid:
+ case QXmlStreamReader::StartDocument:
+ case QXmlStreamReader::EndDocument:
+ case QXmlStreamReader::Characters:
+ case QXmlStreamReader::Comment:
+ case QXmlStreamReader::DTD:
+ case QXmlStreamReader::EntityReference:
+ case QXmlStreamReader::ProcessingInstruction:
+ break;
+ }
+ } while (true);
+}
+
+
void ProjectFile::readStringList(QStringList &stringlist, QXmlStreamReader &reader, const char elementname[])
{
QXmlStreamReader::TokenType type;
@@ -734,6 +774,20 @@ void ProjectFile::setVSConfigurations(const QStringList &vsConfigs)
mVsConfigurations = vsConfigs;
}
+void ProjectFile::setWarningTags(std::size_t cppcheckId, QString tag)
+{
+ if (tag.isEmpty())
+ mWarningTags.erase(cppcheckId);
+ else if (cppcheckId > 0)
+ mWarningTags[cppcheckId] = tag;
+}
+
+QString ProjectFile::getWarningTags(std::size_t cppcheckId) const
+{
+ auto it = mWarningTags.find(cppcheckId);
+ return (it != mWarningTags.end()) ? it->second : QString();
+}
+
bool ProjectFile::write(const QString &filename)
{
if (!filename.isEmpty())
@@ -883,7 +937,7 @@ bool ProjectFile::write(const QString &filename)
if (!suppression.symbolName.empty())
xmlWriter.writeAttribute("symbolName", QString::fromStdString(suppression.symbolName));
if (suppression.cppcheckId > 0)
- xmlWriter.writeAttribute("cppcheck-id", QString::number(suppression.cppcheckId));
+ xmlWriter.writeAttribute(CppcheckXml::CppcheckIdAttributeName, QString::number(suppression.cppcheckId));
if (!suppression.errorId.empty())
xmlWriter.writeCharacters(QString::fromStdString(suppression.errorId));
xmlWriter.writeEndElement();
@@ -914,6 +968,26 @@ bool ProjectFile::write(const QString &filename)
CppcheckXml::ToolElementName);
writeStringList(xmlWriter, mTags, CppcheckXml::TagsElementName, CppcheckXml::TagElementName);
+ if (!mWarningTags.empty()) {
+ QStringList tags;
+ for (const auto wt: mWarningTags) {
+ if (!tags.contains(wt.second))
+ tags.append(wt.second);
+ }
+ for (const QString &tag: tags) {
+ xmlWriter.writeStartElement(CppcheckXml::TagWarningsElementName);
+ xmlWriter.writeAttribute(CppcheckXml::TagAttributeName, tag);
+ QStringList warnings;
+ for (const auto wt: mWarningTags) {
+ if (wt.second == tag) {
+ xmlWriter.writeStartElement(CppcheckXml::WarningElementName);
+ xmlWriter.writeAttribute(CppcheckXml::CppcheckIdAttributeName, QString::number(wt.first));
+ xmlWriter.writeEndElement();
+ }
+ }
+ xmlWriter.writeEndElement();
+ }
+ }
xmlWriter.writeEndDocument();
file.close();
diff --git a/gui/projectfile.h b/gui/projectfile.h
index 1a92f994e..1d372ed35 100644
--- a/gui/projectfile.h
+++ b/gui/projectfile.h
@@ -331,6 +331,12 @@ public:
mTags = tags;
}
+ /** Set tags for a warning */
+ void setWarningTags(std::size_t cppcheckId, QString tags);
+
+ /** Get tags for a warning */
+ QString getWarningTags(std::size_t cppcheckId) const;
+
/**
* @brief Write project file (to disk).
* @param filename Filename to use.
@@ -437,6 +443,12 @@ protected:
*/
void readSuppressions(QXmlStreamReader &reader);
+ /**
+ * @brief Read tag warnings, what warnings are tagged with a specific tag
+ * @param reader XML stream reader.
+ */
+ void readTagWarnings(QXmlStreamReader &reader, QString tag);
+
/**
* @brief Read string list
* @param stringlist destination string list
@@ -552,10 +564,15 @@ private:
bool mClangTidy;
/**
- * @brief Warning tags
+ * @brief Tags
*/
QStringList mTags;
+ /**
+ * @brief Warning tags
+ */
+ std::map mWarningTags;
+
/** Max CTU depth */
int mMaxCtuDepth;
diff --git a/gui/resultstree.cpp b/gui/resultstree.cpp
index 48e481b4f..392fc082e 100644
--- a/gui/resultstree.cpp
+++ b/gui/resultstree.cpp
@@ -49,6 +49,23 @@
#include "path.h"
#include "xmlreportv2.h"
+static const char COLUMN[] = "column";
+static const char CPPCHECKID[] = "cppcheckId";
+static const char CWE[] = "cwe";
+static const char ERRORID[] = "id";
+static const char FILENAME[] = "file";
+static const char FILE0[] = "file0";
+static const char FUNCTION[] = "function";
+static const char HIDE[] = "hide";
+static const char INCOMPLETE[] = "incomplete";
+static const char INCONCLUSIVE[] = "inconclusive";
+static const char LINE[] = "line";
+static const char MESSAGE[] = "message";
+static const char SEVERITY[] = "severity";
+static const char SINCEDATE[] = "sinceDate";
+static const char SUMMARY[] = "summary";
+static const char TAGS[] = "tags";
+
// These must match column headers given in ResultsTree::translate()
static const int COLUMN_SINCE_DATE = 6;
static const int COLUMN_TAGS = 7;
@@ -175,7 +192,9 @@ bool ResultsTree::addErrorItem(const ErrorItem &item)
line.message = item.message;
line.severity = item.severity;
line.sinceDate = item.sinceDate;
- line.tags = item.tags;
+ if (const ProjectFile *activeProject = ProjectFile::getActiveProject()) {
+ line.tags = activeProject->getWarningTags(item.cppcheckId);
+ }
//Create the base item for the error and ensure it has a proper
//file item as a parent
QStandardItem* fileItem = ensureFileItem(loc.file, item.file0, hide);
@@ -190,22 +209,22 @@ bool ResultsTree::addErrorItem(const ErrorItem &item)
//Add user data to that item
QMap data;
- data["severity"] = ShowTypes::SeverityToShowType(item.severity);
- data["summary"] = item.summary;
- data["message"] = item.message;
- data["file"] = loc.file;
- data["line"] = loc.line;
- data["column"] = loc.column;
- data["id"] = item.errorId;
- data["incomplete"] = item.incomplete;
- data["cwe"] = item.cwe;
- data["cppcheckId"] = item.cppcheckId;
- data["inconclusive"] = item.inconclusive;
- data["file0"] = stripPath(item.file0, true);
- data["function"] = item.function;
- data["sinceDate"] = item.sinceDate;
- data["tags"] = item.tags;
- data["hide"] = hide;
+ data[SEVERITY] = ShowTypes::SeverityToShowType(item.severity);
+ data[SUMMARY] = item.summary;
+ data[MESSAGE] = item.message;
+ data[FILENAME] = loc.file;
+ data[LINE] = loc.line;
+ data[COLUMN] = loc.column;
+ data[ERRORID] = item.errorId;
+ data[INCOMPLETE] = item.incomplete;
+ data[CWE] = item.cwe;
+ data[CPPCHECKID] = item.cppcheckId;
+ data[INCONCLUSIVE] = item.inconclusive;
+ data[FILE0] = stripPath(item.file0, true);
+ data[FUNCTION] = item.function;
+ data[SINCEDATE] = item.sinceDate;
+ data[TAGS] = line.tags;
+ data[HIDE] = hide;
stditem->setData(QVariant(data));
//Add backtrace files as children
@@ -226,17 +245,17 @@ bool ResultsTree::addErrorItem(const ErrorItem &item)
// Add user data to that item
QMap child_data;
- child_data["severity"] = ShowTypes::SeverityToShowType(line.severity);
- child_data["summary"] = line.summary;
- child_data["message"] = line.message;
- child_data["file"] = e.file;
- child_data["line"] = e.line;
- child_data["column"] = e.column;
- child_data["id"] = line.errorId;
- child_data["incomplete"] = line.incomplete;
- child_data["cwe"] = line.cwe;
- child_data["cppcheckId"] = line.cppcheckId;
- child_data["inconclusive"] = line.inconclusive;
+ child_data[SEVERITY] = ShowTypes::SeverityToShowType(line.severity);
+ child_data[SUMMARY] = line.summary;
+ child_data[MESSAGE] = line.message;
+ child_data[FILENAME] = e.file;
+ child_data[LINE] = e.line;
+ child_data[COLUMN] = e.column;
+ child_data[ERRORID] = line.errorId;
+ child_data[INCOMPLETE] = line.incomplete;
+ child_data[CWE] = line.cwe;
+ child_data[CPPCHECKID] = line.cppcheckId;
+ child_data[INCONCLUSIVE] = line.inconclusive;
child_item->setData(QVariant(child_data));
}
}
@@ -363,8 +382,8 @@ void ResultsTree::clear(const QString &filename)
continue;
QVariantMap data = fileItem->data().toMap();
- if (stripped == data["file"].toString() ||
- filename == data["file0"].toString()) {
+ if (stripped == data[FILENAME].toString() ||
+ filename == data[FILE0].toString()) {
mModel.removeRow(i);
break;
}
@@ -380,7 +399,7 @@ void ResultsTree::clearRecheckFile(const QString &filename)
QString actualfile((!mCheckPath.isEmpty() && filename.startsWith(mCheckPath)) ? filename.mid(mCheckPath.length() + 1) : filename);
QVariantMap data = fileItem->data().toMap();
- QString storedfile = data["file"].toString();
+ QString storedfile = data[FILENAME].toString();
storedfile = ((!mCheckPath.isEmpty() && storedfile.startsWith(mCheckPath)) ? storedfile.mid(mCheckPath.length() + 1) : storedfile);
if (actualfile == storedfile) {
mModel.removeRow(i);
@@ -450,7 +469,7 @@ void ResultsTree::showHiddenResults()
continue;
QVariantMap data = fileItem->data().toMap();
- data["hide"] = false;
+ data[HIDE] = false;
fileItem->setData(QVariant(data));
int errorcount = fileItem->rowCount();
@@ -458,7 +477,7 @@ void ResultsTree::showHiddenResults()
QStandardItem *child = fileItem->child(j, 0);
if (child) {
data = child->data().toMap();
- data["hide"] = false;
+ data[HIDE] = false;
child->setData(QVariant(data));
}
}
@@ -500,21 +519,21 @@ void ResultsTree::refreshTree()
QVariantMap data = userdata.toMap();
//Check if this error should be hidden
- bool hide = (data["hide"].toBool() || !mShowSeverities.isShown(ShowTypes::VariantToShowType(data["severity"])));
+ bool hide = (data[HIDE].toBool() || !mShowSeverities.isShown(ShowTypes::VariantToShowType(data[SEVERITY])));
//If specified, filter on summary, message, filename, and id
if (!hide && !mFilter.isEmpty()) {
- if (!data["summary"].toString().contains(mFilter, Qt::CaseInsensitive) &&
- !data["message"].toString().contains(mFilter, Qt::CaseInsensitive) &&
- !data["file"].toString().contains(mFilter, Qt::CaseInsensitive) &&
- !data["id"].toString().contains(mFilter, Qt::CaseInsensitive)) {
+ if (!data[SUMMARY].toString().contains(mFilter, Qt::CaseInsensitive) &&
+ !data[MESSAGE].toString().contains(mFilter, Qt::CaseInsensitive) &&
+ !data[FILENAME].toString().contains(mFilter, Qt::CaseInsensitive) &&
+ !data[ERRORID].toString().contains(mFilter, Qt::CaseInsensitive)) {
hide = true;
}
}
// Tool filter
if (!hide) {
- if (data["id"].toString().startsWith("clang"))
+ if (data[ERRORID].toString().startsWith("clang"))
hide = !mShowClang;
else
hide = !mShowCppcheck;
@@ -561,8 +580,8 @@ QStandardItem *ResultsTree::ensureFileItem(const QString &fullpath, const QStrin
//Add user data to that item
QMap data;
- data["file"] = fullpath;
- data["file0"] = file0;
+ data[FILENAME] = fullpath;
+ data[FILE0] = file0;
item->setData(QVariant(data));
mModel.appendRow(item);
@@ -753,7 +772,7 @@ void ResultsTree::startApplication(QStandardItem *target, int application)
QVariantMap data = target->data().toMap();
//Replace (file) with filename
- QString file = data["file"].toString();
+ QString file = data[FILENAME].toString();
file = QDir::toNativeSeparators(file);
#ifdef Q_OS_WIN
file.replace(QString("\\"), QString("\\\\"));
@@ -789,11 +808,11 @@ void ResultsTree::startApplication(QStandardItem *target, int application)
QString params = app.getParameters();
params.replace("(file)", file, Qt::CaseInsensitive);
- QVariant line = data["line"];
+ QVariant line = data[LINE];
params.replace("(line)", QString("%1").arg(line.toInt()), Qt::CaseInsensitive);
- params.replace("(message)", data["message"].toString(), Qt::CaseInsensitive);
- params.replace("(severity)", data["severity"].toString(), Qt::CaseInsensitive);
+ params.replace("(message)", data[MESSAGE].toString(), Qt::CaseInsensitive);
+ params.replace("(severity)", data[SEVERITY].toString(), Qt::CaseInsensitive);
QString program = app.getPath();
@@ -889,14 +908,14 @@ void ResultsTree::copy()
QVariantMap data = item->data().toMap();
if (!data.contains("id"))
continue;
- QString inconclusive = data["inconclusive"].toBool() ? ",inconclusive" : "";
- text += '[' + data["file"].toString() + ':' + QString::number(data["line"].toInt())
+ QString inconclusive = data[INCONCLUSIVE].toBool() ? ",inconclusive" : "";
+ text += '[' + data[FILENAME].toString() + ':' + QString::number(data[LINE].toInt())
+ "] ("
- + QString::fromStdString(Severity::toString(ShowTypes::ShowTypeToSeverity((ShowTypes::ShowType)data["severity"].toInt()))) + inconclusive
+ + QString::fromStdString(Severity::toString(ShowTypes::ShowTypeToSeverity((ShowTypes::ShowType)data[SEVERITY].toInt()))) + inconclusive
+ ") "
- + data["message"].toString()
+ + data[MESSAGE].toString()
+ " ["
- + data["id"].toString()
+ + data[ERRORID].toString()
+ "]\n";
}
@@ -914,7 +933,7 @@ void ResultsTree::hideResult()
QStandardItem *item = mModel.itemFromIndex(index);
//Set the "hide" flag for this item
QVariantMap data = item->data().toMap();
- data["hide"] = true;
+ data[HIDE] = true;
item->setData(QVariant(data));
refreshTree();
@@ -934,7 +953,7 @@ void ResultsTree::recheckSelectedFiles()
while (item->parent())
item = item->parent();
QVariantMap data = item->data().toMap();
- QString currentFile = data["file"].toString();
+ QString currentFile = data[FILENAME].toString();
if (!currentFile.isEmpty()) {
QString fileNameWithCheckPath;
QFileInfo curfileInfo(currentFile);
@@ -948,8 +967,8 @@ void ResultsTree::recheckSelectedFiles()
return;
}
if (Path::isHeader(currentFile.toStdString())) {
- if (!data["file0"].toString().isEmpty() && !selectedItems.contains(data["file0"].toString())) {
- selectedItems<<((!mCheckPath.isEmpty() && (data["file0"].toString().indexOf(mCheckPath) != 0)) ? (mCheckPath + "/" + data["file0"].toString()) : data["file0"].toString());
+ if (!data[FILE0].toString().isEmpty() && !selectedItems.contains(data[FILE0].toString())) {
+ selectedItems<<((!mCheckPath.isEmpty() && (data[FILE0].toString().indexOf(mCheckPath) != 0)) ? (mCheckPath + "/" + data[FILE0].toString()) : data[FILE0].toString());
if (!selectedItems.contains(fileNameWithCheckPath))
selectedItems<parent()->child(mContextItem->row(), 0);
QVariantMap data = mContextItem->data().toMap();
- QString messageId = data["id"].toString();
+ QString messageId = data[ERRORID].toString();
mHiddenMessageId.append(messageId);
@@ -994,8 +1013,8 @@ void ResultsTree::hideAllIdResult()
}
QVariantMap userdata = child->data().toMap();
- if (userdata["id"].toString() == messageId) {
- userdata["hide"] = true;
+ if (userdata[ERRORID].toString() == messageId) {
+ userdata[HIDE] = true;
child->setData(QVariant(userdata));
}
}
@@ -1021,7 +1040,7 @@ void ResultsTree::suppressSelectedIds()
QVariantMap data = item->data().toMap();
if (!data.contains("id"))
continue;
- selectedIds << data["id"].toString();
+ selectedIds << data[ERRORID].toString();
}
// delete all errors with selected message Ids
@@ -1030,7 +1049,7 @@ void ResultsTree::suppressSelectedIds()
for (int j = 0; j < file->rowCount();) {
QStandardItem *errorItem = file->child(j, 0);
QVariantMap userdata = errorItem->data().toMap();
- if (selectedIds.contains(userdata["id"].toString())) {
+ if (selectedIds.contains(userdata[ERRORID].toString())) {
file->removeRow(j);
} else {
j++;
@@ -1067,10 +1086,10 @@ void ResultsTree::suppressCppcheckID()
const QVariantMap data = item->data().toMap();
if (projectFile && data.contains("cppcheckId")) {
Suppressions::Suppression suppression;
- suppression.cppcheckId = data["cppcheckId"].toULongLong();
- suppression.errorId = data["id"].toString().toStdString();
- suppression.fileName = data["file"].toString().toStdString();
- suppression.lineNumber = data["line"].toInt();
+ suppression.cppcheckId = data[CPPCHECKID].toULongLong();
+ suppression.errorId = data[ERRORID].toString().toStdString();
+ suppression.fileName = data[FILENAME].toString().toStdString();
+ suppression.lineNumber = data[LINE].toInt();
projectFile->addSuppression(suppression);
changed = true;
}
@@ -1102,18 +1121,22 @@ void ResultsTree::tagSelectedItems(const QString &tag)
if (!mSelectionModel)
return;
bool isTagged = false;
+ ProjectFile *currentProject = ProjectFile::getActiveProject();
foreach (QModelIndex index, mSelectionModel->selectedRows()) {
QStandardItem *item = mModel.itemFromIndex(index);
QVariantMap data = item->data().toMap();
if (data.contains("tags")) {
- data["tags"] = tag;
+ data[TAGS] = tag;
item->setData(QVariant(data));
item->parent()->child(index.row(), COLUMN_TAGS)->setText(tag);
- isTagged = true;
+ if (currentProject && data.contains(CPPCHECKID)) {
+ isTagged = true;
+ currentProject->setWarningTags(data[CPPCHECKID].toULongLong(), tag);
+ }
}
}
if (isTagged)
- emit tagged();
+ currentProject->write();
}
void ResultsTree::context(int application)
@@ -1137,7 +1160,7 @@ QString ResultsTree::getFilePath(QStandardItem *target, bool fullPath)
QString pathStr;
//Replace (file) with filename
- QString file = data["file"].toString();
+ QString file = data[FILENAME].toString();
pathStr = QDir::toNativeSeparators(file);
if (!fullPath) {
QFileInfo fi(pathStr);
@@ -1237,12 +1260,12 @@ void ResultsTree::updateFromOldReport(const QString &filename)
// New error .. set the "sinceDate" property
if (oldErrorIndex >= 0 && !oldErrors[oldErrorIndex].sinceDate.isEmpty()) {
- data["sinceDate"] = oldErrors[oldErrorIndex].sinceDate;
+ data[SINCEDATE] = oldErrors[oldErrorIndex].sinceDate;
error->setData(data);
fileItem->child(j, COLUMN_SINCE_DATE)->setText(oldErrors[oldErrorIndex].sinceDate);
- } else if (oldErrorIndex < 0 || data["sinceDate"].toString().isEmpty()) {
+ } else if (oldErrorIndex < 0 || data[SINCEDATE].toString().isEmpty()) {
const QString sinceDate = QDate::currentDate().toString(Qt::SystemLocaleShortDate);
- data["sinceDate"] = sinceDate;
+ data[SINCEDATE] = sinceDate;
error->setData(data);
fileItem->child(j, COLUMN_SINCE_DATE)->setText(sinceDate);
if (oldErrorIndex < 0)
@@ -1253,7 +1276,7 @@ void ResultsTree::updateFromOldReport(const QString &filename)
continue;
const ErrorItem &oldErrorItem = oldErrors[oldErrorIndex];
- data["tags"] = oldErrorItem.tags;
+ data[TAGS] = oldErrorItem.tags;
error->setData(data);
}
}
@@ -1264,23 +1287,23 @@ void ResultsTree::readErrorItem(const QStandardItem *error, ErrorItem *item) con
// Get error's user data
QVariantMap data = error->data().toMap();
- item->severity = ShowTypes::ShowTypeToSeverity(ShowTypes::VariantToShowType(data["severity"]));
- item->summary = data["summary"].toString();
- item->message = data["message"].toString();
- item->errorId = data["id"].toString();
- item->incomplete = data["incomplete"].toBool();
- item->cwe = data["cwe"].toInt();
- item->cppcheckId = data["cppcheckId"].toULongLong();
- item->inconclusive = data["inconclusive"].toBool();
- item->file0 = data["file0"].toString();
- item->sinceDate = data["sinceDate"].toString();
- item->tags = data["tags"].toString();
+ item->severity = ShowTypes::ShowTypeToSeverity(ShowTypes::VariantToShowType(data[SEVERITY]));
+ item->summary = data[SUMMARY].toString();
+ item->message = data[MESSAGE].toString();
+ item->errorId = data[ERRORID].toString();
+ item->incomplete = data[INCOMPLETE].toBool();
+ item->cwe = data[CWE].toInt();
+ item->cppcheckId = data[CPPCHECKID].toULongLong();
+ item->inconclusive = data[INCONCLUSIVE].toBool();
+ item->file0 = data[FILE0].toString();
+ item->sinceDate = data[SINCEDATE].toString();
+ item->tags = data[TAGS].toString();
if (error->rowCount() == 0) {
QErrorPathItem e;
- e.file = stripPath(data["file"].toString(), true);
- e.line = data["line"].toUInt();
- e.info = data["message"].toString();
+ e.file = stripPath(data[FILENAME].toString(), true);
+ e.line = data[LINE].toInt();
+ e.info = data[MESSAGE].toString();
item->errorPath << e;
}
@@ -1292,9 +1315,9 @@ void ResultsTree::readErrorItem(const QStandardItem *error, ErrorItem *item) con
QVariantMap child_data = child_userdata.toMap();
QErrorPathItem e;
- e.file = stripPath(child_data["file"].toString(), true);
- e.line = child_data["line"].toUInt();
- e.info = child_data["message"].toString();
+ e.file = stripPath(child_data[FILENAME].toString(), true);
+ e.line = child_data[LINE].toUInt();
+ e.info = child_data[MESSAGE].toString();
item->errorPath << e;
}
}
@@ -1362,7 +1385,7 @@ void ResultsTree::refreshFilePaths(QStandardItem *item)
QVariantMap data = userdata.toMap();
//Get list of files
- QString file = data["file"].toString();
+ QString file = data[FILENAME].toString();
//Update this error's text
error->setText(stripPath(file, false));
@@ -1382,7 +1405,7 @@ void ResultsTree::refreshFilePaths(QStandardItem *item)
QVariantMap child_data = child_userdata.toMap();
//Get list of files
- QString child_files = child_data["file"].toString();
+ QString child_files = child_data[FILENAME].toString();
//Update file's path
child->setText(stripPath(child_files, false));
}
diff --git a/gui/resultsview.cpp b/gui/resultsview.cpp
index fcc88d73b..e5cd359fa 100644
--- a/gui/resultsview.cpp
+++ b/gui/resultsview.cpp
@@ -53,7 +53,6 @@ ResultsView::ResultsView(QWidget * parent) :
connect(mUI.mTree, &ResultsTree::resultsHidden, this, &ResultsView::resultsHidden);
connect(mUI.mTree, &ResultsTree::checkSelected, this, &ResultsView::checkSelected);
connect(mUI.mTree, &ResultsTree::treeSelectionChanged, this, &ResultsView::updateDetails);
- connect(mUI.mTree, &ResultsTree::tagged, this, &ResultsView::tagged);
connect(mUI.mTree, &ResultsTree::suppressIds, this, &ResultsView::suppressIds);
connect(mUI.mTree, &ResultsTree::editFunctionContract, this, &ResultsView::editFunctionContract);
connect(this, &ResultsView::showResults, mUI.mTree, &ResultsTree::showResults);
diff --git a/gui/resultsview.h b/gui/resultsview.h
index 15f98e9cb..2332b429f 100644
--- a/gui/resultsview.h
+++ b/gui/resultsview.h
@@ -218,11 +218,6 @@ signals:
*/
void checkSelected(QStringList selectedFilesList);
- /**
- * Some results have been tagged
- */
- void tagged();
-
/** Suppress Ids */
void suppressIds(QStringList ids);
diff --git a/lib/importproject.h b/lib/importproject.h
index 3676c3b8c..3f4e73ef4 100644
--- a/lib/importproject.h
+++ b/lib/importproject.h
@@ -161,6 +161,10 @@ namespace CppcheckXml {
const char ToolsElementName[] = "tools";
const char TagsElementName[] = "tags";
const char TagElementName[] = "tag";
+ const char TagWarningsElementName[] = "tag-warnings";
+ const char TagAttributeName[] = "tag";
+ const char WarningElementName[] = "warning";
+ const char CppcheckIdAttributeName[] = "cppcheck-id";
const char CheckHeadersElementName[] = "check-headers";
const char CheckUnusedTemplatesElementName[] = "check-unused-templates";
const char MaxCtuDepthElementName[] = "max-ctu-depth";
From 592637af612eb7f7985e7eb35b95d42c2fa71ac6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Fri, 17 Jul 2020 09:05:38 +0200
Subject: [PATCH 18/58] Bug hunting; Avoid uninit false positives with simple
analysis
---
lib/bughuntingchecks.cpp | 44 ++++++++++++++++++++++++++++++++--------
1 file changed, 36 insertions(+), 8 deletions(-)
diff --git a/lib/bughuntingchecks.cpp b/lib/bughuntingchecks.cpp
index 454a50bc5..cb93bb5c0 100644
--- a/lib/bughuntingchecks.cpp
+++ b/lib/bughuntingchecks.cpp
@@ -157,6 +157,39 @@ static void integerOverflow(const Token *tok, const ExprEngine::Value &value, Ex
}
#endif
+/** check if variable is unconditionally assigned */
+static bool isVariableAssigned(const Variable *var, const Token *tok, const Token *scopeStart=nullptr)
+{
+ const Token * const start = scopeStart && precedes(var->nameToken(), scopeStart) ? scopeStart : var->nameToken();
+
+ for (const Token *prev = tok->previous(); prev; prev = prev->previous()) {
+ if (!precedes(start, prev))
+ break;
+
+ if (prev->str() == "}") {
+ if (Token::simpleMatch(prev->link()->tokAt(-2), "} else {")) {
+ const Token *elseEnd = prev;
+ const Token *elseStart = prev->link();
+ const Token *ifEnd = elseStart->tokAt(-2);
+ const Token *ifStart = ifEnd->link();
+ if (isVariableAssigned(var, ifEnd, ifStart) && isVariableAssigned(var, elseEnd, elseStart)) {
+ return true;
+ }
+ }
+ prev = prev->link();
+ }
+ if (scopeStart && Token::Match(prev, "return|throw|continue|break"))
+ return true;
+ if (Token::Match(prev, "%varid% =", var->declarationId()))
+ return true;
+
+ // bailout; if variable is used previously that is checked first
+ if (!scopeStart && prev->varId() == var->declarationId())
+ return true;
+ }
+ return false;
+}
+
static void uninit(const Token *tok, const ExprEngine::Value &value, ExprEngine::DataBase *dataBase)
{
if (!tok->astParent())
@@ -256,16 +289,11 @@ static void uninit(const Token *tok, const ExprEngine::Value &value, ExprEngine:
// Are there unconditional assignment?
if (var && Token::Match(var->nameToken(), "%varid% ;| %varid%| =", tok->varId()))
return;
- for (const Token *prev = tok->previous(); prev; prev = prev->previous()) {
- if (!precedes(var->nameToken(), prev))
- break;
- if (prev->str() == "}")
- prev = prev->link();
- if (Token::Match(prev, "%varid% =", tok->varId()))
- return;
- }
}
+ if (tok->variable() && isVariableAssigned(tok->variable(), tok))
+ return;
+
// Uninitialized function argument
bool inconclusive = false;
if (Token::Match(tok->astParent(), "[,(]")) {
From f2bd603bd308effc5dfa0f4d4dd28d7b00f4dfd1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Fri, 17 Jul 2020 11:02:46 +0200
Subject: [PATCH 19/58] Bug hunting; Fix TestBughuntingChecks
---
lib/bughuntingchecks.cpp | 18 ++++++++++++------
1 file changed, 12 insertions(+), 6 deletions(-)
diff --git a/lib/bughuntingchecks.cpp b/lib/bughuntingchecks.cpp
index cb93bb5c0..2162a8d05 100644
--- a/lib/bughuntingchecks.cpp
+++ b/lib/bughuntingchecks.cpp
@@ -180,12 +180,18 @@ static bool isVariableAssigned(const Variable *var, const Token *tok, const Toke
}
if (scopeStart && Token::Match(prev, "return|throw|continue|break"))
return true;
- if (Token::Match(prev, "%varid% =", var->declarationId()))
- return true;
-
- // bailout; if variable is used previously that is checked first
- if (!scopeStart && prev->varId() == var->declarationId())
- return true;
+ if (Token::Match(prev, "%varid% =", var->declarationId())) {
+ bool usedInRhs = false;
+ visitAstNodes(prev->next()->astOperand2(), [&usedInRhs, var](const Token *tok) {
+ if (tok->varId() == var->declarationId()) {
+ usedInRhs = true;
+ return ChildrenToVisit::done;
+ }
+ return ChildrenToVisit::op1_and_op2;
+ });
+ if (!usedInRhs)
+ return true;
+ }
}
return false;
}
From 7a4e6daecde8ea2dbc783e85d6b9266675705464 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Fri, 17 Jul 2020 11:26:03 +0200
Subject: [PATCH 20/58] Fix import GUI project problem
---
lib/importproject.cpp | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/lib/importproject.cpp b/lib/importproject.cpp
index ad498d21d..62ff618ca 100644
--- a/lib/importproject.cpp
+++ b/lib/importproject.cpp
@@ -1074,7 +1074,7 @@ bool ImportProject::importCppcheckGuiProject(std::istream &istr, Settings *setti
guiProject.libraries = readXmlStringList(node, "", CppcheckXml::LibraryElementName, nullptr);
else if (strcmp(node->Name(), CppcheckXml::SuppressionsElementName) == 0) {
for (const tinyxml2::XMLElement *child = node->FirstChildElement(); child; child = child->NextSiblingElement()) {
- if (strcmp(child->Name(), CppcheckXml::SuppressionsElementName) != 0)
+ if (strcmp(child->Name(), CppcheckXml::SuppressionElementName) != 0)
continue;
auto read = [](const char *s, const char *def) {
return s ? s : def;
@@ -1082,6 +1082,8 @@ bool ImportProject::importCppcheckGuiProject(std::istream &istr, Settings *setti
Suppressions::Suppression s;
s.errorId = read(child->GetText(), "");
s.fileName = read(child->Attribute("fileName"), "");
+ if (!s.fileName.empty())
+ s.fileName = joinRelativePath(path, s.fileName);
s.lineNumber = child->IntAttribute("lineNumber", Suppressions::Suppression::NO_LINE);
s.symbolName = read(child->Attribute("symbolName"), "");
std::istringstream(read(child->Attribute("cppcheck-id"), "0")) >> s.cppcheckId;
From 2713474f56a11bbd59e01e7f271ce0901e87856d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Fri, 17 Jul 2020 12:25:06 +0200
Subject: [PATCH 21/58] Fixed Cppcheck warning
---
gui/projectfile.cpp | 2 +-
gui/projectfile.h | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/gui/projectfile.cpp b/gui/projectfile.cpp
index 8745cf345..45035aec1 100644
--- a/gui/projectfile.cpp
+++ b/gui/projectfile.cpp
@@ -641,7 +641,7 @@ void ProjectFile::readSuppressions(QXmlStreamReader &reader)
}
-void ProjectFile::readTagWarnings(QXmlStreamReader &reader, QString tag)
+void ProjectFile::readTagWarnings(QXmlStreamReader &reader, const QString &tag)
{
QXmlStreamReader::TokenType type;
do {
diff --git a/gui/projectfile.h b/gui/projectfile.h
index 1d372ed35..388c03379 100644
--- a/gui/projectfile.h
+++ b/gui/projectfile.h
@@ -447,7 +447,7 @@ protected:
* @brief Read tag warnings, what warnings are tagged with a specific tag
* @param reader XML stream reader.
*/
- void readTagWarnings(QXmlStreamReader &reader, QString tag);
+ void readTagWarnings(QXmlStreamReader &reader, const QString &tag);
/**
* @brief Read string list
From 58638d775769acfb50778b87dfc69c9e6a3d8bed Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Fri, 17 Jul 2020 13:20:31 +0200
Subject: [PATCH 22/58] Bug hunting; Fix itc.py test
---
lib/bughuntingchecks.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/lib/bughuntingchecks.cpp b/lib/bughuntingchecks.cpp
index 2162a8d05..3ba3643a6 100644
--- a/lib/bughuntingchecks.cpp
+++ b/lib/bughuntingchecks.cpp
@@ -295,10 +295,10 @@ static void uninit(const Token *tok, const ExprEngine::Value &value, ExprEngine:
// Are there unconditional assignment?
if (var && Token::Match(var->nameToken(), "%varid% ;| %varid%| =", tok->varId()))
return;
- }
- if (tok->variable() && isVariableAssigned(tok->variable(), tok))
- return;
+ if (tok->variable() && isVariableAssigned(tok->variable(), tok))
+ return;
+ }
// Uninitialized function argument
bool inconclusive = false;
From 382f21a5c926368dadb4be769c4dd238b83421cb Mon Sep 17 00:00:00 2001
From: Georgy Komarov
Date: Sat, 18 Jul 2020 07:02:12 +0300
Subject: [PATCH 23/58] Fixed crash on garbage code: comparisson with an empty
second operand
This will fix #9774.
---
lib/tokenize.cpp | 17 ++++++++++++-----
test/testgarbage.cpp | 2 +-
test/testtokenize.cpp | 5 ++++-
3 files changed, 17 insertions(+), 7 deletions(-)
diff --git a/lib/tokenize.cpp b/lib/tokenize.cpp
index a9fa35bea..196653206 100644
--- a/lib/tokenize.cpp
+++ b/lib/tokenize.cpp
@@ -9624,11 +9624,18 @@ void Tokenizer::findGarbageCode() const
if (match1 && match2)
syntaxError(tok);
}
- if (Token::Match(tok, "%or%|%oror%|~|^|!|%comp%|+|-|/|% )|]|}")) {
- if (isC())
- syntaxError(tok, tok->str() + tok->next()->str());
- if (tok->str() != ">" && !Token::simpleMatch(tok->previous(), "operator"))
- syntaxError(tok, tok->str() + " " + tok->next()->str());
+ if (Token::Match(tok, "%or%|%oror%|~|^|!|%comp%|+|-|/|%")) {
+ std::string code = "";
+ if (Token::Match(tok->next(), ")|]|}"))
+ code = tok->str() + tok->next()->str();
+ if (Token::simpleMatch(tok->next(), "( )"))
+ code = tok->str() + "()";
+ if (!code.empty()) {
+ if (isC())
+ syntaxError(tok, code);
+ if (tok->str() != ">" && !Token::simpleMatch(tok->previous(), "operator"))
+ syntaxError(tok, code);
+ }
}
if (Token::Match(tok, "%num%|%bool%|%char%|%str% %num%|%bool%|%char%|%str%") && !Token::Match(tok, "%str% %str%"))
syntaxError(tok);
diff --git a/test/testgarbage.cpp b/test/testgarbage.cpp
index 48c1df944..814d2ac21 100644
--- a/test/testgarbage.cpp
+++ b/test/testgarbage.cpp
@@ -1402,7 +1402,7 @@ private:
void garbageCode164() {
//7234
- checkCode("class d{k p;}(){d::d():B<()}");
+ ASSERT_THROW(checkCode("class d{k p;}(){d::d():B<()}"), InternalError);
}
void garbageCode165() {
diff --git a/test/testtokenize.cpp b/test/testtokenize.cpp
index 361c396a8..4d2705d9e 100644
--- a/test/testtokenize.cpp
+++ b/test/testtokenize.cpp
@@ -8087,7 +8087,7 @@ private:
ASSERT_THROW(tokenizeAndStringify("void foo() { for_chain( if (!done) done = 1); }"), InternalError);
ASSERT_THROW(tokenizeAndStringify("void foo() { for_chain( a, b, if (!done) done = 1); }"), InternalError);
- ASSERT_THROW_EQUALS(tokenizeAndStringify("void f() { if (retval==){} }"), InternalError, "syntax error: == )");
+ ASSERT_THROW_EQUALS(tokenizeAndStringify("void f() { if (retval==){} }"), InternalError, "syntax error: ==)");
// after (expr)
ASSERT_NO_THROW(tokenizeAndStringify("void f() { switch (a) int b; }"));
@@ -8108,6 +8108,9 @@ private:
// Ticket #9664
ASSERT_NO_THROW(tokenizeAndStringify("S s = { .x { 2 }, .y[0] { 3 } };"));
+
+ ASSERT_THROW_EQUALS(tokenizeAndStringify("void f() { assert(a==()); }"), InternalError, "syntax error: ==()");
+ ASSERT_THROW_EQUALS(tokenizeAndStringify("void f() { assert(a+()); }"), InternalError, "syntax error: +()");
}
From 6bc13080eec83491e31f6cdc1f7f4d66733830fc Mon Sep 17 00:00:00 2001
From: Georgy Komarov
Date: Sat, 18 Jul 2020 07:07:20 +0300
Subject: [PATCH 24/58] Simplify condition
---
lib/tokenize.cpp | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/lib/tokenize.cpp b/lib/tokenize.cpp
index 196653206..06884c75a 100644
--- a/lib/tokenize.cpp
+++ b/lib/tokenize.cpp
@@ -9631,9 +9631,7 @@ void Tokenizer::findGarbageCode() const
if (Token::simpleMatch(tok->next(), "( )"))
code = tok->str() + "()";
if (!code.empty()) {
- if (isC())
- syntaxError(tok, code);
- if (tok->str() != ">" && !Token::simpleMatch(tok->previous(), "operator"))
+ if (isC() || (tok->str() != ">" && !Token::simpleMatch(tok->previous(), "operator")))
syntaxError(tok, code);
}
}
From 744b3631861d510abde174dee88c83359b178dc0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Sat, 18 Jul 2020 08:23:04 +0200
Subject: [PATCH 25/58] GUI; Tweaks. Add tooltips, change texts.
---
gui/projectfiledialog.cpp | 5 +--
gui/projectfiledialog.ui | 39 ++++++++++++++++++----
gui/resultsview.ui | 69 +++++++++++++++------------------------
3 files changed, 62 insertions(+), 51 deletions(-)
diff --git a/gui/projectfiledialog.cpp b/gui/projectfiledialog.cpp
index 4eb1c403d..52ce8c61e 100644
--- a/gui/projectfiledialog.cpp
+++ b/gui/projectfiledialog.cpp
@@ -208,6 +208,7 @@ ProjectFileDialog::ProjectFileDialog(ProjectFile *projectFile, QWidget *parent)
connect(mUI.mListSuppressions, &QListWidget::doubleClicked, this, &ProjectFileDialog::editSuppression);
connect(mUI.mBtnBrowseMisraFile, &QPushButton::clicked, this, &ProjectFileDialog::browseMisraFile);
connect(mUI.mChkAllVsConfigs, &QCheckBox::clicked, this, &ProjectFileDialog::checkAllVSConfigs);
+ connect(mUI.mBtnNormalAnalysis, &QCheckBox::toggled, mUI.mBtnSafeClasses, &QCheckBox::setEnabled);
loadFromProjectFile(projectFile);
}
@@ -266,7 +267,7 @@ void ProjectFileDialog::loadFromProjectFile(const ProjectFile *projectFile)
else
mUI.mBtnCppcheckParser->setChecked(true);
mUI.mBtnSafeClasses->setChecked(projectFile->safeChecks.classes);
- mUI.mBugHunting->setChecked(projectFile->bugHunting);
+ mUI.mBtnBugHunting->setChecked(projectFile->bugHunting);
setExcludedPaths(projectFile->getExcludedPaths());
setLibraries(projectFile->getLibraries());
const QString platform = projectFile->getPlatform();
@@ -371,7 +372,7 @@ void ProjectFileDialog::saveToProjectFile(ProjectFile *projectFile) const
projectFile->setLibraries(getLibraries());
projectFile->clangParser = mUI.mBtnClangParser->isChecked();
projectFile->safeChecks.classes = mUI.mBtnSafeClasses->isChecked();
- projectFile->bugHunting = mUI.mBugHunting->isChecked();
+ projectFile->bugHunting = mUI.mBtnBugHunting->isChecked();
if (mUI.mComboBoxPlatform->currentText().endsWith(".xml"))
projectFile->setPlatform(mUI.mComboBoxPlatform->currentText());
else {
diff --git a/gui/projectfiledialog.ui b/gui/projectfiledialog.ui
index 1cf6d5d15..d809dd894 100644
--- a/gui/projectfiledialog.ui
+++ b/gui/projectfiledialog.ui
@@ -420,7 +420,11 @@
-
-
+
+
+ This is a workfolder that Cppcheck will use for various purposes.
+
+
-
@@ -461,18 +465,31 @@
-
- Check that code is safe
+ Analysis
-
-
+
- Bug hunting -- Detect all bugs. Generates mostly noise.
+ Normal analysis -- Avoid false positives.
+
+
+ true
+
+
+
+ -
+
+
+ Bug hunting -- Generates mostly noise. The goal is to be "soundy" and detect most bugs.
-
+
+ If you want to design your classes to be as flexible and robust as possible then the public interface must be very robust. Cppcheck will asumme that arguments can take *any* value.
+
Check that each class has a safe public interface
@@ -605,7 +622,11 @@
-
-
+
+
+ Filepaths in warnings will be relative to this path
+
+
@@ -617,7 +638,11 @@
-
-
+
+
+ If tags are added, you will be able to right click on warnings and set one of these tags. You can manually categorize warnings.
+
+
@@ -625,7 +650,7 @@
-
- Exclude source files in paths
+ Exclude source files
-
diff --git a/gui/resultsview.ui b/gui/resultsview.ui
index 584c8a9ea..cfe8b57d5 100644
--- a/gui/resultsview.ui
+++ b/gui/resultsview.ui
@@ -159,49 +159,34 @@
-
-
-
- Qt::Vertical
+
+
+ Configured contracts:
+
+
+
+ -
+
+
+ QAbstractItemView::NoEditTriggers
+
+
+
+ -
+
+
+ Missing contracts:
+
+
+
+ -
+
+
+ QAbstractItemView::NoEditTriggers
+
+
+ true
-
-
-
-
-
-
- Configured contracts:
-
-
-
- -
-
-
- QAbstractItemView::NoEditTriggers
-
-
-
-
-
-
-
- -
-
-
- Missing contracts:
-
-
-
- -
-
-
- QAbstractItemView::NoEditTriggers
-
-
- true
-
-
-
-
-
From 23008e7bc0cae23b93269f782a4c7a15772e3c50 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Sat, 18 Jul 2020 08:56:31 +0200
Subject: [PATCH 26/58] GUI: Add button in ProjectFileDialog to exclude a file
---
gui/projectfiledialog.cpp | 20 ++++++++++++++++----
gui/projectfiledialog.h | 7 ++++++-
gui/projectfiledialog.ui | 9 ++++++++-
3 files changed, 30 insertions(+), 6 deletions(-)
diff --git a/gui/projectfiledialog.cpp b/gui/projectfiledialog.cpp
index 52ce8c61e..70452845a 100644
--- a/gui/projectfiledialog.cpp
+++ b/gui/projectfiledialog.cpp
@@ -199,6 +199,7 @@ ProjectFileDialog::ProjectFileDialog(ProjectFile *projectFile, QWidget *parent)
connect(mUI.mBtnEditInclude, &QPushButton::clicked, this, &ProjectFileDialog::editIncludeDir);
connect(mUI.mBtnRemoveInclude, &QPushButton::clicked, this, &ProjectFileDialog::removeIncludeDir);
connect(mUI.mBtnAddIgnorePath, SIGNAL(clicked()), this, SLOT(addExcludePath()));
+ connect(mUI.mBtnAddIgnoreFile, SIGNAL(clicked()), this, SLOT(addExcludeFile()));
connect(mUI.mBtnEditIgnorePath, &QPushButton::clicked, this, &ProjectFileDialog::editExcludePath);
connect(mUI.mBtnRemoveIgnorePath, &QPushButton::clicked, this, &ProjectFileDialog::removeExcludePath);
connect(mUI.mBtnIncludeUp, &QPushButton::clicked, this, &ProjectFileDialog::moveIncludePathUp);
@@ -718,9 +719,17 @@ void ProjectFileDialog::editIncludeDir()
void ProjectFileDialog::addExcludePath()
{
- QString dir = getExistingDirectory(tr("Select directory to ignore"), true);
- if (!dir.isEmpty())
- addExcludePath(dir);
+ addExcludePath(getExistingDirectory(tr("Select directory to ignore"), true));
+}
+
+void ProjectFileDialog::addExcludeFile()
+{
+ const QFileInfo inf(mProjectFile->getFilename());
+ const QDir &dir = inf.absoluteDir();
+ QMap filters;
+ filters[tr("Source files")] = "*.c *.cpp";
+ filters[tr("All files")] = "*.*";
+ addExcludePath(QFileDialog::getOpenFileName(this, tr("Exclude file"), dir.canonicalPath(), toFilterString(filters)));
}
void ProjectFileDialog::editExcludePath()
@@ -803,7 +812,10 @@ int ProjectFileDialog::getSuppressionIndex(const QString &shortText) const
void ProjectFileDialog::browseMisraFile()
{
- const QString fileName = QFileDialog::getOpenFileName(this, tr("Select MISRA rule texts file"), QDir::homePath(), tr("Misra rule texts file (%1)").arg("*.txt"));
+ const QString fileName = QFileDialog::getOpenFileName(this,
+ tr("Select MISRA rule texts file"),
+ QDir::homePath(),
+ tr("Misra rule texts file (%1)").arg("*.txt"));
if (!fileName.isEmpty()) {
QSettings settings;
mUI.mEditMisraFile->setText(fileName);
diff --git a/gui/projectfiledialog.h b/gui/projectfiledialog.h
index 30237a370..fef320a4f 100644
--- a/gui/projectfiledialog.h
+++ b/gui/projectfiledialog.h
@@ -215,10 +215,15 @@ protected slots:
void editIncludeDir();
/**
- * @brief Add new path to exclude.
+ * @brief Add new path to exclude list.
*/
void addExcludePath();
+ /**
+ * @brief Add new file to exclude list.
+ */
+ void addExcludeFile();
+
/**
* @brief Edit excluded path in the list.
*/
diff --git a/gui/projectfiledialog.ui b/gui/projectfiledialog.ui
index d809dd894..4ba8af1aa 100644
--- a/gui/projectfiledialog.ui
+++ b/gui/projectfiledialog.ui
@@ -661,7 +661,14 @@
-
- Add...
+ Add folder...
+
+
+
+ -
+
+
+ Add file...
From 9ec5ae2929dcded279c3c9d73330c201fc90d031 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Sat, 18 Jul 2020 15:27:25 +0200
Subject: [PATCH 27/58] GUI: Started writing a manual
---
man/gui-manual.md | 183 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 183 insertions(+)
create mode 100644 man/gui-manual.md
diff --git a/man/gui-manual.md b/man/gui-manual.md
new file mode 100644
index 000000000..d4ccee5ac
--- /dev/null
+++ b/man/gui-manual.md
@@ -0,0 +1,183 @@
+---
+title: Cppcheck GUI manual
+subtitle: Version 2.1.99
+author: Cppcheck team
+lang: en
+documentclass: report
+---
+
+
+# Standalone analysis
+
+It is possible to quickly analyze files. Open the `Analyze` menu and click on either `Files...` or `Directory...`.
+
+It is recommended that you create a project for analysis. A properly configured project will give you better analysis.
+
+# Project
+
+## Creating project
+
+Open the `File` menu and click on `New project...`.
+
+## Project options
+
+The `Project file` dialog contains 4 tabs:
+ - Paths and defines; paths to check and basic preprocessor settings.
+ - Types and Functions; configuration of platform and 3rd party libraries
+ - Analysis; analysis options
+ - Warning options; formatting warnings, suppressing warnings, etc
+ - Addons; extra analysis with addons
+
+### Paths and defines
+
+It is recommended to import a project file.
+
+#### Import project
+
+Project to import. Cppcheck will get:
+ * what files to check
+ * preprocessor defines
+ * preprocessor include paths
+ * language standard if set
+
+#### Paths (If you do not import project)
+
+What paths to check.
+
+#### Defines (If you do not import project)
+
+Cppcheck automatically checks the code with different preprocessor configurations.
+
+ #ifdef A
+ code1
+ #endif
+ #ifdef B
+ code2
+ #endif
+
+Cppcheck will automatically perform analysis both when A is defined and B is defined. So any bugs in both code1 and code2 will be detected.
+
+If you want to configure that A will always be defined in Cppcheck analysis you can do that here.
+
+Defines are separated by semicolon. So you can for instance write:
+
+ A;B=3;C
+
+#### Undefines (If you do not import project)
+
+
+Cppcheck automatically checks the code with different preprocessor configurations.
+
+ #ifdef A
+ code1
+ #endif
+ #ifdef B
+ code2
+ #endif
+
+Cppcheck will automatically perform analysis both when A is defined and B is defined. So any bugs in both code1 and code2 will be detected.
+
+If you want to configure that A is never defined in Cppcheck analysis you can do that here.
+
+Undefines are separated by semicolon. So you can for instance write:
+
+ A;C
+
+#### Include paths (If you do not import project)
+
+Specify include paths.
+
+### Types and Functions
+
+Cppcheck uses `Platform` setting to determine size of short/int/long/pointer/etc.
+
+Check the libraries that you use in the `Libraries` listbox.
+
+### Analysis
+
+#### Cppcheck build dir
+
+This is a work-folder that Cppcheck uses. Each Cppcheck project should have a separate build dir. It is used for:
+ * whole program analysis
+ * debug output
+ * faster analysis (if a source file has changed check it, if source file is not changed then reuse old results)
+ * statistics
+
+#### Parser
+
+It is in general recommended to use Cppcheck parser. However you can choose to use Clang parser; Clang will be executed with a command line flag that tells it to dump its AST and Cppcheck will read that AST and convert it into a corresponding Cppcheck AST and use that.
+
+#### Analysis
+
+Configure what kind of analysis you want.
+
+The `Normal analysis` is recommended for most use cases. Especially if you use Cppcheck in CI.
+
+The `Bug hunting` can be used if you really want to find a bug in your code and can invest time looking at bad results and providing extra configuration.
+
+#### Limit analysis
+
+You can turn off checking of headers. That could be interesting if Cppcheck is very slow. But normally, you should check the code in headers.
+
+It is possible to check the code in unused templates. However the Cppcheck AST will be incomplete/wrong. The recommendation is that you do not check unused templates to avoid wrong warnings. The templates will be checked properly when you do use them.
+
+Max CTU depth: How deep should the whole program analysis be. The risk with a "too high" value is that Cppcheck will be slow.
+
+Max recursion in template instantiation: Max recursion when Cppcheck instantiates templates. The risk with a "too high" value is that Cppcheck will be slow and can require much memory.
+
+
+### Warning options
+
+#### Root path
+
+The root path for warnings. Cppcheck will strip away this part of the path from warnings. For instance if there is a warning in `../myproject/foo/bar/file.cpp` and the root path is `../myproject/foo` then the path for the warning will be `bar/file.cpp`.
+
+#### Warning Tags
+
+Tags allow you to manually categorize warnings.
+
+#### Exclude source files
+
+Excluded source files will not be analyzed by Cppcheck
+
+#### Suppressions
+
+List of suppressions. These warnings will not be shown.
+
+### Addons
+
+Y2038 - 32-bit timers that count number of seconds since 1970 will overflow in year 2038. Check that the code does not use such timers.
+
+Thread safety - Check that the code is thread safe
+
+Cert - Ensure that the Cert coding standard is followed
+
+Misra - Ensure that the Misra coding standard is followed. Please note you need to have a textfile with the misra rule texts to get proper warning messages. Cppcheck is not legally allowed to distribute the misra rule texts.
+
+Clang-tidy - Run Clang-tidy
+
+
+# Preferences
+
+TODO
+
+
+# Looking at results
+
+When you have run the analysis it is time to look at the results.
+
+If you click on a warning then the corresponding code will be shown in the "Warning details" at the bottom.
+
+You can right click warnings to get options. The difference of "hiding" a warning and "suppressing" a warning is that the suppression is permanent and hiding the warning is only temporary.
+
+
+# Tagging warnings
+
+You can manually categorize warnings.
+
+You choose the names of the categories yourself in the project file dialog.
+
+If tag names are configured then when you look at results you can right click on a warning and tag it.
+
+
+
From 2b0505930722af59c0070d093f59cec3d3245f22 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Sat, 18 Jul 2020 17:10:39 +0200
Subject: [PATCH 28/58] GUI; Tweaked button texts
---
gui/projectfiledialog.ui | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/gui/projectfiledialog.ui b/gui/projectfiledialog.ui
index 4ba8af1aa..31884ce91 100644
--- a/gui/projectfiledialog.ui
+++ b/gui/projectfiledialog.ui
@@ -661,14 +661,14 @@
-
- Add folder...
+ Exclude folder...
-
- Add file...
+ Exclude file...
From 46d997cd71db27754e4079266ea0cfa13d2023b4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Sat, 18 Jul 2020 17:20:45 +0200
Subject: [PATCH 29/58] GUI; Remove unused signal ResultTree::tagged
---
gui/resultstree.h | 5 -----
1 file changed, 5 deletions(-)
diff --git a/gui/resultstree.h b/gui/resultstree.h
index cc7dacd35..5f2557826 100644
--- a/gui/resultstree.h
+++ b/gui/resultstree.h
@@ -202,11 +202,6 @@ signals:
*/
void treeSelectionChanged(const QModelIndex ¤t);
- /**
- * Selected item(s) has been tagged
- */
- void tagged();
-
/** Suppress Ids */
void suppressIds(QStringList ids);
From 7cb65b7f674ee9d04a03e6e6a56d0b33eae23f0a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Sat, 18 Jul 2020 18:14:55 +0200
Subject: [PATCH 30/58] GUI: Speedup code editor when selecting another warning
in the same file
---
gui/codeeditor.cpp | 13 +++++++++++++
gui/codeeditor.h | 24 ++++++++++++++++++++++++
gui/resultstree.cpp | 3 +++
gui/resultsview.cpp | 32 +++++++++++++++++++-------------
gui/resultsview.h | 3 +++
lib/bughuntingchecks.cpp | 7 +++++--
6 files changed, 67 insertions(+), 15 deletions(-)
diff --git a/gui/codeeditor.cpp b/gui/codeeditor.cpp
index a44e351c1..e7d6c6f07 100644
--- a/gui/codeeditor.cpp
+++ b/gui/codeeditor.cpp
@@ -257,6 +257,19 @@ void CodeEditor::setError(const QString &code, int errorLine, const QStringList
highlightErrorLine();
}
+void CodeEditor::setError(int errorLine, const QStringList &symbols)
+{
+ mHighlighter->setSymbols(symbols);
+
+ mErrorPosition = getPos(toPlainText(), errorLine);
+ QTextCursor tc = textCursor();
+ tc.setPosition(mErrorPosition);
+ setTextCursor(tc);
+ centerCursor();
+
+ highlightErrorLine();
+}
+
int CodeEditor::lineNumberAreaWidth()
{
int digits = 1;
diff --git a/gui/codeeditor.h b/gui/codeeditor.h
index 09fe523d0..3ed01d6b0 100644
--- a/gui/codeeditor.h
+++ b/gui/codeeditor.h
@@ -77,6 +77,29 @@ public:
*/
void setError(const QString &code, int errorLine, const QStringList &symbols);
+ /**
+ * Goto another error in existing source file
+ * \param errorLine line number
+ * \param symbols the related symbols, these are marked
+ */
+ void setError(int errorLine, const QStringList &symbols);
+
+ void setFileName(const QString &fileName)
+ {
+ mFileName = fileName;
+ }
+
+ QString getFileName() const
+ {
+ return mFileName;
+ }
+
+ void clear()
+ {
+ mFileName.clear();
+ setPlainText(QString());
+ }
+
protected:
void resizeEvent(QResizeEvent *event) override;
@@ -93,6 +116,7 @@ private:
Highlighter *mHighlighter;
CodeEditorStyle *mWidgetStyle;
int mErrorPosition;
+ QString mFileName;
};
diff --git a/gui/resultstree.cpp b/gui/resultstree.cpp
index 392fc082e..191735c39 100644
--- a/gui/resultstree.cpp
+++ b/gui/resultstree.cpp
@@ -63,6 +63,7 @@ static const char LINE[] = "line";
static const char MESSAGE[] = "message";
static const char SEVERITY[] = "severity";
static const char SINCEDATE[] = "sinceDate";
+static const char SYMBOLNAMES[] = "symbolNames";
static const char SUMMARY[] = "summary";
static const char TAGS[] = "tags";
@@ -223,6 +224,7 @@ bool ResultsTree::addErrorItem(const ErrorItem &item)
data[FILE0] = stripPath(item.file0, true);
data[FUNCTION] = item.function;
data[SINCEDATE] = item.sinceDate;
+ data[SYMBOLNAMES] = item.symbolNames;
data[TAGS] = line.tags;
data[HIDE] = hide;
stditem->setData(QVariant(data));
@@ -256,6 +258,7 @@ bool ResultsTree::addErrorItem(const ErrorItem &item)
child_data[CWE] = line.cwe;
child_data[CPPCHECKID] = line.cppcheckId;
child_data[INCONCLUSIVE] = line.inconclusive;
+ child_data[SYMBOLNAMES] = item.symbolNames;
child_item->setData(QVariant(child_data));
}
}
diff --git a/gui/resultsview.cpp b/gui/resultsview.cpp
index e5cd359fa..35a5b5d96 100644
--- a/gui/resultsview.cpp
+++ b/gui/resultsview.cpp
@@ -385,9 +385,8 @@ void ResultsView::updateDetails(const QModelIndex &index)
QStandardItemModel *model = qobject_cast(mUI.mTree->model());
QStandardItem *item = model->itemFromIndex(index);
- mUI.mCode->setPlainText(QString());
-
if (!item) {
+ mUI.mCode->clear();
mUI.mDetails->setText(QString());
return;
}
@@ -400,6 +399,7 @@ void ResultsView::updateDetails(const QModelIndex &index)
// If there is no severity data then it is a parent item without summary and message
if (!data.contains("severity")) {
+ mUI.mCode->clear();
mUI.mDetails->setText(QString());
return;
}
@@ -425,19 +425,25 @@ void ResultsView::updateDetails(const QModelIndex &index)
if (!QFileInfo(filepath).exists() && QFileInfo(mUI.mTree->getCheckDirectory() + '/' + filepath).exists())
filepath = mUI.mTree->getCheckDirectory() + '/' + filepath;
- QFile file(filepath);
- if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
- QStringList symbols;
- QRegularExpression re(".*: ([A-Za-z_][A-Za-z0-9_]*)$");
- const QString errorMessage = data["message"].toString();
- QRegularExpressionMatch match = re.match(errorMessage);
- if (match.hasMatch()) {
- symbols << match.captured(1);
- }
+ QStringList symbols;
+ if (data.contains("symbolNames"))
+ symbols = data["symbolNames"].toString().split("\n");
- QTextStream in(&file);
- mUI.mCode->setError(in.readAll(), lineNumber, symbols);
+ if (filepath == mUI.mCode->getFileName())
+ {
+ mUI.mCode->setError(lineNumber, symbols);
+ return;
}
+
+ QFile file(filepath);
+ if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ mUI.mCode->clear();
+ return;
+ }
+
+ QTextStream in(&file);
+ mUI.mCode->setError(in.readAll(), lineNumber, symbols);
+ mUI.mCode->setFileName(filepath);
}
void ResultsView::log(const QString &str)
diff --git a/gui/resultsview.h b/gui/resultsview.h
index 2332b429f..44871a266 100644
--- a/gui/resultsview.h
+++ b/gui/resultsview.h
@@ -359,6 +359,9 @@ private slots:
void on_mListLog_customContextMenuRequested(const QPoint &pos);
private:
QSet mContracts;
+
+ /** Current file shown in the code editor */
+ QString mCurrentFileName;
};
/// @}
#endif // RESULTSVIEW_H
diff --git a/lib/bughuntingchecks.cpp b/lib/bughuntingchecks.cpp
index 3ba3643a6..82c7bdde9 100644
--- a/lib/bughuntingchecks.cpp
+++ b/lib/bughuntingchecks.cpp
@@ -352,10 +352,11 @@ static void uninit(const Token *tok, const ExprEngine::Value &value, ExprEngine:
const std::string inconclusiveMessage(inconclusive ? ". It is inconclusive if there would be a problem in the function call." : "");
if (!uninitStructMember.empty()) {
+ const std::string symbol = tok->expressionString() + "." + uninitStructMember;
dataBase->reportError(tok,
Severity::SeverityType::error,
"bughuntingUninitStructMember",
- "Cannot determine that '" + tok->expressionString() + "." + uninitStructMember + "' is initialized" + inconclusiveMessage,
+ "$symbol:" + symbol + "\nCannot determine that '$symbol' is initialized" + inconclusiveMessage,
CWE_USE_OF_UNINITIALIZED_VARIABLE,
inconclusive,
value.type == ExprEngine::ValueType::BailoutValue);
@@ -366,10 +367,12 @@ static void uninit(const Token *tok, const ExprEngine::Value &value, ExprEngine:
if (uninitData)
uninitexpr += "[0]";
+ const std::string symbol = (tok->varId() > 0) ? ("$symbol:" + tok->str() + "\n") : std::string();
+
dataBase->reportError(tok,
Severity::SeverityType::error,
"bughuntingUninit",
- "Cannot determine that '" + uninitexpr + "' is initialized" + inconclusiveMessage,
+ symbol + "Cannot determine that '" + uninitexpr + "' is initialized" + inconclusiveMessage,
CWE_USE_OF_UNINITIALIZED_VARIABLE,
inconclusive,
value.type == ExprEngine::ValueType::BailoutValue);
From e9281babc498e7fd3bcd19a9f418637a9a399634 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Sat, 18 Jul 2020 18:54:21 +0200
Subject: [PATCH 31/58] Bug hunting; avoid false positives for structs/classes
with constructors
---
lib/exprengine.cpp | 11 +++++++++--
test/testbughuntingchecks.cpp | 12 ++++++++++++
2 files changed, 21 insertions(+), 2 deletions(-)
diff --git a/lib/exprengine.cpp b/lib/exprengine.cpp
index 388f4149c..f0ad91118 100644
--- a/lib/exprengine.cpp
+++ b/lib/exprengine.cpp
@@ -2567,8 +2567,15 @@ static ExprEngine::ValuePtr createVariableValue(const Variable &var, Data &data)
data.addConstraints(value, var.nameToken());
return value;
}
- if (valueType->type == ValueType::Type::RECORD)
- return createStructVal(valueType->typeScope, var.isLocal() && !var.isStatic(), data);
+ if (valueType->type == ValueType::Type::RECORD) {
+ bool init = true;
+ if (var.isLocal() && !var.isStatic()) {
+ init = valueType->typeScope &&
+ valueType->typeScope->definedType &&
+ valueType->typeScope->definedType->needInitialization != Type::NeedInitialization::False;
+ }
+ return createStructVal(valueType->typeScope, init, data);
+ }
if (valueType->smartPointerType) {
auto structValue = createStructVal(valueType->smartPointerType->classScope, var.isLocal() && !var.isStatic(), data);
auto size = std::make_shared(data.getNewSymbolName(), 1, ~0UL);
diff --git a/test/testbughuntingchecks.cpp b/test/testbughuntingchecks.cpp
index bfb62e985..392991b31 100644
--- a/test/testbughuntingchecks.cpp
+++ b/test/testbughuntingchecks.cpp
@@ -39,6 +39,7 @@ private:
TEST_CASE(uninit_array);
TEST_CASE(uninit_function_par);
TEST_CASE(uninit_malloc);
+ TEST_CASE(uninit_struct);
TEST_CASE(ctu);
#endif
}
@@ -98,6 +99,17 @@ private:
ASSERT_EQUALS("[test.cpp:1]: (error) Cannot determine that '*p' is initialized\n", errout.str());
}
+ void uninit_struct() {
+ // Assume that constructors initialize all members
+ // TODO whole program analysis
+ check("struct Data { Data(); int x; }\n"
+ "void foo() {\n"
+ " Data data;\n"
+ " x = data.x;\n"
+ "}");
+ ASSERT_EQUALS("", errout.str());
+ }
+
void ctu() {
check("void init(int &x) {\n"
" x = 1;\n"
From 3723c708fc051c74269a56b2d338087f5db9db97 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Sun, 19 Jul 2020 08:07:10 +0200
Subject: [PATCH 32/58] ImportProject; Do not fail loading a GUI project that
has warning tags
---
lib/importproject.cpp | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/lib/importproject.cpp b/lib/importproject.cpp
index 62ff618ca..3f81338d6 100644
--- a/lib/importproject.cpp
+++ b/lib/importproject.cpp
@@ -1130,7 +1130,9 @@ bool ImportProject::importCppcheckGuiProject(std::istream &istr, Settings *setti
else
return false;
}
- } else
+ } else if (strcmp(node->Name(), CppcheckXml::TagWarningsElementName) == 0)
+ ; // TODO
+ else
return false;
}
settings->basePaths = temp.basePaths;
From 011c5e97f98e9d2a5db36297fd85f3a5591b1b0e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Sun, 19 Jul 2020 08:56:14 +0200
Subject: [PATCH 33/58] gui-manual: Preferences
---
man/gui-manual.md | 133 ++++++++++++++++++++++++++++++++++++++--------
1 file changed, 110 insertions(+), 23 deletions(-)
diff --git a/man/gui-manual.md b/man/gui-manual.md
index d4ccee5ac..a94b0c664 100644
--- a/man/gui-manual.md
+++ b/man/gui-manual.md
@@ -9,9 +9,11 @@ documentclass: report
# Standalone analysis
-It is possible to quickly analyze files. Open the `Analyze` menu and click on either `Files...` or `Directory...`.
+It is possible to quickly analyze files. Open the `Analyze` menu and click on
+either `Files...` or `Directory...`.
-It is recommended that you create a project for analysis. A properly configured project will give you better analysis.
+It is recommended that you create a project for analysis. A properly configured
+project will give you better analysis.
# Project
@@ -55,9 +57,11 @@ Cppcheck automatically checks the code with different preprocessor configuration
code2
#endif
-Cppcheck will automatically perform analysis both when A is defined and B is defined. So any bugs in both code1 and code2 will be detected.
+Cppcheck will automatically perform analysis both when A is defined and B is
+defined. So any bugs in both code1 and code2 will be detected.
-If you want to configure that A will always be defined in Cppcheck analysis you can do that here.
+If you want to configure that A will always be defined in Cppcheck analysis you
+can do that here.
Defines are separated by semicolon. So you can for instance write:
@@ -75,9 +79,11 @@ Cppcheck automatically checks the code with different preprocessor configuration
code2
#endif
-Cppcheck will automatically perform analysis both when A is defined and B is defined. So any bugs in both code1 and code2 will be detected.
+Cppcheck will automatically perform analysis both when A is defined and B is
+defined. So any bugs in both code1 and code2 will be detected.
-If you want to configure that A is never defined in Cppcheck analysis you can do that here.
+If you want to configure that A is never defined in Cppcheck analysis you can
+do that here.
Undefines are separated by semicolon. So you can for instance write:
@@ -97,40 +103,57 @@ Check the libraries that you use in the `Libraries` listbox.
#### Cppcheck build dir
-This is a work-folder that Cppcheck uses. Each Cppcheck project should have a separate build dir. It is used for:
+This is a work-folder that Cppcheck uses. Each Cppcheck project should have a
+separate build dir. It is used for:
* whole program analysis
* debug output
- * faster analysis (if a source file has changed check it, if source file is not changed then reuse old results)
+ * faster analysis (if a source file has changed check it, if source file is
+ not changed then reuse old results)
* statistics
#### Parser
-It is in general recommended to use Cppcheck parser. However you can choose to use Clang parser; Clang will be executed with a command line flag that tells it to dump its AST and Cppcheck will read that AST and convert it into a corresponding Cppcheck AST and use that.
+It is in general recommended to use Cppcheck parser. However you can choose to
+use Clang parser; Clang will be executed with a command line flag that tells it
+to dump its AST and Cppcheck will read that AST and convert it into a
+corresponding Cppcheck AST and use that.
#### Analysis
Configure what kind of analysis you want.
-The `Normal analysis` is recommended for most use cases. Especially if you use Cppcheck in CI.
+The `Normal analysis` is recommended for most use cases. Especially if you use
+Cppcheck in CI.
-The `Bug hunting` can be used if you really want to find a bug in your code and can invest time looking at bad results and providing extra configuration.
+The `Bug hunting` can be used if you really want to find a bug in your code
+and can invest time looking at bad results and providing extra configuration.
#### Limit analysis
-You can turn off checking of headers. That could be interesting if Cppcheck is very slow. But normally, you should check the code in headers.
+You can turn off checking of headers. That could be interesting if Cppcheck is
+very slow. But normally, you should check the code in headers.
-It is possible to check the code in unused templates. However the Cppcheck AST will be incomplete/wrong. The recommendation is that you do not check unused templates to avoid wrong warnings. The templates will be checked properly when you do use them.
+It is possible to check the code in unused templates. However the Cppcheck AST
+will be incomplete/wrong. The recommendation is that you do not check unused
+templates to avoid wrong warnings. The templates will be checked properly when
+you do use them.
-Max CTU depth: How deep should the whole program analysis be. The risk with a "too high" value is that Cppcheck will be slow.
+Max CTU depth: How deep should the whole program analysis be. The risk with a
+"too high" value is that Cppcheck will be slow.
-Max recursion in template instantiation: Max recursion when Cppcheck instantiates templates. The risk with a "too high" value is that Cppcheck will be slow and can require much memory.
+Max recursion in template instantiation: Max recursion when Cppcheck
+instantiates templates. The risk with a "too high" value is that Cppcheck will
+be slow and can require much memory.
### Warning options
#### Root path
-The root path for warnings. Cppcheck will strip away this part of the path from warnings. For instance if there is a warning in `../myproject/foo/bar/file.cpp` and the root path is `../myproject/foo` then the path for the warning will be `bar/file.cpp`.
+The root path for warnings. Cppcheck will strip away this part of the path
+from warnings. For instance if there is a warning in
+`../myproject/foo/bar/file.cpp` and the root path is `../myproject/foo` then
+the path for the warning will be `bar/file.cpp`.
#### Warning Tags
@@ -146,38 +169,102 @@ List of suppressions. These warnings will not be shown.
### Addons
-Y2038 - 32-bit timers that count number of seconds since 1970 will overflow in year 2038. Check that the code does not use such timers.
+Y2038 - 32-bit timers that count number of seconds since 1970 will overflow in
+year 2038. Check that the code does not use such timers.
Thread safety - Check that the code is thread safe
Cert - Ensure that the Cert coding standard is followed
-Misra - Ensure that the Misra coding standard is followed. Please note you need to have a textfile with the misra rule texts to get proper warning messages. Cppcheck is not legally allowed to distribute the misra rule texts.
+Misra - Ensure that the Misra coding standard is followed. Please note you
+need to have a textfile with the misra rule texts to get proper warning
+messages. Cppcheck is not legally allowed to distribute the misra rule texts.
Clang-tidy - Run Clang-tidy
# Preferences
-TODO
+`Number of threads`: Number of threads to use in analysis. Each thread checks
+its own source file.
+
+`Force checking of all #ifdef configurations`: Cppcheck try to check all code
+and will therefore guess different preprocessor configurations. The maximum
+number of configurations that is checked is 14 by default.
+
+`Show full path of files`: Show the full paths in the results.
+
+`Show "No errors found" message when no errors found`: If you want to get a
+message box about this.
+
+`Display error id column "Id"`: Show error id in results
+
+`Enable inline suppressions`: You can suppress warnings with comments. See the
+Cppcheck manual (http://cppcheck.sf.net/manual.pdf) for more information about
+those.
+
+`Check for inconclusive errors also`: When full analysis of the code can not
+determine if there should be a warning or not, it is inconclusive. Normally
+Cppcheck does not warn then.
+
+`Show statistics on check completion`: Show statistics in a window when
+analysis finish.
+
+`Show internal warnings in log`: Internal warnings (for debugging) is shown
+in the `Analysis log`.
+
+`Applications`: Configure external editor to open from context menu when you
+right click on a warning.
+
+`Save all errors when creating report`: If hidden warnings should be saved or
+not.
+
+`Save full path to files in report`: If you use `Root path` the warnings on the
+screen will not have the full path.
+
+`Language`: Configure language to use for GUI.
+
+`Python binary`: To be able to execute addons, Cppcheck needs to know where
+python is. Unless you configure something, Cppcheck will try to execute python
+in your PATH.
+
+`Misra rule texts`: Only needed if you want to use the Misra addon. Cppcheck is
+not legally allowed to distribute the Misra rule texts and these must be
+provided by users. The Misra rule texts are proprietary. An example rule text
+file can be found here: https://github.com/danmar/cppcheck/blob/main/addons/test/misra/misra2012_rules_dummy_ascii.txt
+
+`Clang path`: The path to `clang` binary. If no path is provided then system
+PATH is used.
+
+`Visual studio headers`: If you want to use the Visual Studio headers in the
+analysis you can provide the path(s) here. Hint: Open a visual studio command
+prompt and type `SET INCLUDE`. Then copy/paste the paths.
+
+`Code editor style`: The visual theme to use for the code editor that is used
+when you investigate results.
+
# Looking at results
When you have run the analysis it is time to look at the results.
-If you click on a warning then the corresponding code will be shown in the "Warning details" at the bottom.
+If you click on a warning then the corresponding code will be shown in the
+"Warning details" at the bottom.
-You can right click warnings to get options. The difference of "hiding" a warning and "suppressing" a warning is that the suppression is permanent and hiding the warning is only temporary.
+You can right click warnings to get options. The difference of "hiding" a
+warning and "suppressing" a warning is that the suppression is permanent and
+hiding the warning is only temporary.
# Tagging warnings
You can manually categorize warnings.
-You choose the names of the categories yourself in the project file dialog.
+You choose the names of the categories yourself in the project file dialog.
-If tag names are configured then when you look at results you can right click on a warning and tag it.
+If tag names are configured then when you look at results you can right click
+on a warning and tag it.
From 7e65b561f0e47d03b856cbd7c65a57d72c6a337e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Sun, 19 Jul 2020 11:10:38 +0200
Subject: [PATCH 34/58] AST: Fix ast for 'for ((..initexpr..);;)'
---
lib/tokenlist.cpp | 12 ++++++++++++
test/testtokenize.cpp | 2 ++
2 files changed, 14 insertions(+)
diff --git a/lib/tokenlist.cpp b/lib/tokenlist.cpp
index d923ca7b3..e12983f8e 100644
--- a/lib/tokenlist.cpp
+++ b/lib/tokenlist.cpp
@@ -1387,6 +1387,18 @@ static Token * createAstAtToken(Token *tok, bool cpp)
init1 = tok2;
AST_state state1(cpp);
compileExpression(tok2, state1);
+ if (init1->str() == "(") {
+ for (Token *tok3 = init1; tok3 != tok3->link(); tok3 = tok3->next()) {
+ if (tok3->astParent()) {
+ while (tok3->astParent())
+ tok3 = tok3->astParent();
+ init1 = tok3;
+ break;
+ }
+ if (!Token::Match(tok3, "%op%|(|["))
+ init1 = tok3;
+ }
+ }
} else {
while (tok2 && tok2 != endPar && tok2->str() != ";") {
if (tok2->str() == "<" && tok2->link()) {
diff --git a/test/testtokenize.cpp b/test/testtokenize.cpp
index 4d2705d9e..63438b58e 100644
--- a/test/testtokenize.cpp
+++ b/test/testtokenize.cpp
@@ -7677,6 +7677,8 @@ private:
ASSERT_EQUALS("pf.pf.12,(&&", testAst("((p.f) && (p.f)(1,2))"));
+ ASSERT_EQUALS("forresdirGetFirst.file&_T(,(=;;(", testAst("for ((res = dir.GetFirst(&file, _T("")));;) {}"));
+
// problems with: if (x[y]==z)
ASSERT_EQUALS("ifa(0[1==(", testAst("if(a()[0]==1){}"));
ASSERT_EQUALS("ifbuff0[&(*1==(", testAst("if (*((DWORD*)&buff[0])==1){}"));
From 5df9cd90a61b8adaf33e9ee1ce73c2ed85dcf059 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Sun, 19 Jul 2020 11:10:53 +0200
Subject: [PATCH 35/58] astyle formatting
[ci skip]
---
gui/codeeditor.h | 9 +++------
gui/resultsview.cpp | 3 +--
2 files changed, 4 insertions(+), 8 deletions(-)
diff --git a/gui/codeeditor.h b/gui/codeeditor.h
index 3ed01d6b0..479125c0a 100644
--- a/gui/codeeditor.h
+++ b/gui/codeeditor.h
@@ -84,18 +84,15 @@ public:
*/
void setError(int errorLine, const QStringList &symbols);
- void setFileName(const QString &fileName)
- {
+ void setFileName(const QString &fileName) {
mFileName = fileName;
}
- QString getFileName() const
- {
+ QString getFileName() const {
return mFileName;
}
- void clear()
- {
+ void clear() {
mFileName.clear();
setPlainText(QString());
}
diff --git a/gui/resultsview.cpp b/gui/resultsview.cpp
index 35a5b5d96..b3beb24b7 100644
--- a/gui/resultsview.cpp
+++ b/gui/resultsview.cpp
@@ -429,8 +429,7 @@ void ResultsView::updateDetails(const QModelIndex &index)
if (data.contains("symbolNames"))
symbols = data["symbolNames"].toString().split("\n");
- if (filepath == mUI.mCode->getFileName())
- {
+ if (filepath == mUI.mCode->getFileName()) {
mUI.mCode->setError(lineNumber, symbols);
return;
}
From 41a846d8a714b50410dbca5e03d690e5540cc274 Mon Sep 17 00:00:00 2001
From: "Richard A. Smith"
Date: Thu, 16 Jul 2020 16:29:17 -0300
Subject: [PATCH 36/58] misra.py: Squelch duplicate violation messages
When checking multiple files if the same violation is encountered from
an included file then the violation is both displayed and counted in the
error summary.
Track each violation by location and error number and if the violation
has been previously encountered then do not report it again.
---
addons/misra.py | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)
diff --git a/addons/misra.py b/addons/misra.py
index 5763ce668..6e8b1602f 100755
--- a/addons/misra.py
+++ b/addons/misra.py
@@ -1059,6 +1059,8 @@ class MisraChecker:
self.severity = None
+ self.existing_violations = set()
+
def __repr__(self):
attrs = ["settings", "verify_expected", "verify_actual", "violations",
"ruleTexts", "suppressedRules", "dumpfileSuppressions",
@@ -2726,11 +2728,17 @@ class MisraChecker:
if self.severity:
cppcheck_severity = self.severity
- cppcheckdata.reportError(location, cppcheck_severity, errmsg, 'misra', errorId, misra_severity)
+ this_violation = '{}-{}-{}-{}'.format(location.file, location.linenr, location.column, ruleNum)
- if misra_severity not in self.violations:
- self.violations[misra_severity] = []
- self.violations[misra_severity].append('misra-' + errorId)
+ # If this is new violation then record it and show it. If not then
+ # skip it since it has already been displayed.
+ if not this_violation in self.existing_violations:
+ self.existing_violations.add(this_violation)
+ cppcheckdata.reportError(location, cppcheck_severity, errmsg, 'misra', errorId, misra_severity)
+
+ if misra_severity not in self.violations:
+ self.violations[misra_severity] = []
+ self.violations[misra_severity].append('misra-' + errorId)
def loadRuleTexts(self, filename):
num1 = 0
From fe0081496c95526ec9491bb043ed7a81e8f6953e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Sun, 19 Jul 2020 16:27:56 +0200
Subject: [PATCH 37/58] Bug hunting; Avoid bailout uninit FP, arrays
---
lib/bughuntingchecks.cpp | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/lib/bughuntingchecks.cpp b/lib/bughuntingchecks.cpp
index 82c7bdde9..eb38858e1 100644
--- a/lib/bughuntingchecks.cpp
+++ b/lib/bughuntingchecks.cpp
@@ -296,6 +296,10 @@ static void uninit(const Token *tok, const ExprEngine::Value &value, ExprEngine:
if (var && Token::Match(var->nameToken(), "%varid% ;| %varid%| =", tok->varId()))
return;
+ // Arrays are allocated on the stack
+ if (var && Token::Match(tok, "%var% [") && var->isArray())
+ return;
+
if (tok->variable() && isVariableAssigned(tok->variable(), tok))
return;
}
From 4a76dbb632320c1c387c7327a7e06613622eedf3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Sun, 19 Jul 2020 16:54:44 +0200
Subject: [PATCH 38/58] Bug hunting; Avoid bailout uninit FP, stream object
---
lib/bughuntingchecks.cpp | 4 ++++
lib/exprengine.cpp | 4 ++++
test/testbughuntingchecks.cpp | 17 +++++++++++++++++
3 files changed, 25 insertions(+)
diff --git a/lib/bughuntingchecks.cpp b/lib/bughuntingchecks.cpp
index eb38858e1..afb55c5f7 100644
--- a/lib/bughuntingchecks.cpp
+++ b/lib/bughuntingchecks.cpp
@@ -269,6 +269,10 @@ static void uninit(const Token *tok, const ExprEngine::Value &value, ExprEngine:
if (Token::Match(tok, "%var% .") && tok->next()->originalName() != "->")
return;
+ // Assume that stream object is initialized
+ if (Token::Match(tok->previous(), "[;{}] %var% <<|>>") && !tok->next()->astParent())
+ return;
+
// Containers are not uninitialized
std::vector tokens{tok, tok->astOperand1(), tok->astOperand2()};
if (Token::Match(tok->previous(), ". %name%"))
diff --git a/lib/exprengine.cpp b/lib/exprengine.cpp
index f0ad91118..59fc71ffa 100644
--- a/lib/exprengine.cpp
+++ b/lib/exprengine.cpp
@@ -2241,6 +2241,10 @@ static std::string execute(const Token *start, const Token *end, Data &data)
if (Token::Match(tok, "[;{}]"))
data.trackProgramState(tok);
+ if (Token::simpleMatch(tok, "__CPPCHECK_BAILOUT__ ;"))
+ // This is intended for testing
+ throw ExprEngineException(tok, "__CPPCHECK_BAILOUT__");
+
if (Token::simpleMatch(tok, "while (") && (tok->linkAt(1), ") ;") && tok->next()->astOperand1()->hasKnownIntValue() && tok->next()->astOperand1()->getKnownIntValue() == 0) {
tok = tok->tokAt(4);
continue;
diff --git a/test/testbughuntingchecks.cpp b/test/testbughuntingchecks.cpp
index 392991b31..0e2d7ccc1 100644
--- a/test/testbughuntingchecks.cpp
+++ b/test/testbughuntingchecks.cpp
@@ -40,6 +40,7 @@ private:
TEST_CASE(uninit_function_par);
TEST_CASE(uninit_malloc);
TEST_CASE(uninit_struct);
+ TEST_CASE(uninit_bailout);
TEST_CASE(ctu);
#endif
}
@@ -110,6 +111,22 @@ private:
ASSERT_EQUALS("", errout.str());
}
+ void uninit_bailout() {
+ check("void foo() {\n"
+ " __CPPCHECK_BAILOUT__;\n"
+ " int values[5];\n"
+ " values[i] = 123;\n"
+ "}");
+ ASSERT_EQUALS("", errout.str());
+
+ check("void foo() {\n"
+ " __CPPCHECK_BAILOUT__;\n"
+ " std::ostringstream comm;\n"
+ " comm << 123;\n"
+ "}");
+ ASSERT_EQUALS("", errout.str());
+ }
+
void ctu() {
check("void init(int &x) {\n"
" x = 1;\n"
From 0a84a1fad7186e5a850ee9509ea93bc6261737ef Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Sun, 19 Jul 2020 21:40:17 +0200
Subject: [PATCH 39/58] GUI: Add help files that replace the gui-manual.md
---
gui/help/investigating-warnings.html | 22 +++
gui/help/preferences.html | 74 ++++++++
gui/help/projectfiledialog.html | 181 ++++++++++++++++++
gui/help/standalone-analysis.html | 17 ++
gui/help/tagging.html | 22 +++
man/gui-manual.md | 270 ---------------------------
6 files changed, 316 insertions(+), 270 deletions(-)
create mode 100644 gui/help/investigating-warnings.html
create mode 100644 gui/help/preferences.html
create mode 100644 gui/help/projectfiledialog.html
create mode 100644 gui/help/standalone-analysis.html
create mode 100644 gui/help/tagging.html
delete mode 100644 man/gui-manual.md
diff --git a/gui/help/investigating-warnings.html b/gui/help/investigating-warnings.html
new file mode 100644
index 000000000..5eec66bd0
--- /dev/null
+++ b/gui/help/investigating-warnings.html
@@ -0,0 +1,22 @@
+
+
+Investigating warnings
+
+
+
+Investigating warnings
+
+When you have run the analysis it is time to look at the results.
+
+If you click on a warning then the corresponding code will be shown in the
+"Warning details" at the bottom.
+
+You can right click warnings to get options. The difference of
+"hiding" a warning and "suppressing" a warning is that
+the suppression is permanent and hiding the warning is only temporary. When
+suppressing warning(s), that is saved in the project file.
+
+
+
+
+
diff --git a/gui/help/preferences.html b/gui/help/preferences.html
new file mode 100644
index 000000000..31cce6593
--- /dev/null
+++ b/gui/help/preferences.html
@@ -0,0 +1,74 @@
+
+
+Preferences
+
+
+
+Preferences
+
+Number of threads
Number of threads to use in analysis. Each
+thread checks its own source file.
+
+Force checking of all #ifdef configurations
Cppcheck try to check
+all code and will therefore guess different preprocessor configurations. The
+maximum number of configurations that is checked is 14 by default.
+
+Show full path of files
Show the full paths in the results.
+
+Show "No errors found" message when no errors found
+If you want to get a message box about this.
+
+Display error id column "Id"
Show error id in results
+
+Enable inline suppressions
You can suppress warnings with comments.
+See the Cppcheck manual (http://cppcheck.sf.net/manual.pdf) for more information
+about inline suppressions.
+
+Check for inconclusive errors also
When full analysis of the code
+can not determine if there should be a warning or not, it is inconclusive.
+Normally Cppcheck does not warn then.
+
+Show statistics on check completion
Show statistics in a window
+when analysis finish.
+
+Show internal warnings in log
Internal warnings (for debugging) is
+shown in the Analysis log.
+
+Applications
Configure external editor to open from context menu
+when you right click on a warning.
+
+Save all errors when creating report
If hidden warnings should be
+saved or not.
+
+Save full path to files in report
If you use Root path the
+warnings on the screen will not have the full path.
+
+Language
Configure language to use for GUI.
+
+Python binary
To be able to execute addons, Cppcheck needs to know
+where python is. Unless you configure something, Cppcheck will try to execute
+python in your PATH.
+
+Misra rule texts
Only needed if you want to use the Misra addon.
+Cppcheck is not legally allowed to distribute the Misra rule texts and these
+must be provided by users. The Misra rule texts are proprietary. An example
+rule text file:
+
Appendix A Summary of guidelines
+Rule 1.1
+Text of rule 1.1
+Rule 1.2
+Text of rule 1.2
+
+
+Clang path
The path to clang binary. If no path is provided
+then system PATH is used.
+
+Visual studio headers
If you want to use the Visual Studio headers
+in the analysis you can provide the path(s) here. Hint: Open a visual studio
+command prompt and type SET INCLUDE. Then copy/paste the paths.
+
+Code editor style
The visual theme to use for the code editor that
+is used when you investigate results.
+
+
+
diff --git a/gui/help/projectfiledialog.html b/gui/help/projectfiledialog.html
new file mode 100644
index 000000000..5e4ef7bc5
--- /dev/null
+++ b/gui/help/projectfiledialog.html
@@ -0,0 +1,181 @@
+
+
+Project File Dialog
+
+
+
+Project File Dialog
+
+The Project file dialog contains 4 tabs:
+
+- Paths and defines; paths to check and basic preprocessor settings.
+
- Types and Functions; configuration of platform and 3rd party libraries
+
- Analysis; analysis options
+
- Warning options; formatting warnings, suppressing warnings, etc
+
- Addons; extra analysis with addons
+
+
+Paths and defines
+
+It is recommended to import a project file.
+
+ Import project
+
+Project to import. Cppcheck will get:
+
+- What files to check
+
- Preprocessor defines
+
- Preprocessor include paths
+
- Language standard if set
+
+
+Paths (If you do not import project)
+
+What paths to check.
+
+Defines (If you do not import project)
+
+Cppcheck automatically checks the code with different preprocessor
+configurations.
+
+#ifdef A
+code1
+#endif
+#ifdef B
+code2
+#endif
+
+Cppcheck will automatically perform analysis both when A is defined and B is
+defined. So any bugs in both code1 and code2 will be detected.
+
+If you want to configure that A will always be defined in Cppcheck analysis
+you can do that here.
+
+Defines are separated by semicolon. So you can for instance write:
+
+A;B=3;C
+
+Undefines (If you do not import project)
+
+Cppcheck automatically checks the code with different preprocessor
+configurations.
+
+#ifdef A
+code1
+#endif
+#ifdef B
+code2
+#endif
+
+Cppcheck will automatically perform analysis both when A is defined and B is
+defined. So any bugs in both code1 and code2 will be detected.
+
+If you want to configure that A is never defined in Cppcheck analysis you
+can do that here.
+
+Undefines are separated by semicolon. So you can for instance write:
+
+A;C
+
+Include paths (If you do not import project)
+
+Specify include paths.
+
+Types and Functions
+
+Cppcheck uses the Platform setting to determine size of
+short/int/long/pointer/etc.
+
+Check the libraries that you use in the Libraries listbox.
+
+Analysis
+
+Cppcheck build dir
+
+This is a work-folder that Cppcheck uses. Each Cppcheck project should have
+a separate build dir. It is used for:
+
+- whole program analysis
+
- debug output
+
- faster analysis (if a source file has changed check it, if source file is
+ not changed then reuse old results)
+
- statistics
+
+
+Parser
+
+It is in general recommended to use Cppcheck parser. However you can choose
+to use Clang parser; Clang will be executed with a command line flag that tells
+it to dump its AST and Cppcheck will read that AST and convert it into a
+corresponding Cppcheck AST and use that.
+
+Analysis
+
+Configure what kind of analysis you want.
+
+The Normal analysis is recommended for most use cases. Especially if
+you use Cppcheck in CI.
+
+The Bug hunting can be used if you really want to find a bug in your
+code and can invest time looking at bad results and providing extra
+configuration.
+
+Limit analysis
+
+You can turn off checking of headers. That could be interesting if Cppcheck
+is very slow. But normally, you should check the code in headers.
+
+It is possible to check the code in unused templates. However the Cppcheck
+AST will be incomplete/wrong. The recommendation is that you do not check
+unused templates to avoid wrong warnings. The templates will be checked
+properly when you do use them.
+
+Max CTU depth: How deep should the whole program analysis be. The risk with
+a "too high" value is that Cppcheck will be slow.
+
+Max recursion in template instantiation: Max recursion when Cppcheck
+instantiates templates. The risk with a "too high" value is that
+Cppcheck will be slow and can require much memory.
+
+
+Warning options
+
+Root path
+
+The root path for warnings. Cppcheck will strip away this part of the path
+from warnings. For instance if there is a warning in
+
../myproject/foo/bar/file.cpp
and the root path is
+../myproject/foo
then the path for the warning will be
+bar/file.cpp
.
+
+Warning Tags
+
+Tags allow you to manually categorize warnings.
+
+Exclude source files
+
+Excluded source files will not be analyzed by Cppcheck.
+
+Suppressions
+
+List of suppressions. These warnings will not be shown.
+
+Addons
+
+Y2038
32-bit timers that count number of seconds since 1970 will
+overflow in year 2038. Check that the code does not use such timers.
+
+Thread safety
Check that the code is thread safe
+
+Cert
Ensure that the Cert coding standard is followed
+
+Misra
Ensure that the Misra coding standard is followed. Please
+note you need to have a textfile with the misra rule texts to get proper
+warning messages. Cppcheck is not legally allowed to distribute the misra
+rule texts.
+
+Clang-tidy
Run Clang-tidy
+
+
+
+
diff --git a/gui/help/standalone-analysis.html b/gui/help/standalone-analysis.html
new file mode 100644
index 000000000..ad7fae5fe
--- /dev/null
+++ b/gui/help/standalone-analysis.html
@@ -0,0 +1,17 @@
+
+
+
+Standalone analysis
+
+
+
+Standalone analysis
+
+It is possible to quickly analyze files. Open the Analyze menu and click on
+either Files... or Directory....
+
+It is recommended that you create a project for analysis. A properly configured
+project will give you better analysis.
+
+
+
diff --git a/gui/help/tagging.html b/gui/help/tagging.html
new file mode 100644
index 000000000..7ac4e7ee8
--- /dev/null
+++ b/gui/help/tagging.html
@@ -0,0 +1,22 @@
+
+
+Tagging warnings
+
+
+
+Tagging warnings
+
+You can manually categorize warnings.
+
+You choose the names of the categories yourself in the project file
+dialog.
+
+If tag names are configured, then you can tag the warnings by
+right-clicking on them and selecting the proper tag in the
+context menu.
+
+Tags are saved in the project file and will be permanent.
+
+
+
+
diff --git a/man/gui-manual.md b/man/gui-manual.md
deleted file mode 100644
index a94b0c664..000000000
--- a/man/gui-manual.md
+++ /dev/null
@@ -1,270 +0,0 @@
----
-title: Cppcheck GUI manual
-subtitle: Version 2.1.99
-author: Cppcheck team
-lang: en
-documentclass: report
----
-
-
-# Standalone analysis
-
-It is possible to quickly analyze files. Open the `Analyze` menu and click on
-either `Files...` or `Directory...`.
-
-It is recommended that you create a project for analysis. A properly configured
-project will give you better analysis.
-
-# Project
-
-## Creating project
-
-Open the `File` menu and click on `New project...`.
-
-## Project options
-
-The `Project file` dialog contains 4 tabs:
- - Paths and defines; paths to check and basic preprocessor settings.
- - Types and Functions; configuration of platform and 3rd party libraries
- - Analysis; analysis options
- - Warning options; formatting warnings, suppressing warnings, etc
- - Addons; extra analysis with addons
-
-### Paths and defines
-
-It is recommended to import a project file.
-
-#### Import project
-
-Project to import. Cppcheck will get:
- * what files to check
- * preprocessor defines
- * preprocessor include paths
- * language standard if set
-
-#### Paths (If you do not import project)
-
-What paths to check.
-
-#### Defines (If you do not import project)
-
-Cppcheck automatically checks the code with different preprocessor configurations.
-
- #ifdef A
- code1
- #endif
- #ifdef B
- code2
- #endif
-
-Cppcheck will automatically perform analysis both when A is defined and B is
-defined. So any bugs in both code1 and code2 will be detected.
-
-If you want to configure that A will always be defined in Cppcheck analysis you
-can do that here.
-
-Defines are separated by semicolon. So you can for instance write:
-
- A;B=3;C
-
-#### Undefines (If you do not import project)
-
-
-Cppcheck automatically checks the code with different preprocessor configurations.
-
- #ifdef A
- code1
- #endif
- #ifdef B
- code2
- #endif
-
-Cppcheck will automatically perform analysis both when A is defined and B is
-defined. So any bugs in both code1 and code2 will be detected.
-
-If you want to configure that A is never defined in Cppcheck analysis you can
-do that here.
-
-Undefines are separated by semicolon. So you can for instance write:
-
- A;C
-
-#### Include paths (If you do not import project)
-
-Specify include paths.
-
-### Types and Functions
-
-Cppcheck uses `Platform` setting to determine size of short/int/long/pointer/etc.
-
-Check the libraries that you use in the `Libraries` listbox.
-
-### Analysis
-
-#### Cppcheck build dir
-
-This is a work-folder that Cppcheck uses. Each Cppcheck project should have a
-separate build dir. It is used for:
- * whole program analysis
- * debug output
- * faster analysis (if a source file has changed check it, if source file is
- not changed then reuse old results)
- * statistics
-
-#### Parser
-
-It is in general recommended to use Cppcheck parser. However you can choose to
-use Clang parser; Clang will be executed with a command line flag that tells it
-to dump its AST and Cppcheck will read that AST and convert it into a
-corresponding Cppcheck AST and use that.
-
-#### Analysis
-
-Configure what kind of analysis you want.
-
-The `Normal analysis` is recommended for most use cases. Especially if you use
-Cppcheck in CI.
-
-The `Bug hunting` can be used if you really want to find a bug in your code
-and can invest time looking at bad results and providing extra configuration.
-
-#### Limit analysis
-
-You can turn off checking of headers. That could be interesting if Cppcheck is
-very slow. But normally, you should check the code in headers.
-
-It is possible to check the code in unused templates. However the Cppcheck AST
-will be incomplete/wrong. The recommendation is that you do not check unused
-templates to avoid wrong warnings. The templates will be checked properly when
-you do use them.
-
-Max CTU depth: How deep should the whole program analysis be. The risk with a
-"too high" value is that Cppcheck will be slow.
-
-Max recursion in template instantiation: Max recursion when Cppcheck
-instantiates templates. The risk with a "too high" value is that Cppcheck will
-be slow and can require much memory.
-
-
-### Warning options
-
-#### Root path
-
-The root path for warnings. Cppcheck will strip away this part of the path
-from warnings. For instance if there is a warning in
-`../myproject/foo/bar/file.cpp` and the root path is `../myproject/foo` then
-the path for the warning will be `bar/file.cpp`.
-
-#### Warning Tags
-
-Tags allow you to manually categorize warnings.
-
-#### Exclude source files
-
-Excluded source files will not be analyzed by Cppcheck
-
-#### Suppressions
-
-List of suppressions. These warnings will not be shown.
-
-### Addons
-
-Y2038 - 32-bit timers that count number of seconds since 1970 will overflow in
-year 2038. Check that the code does not use such timers.
-
-Thread safety - Check that the code is thread safe
-
-Cert - Ensure that the Cert coding standard is followed
-
-Misra - Ensure that the Misra coding standard is followed. Please note you
-need to have a textfile with the misra rule texts to get proper warning
-messages. Cppcheck is not legally allowed to distribute the misra rule texts.
-
-Clang-tidy - Run Clang-tidy
-
-
-# Preferences
-
-`Number of threads`: Number of threads to use in analysis. Each thread checks
-its own source file.
-
-`Force checking of all #ifdef configurations`: Cppcheck try to check all code
-and will therefore guess different preprocessor configurations. The maximum
-number of configurations that is checked is 14 by default.
-
-`Show full path of files`: Show the full paths in the results.
-
-`Show "No errors found" message when no errors found`: If you want to get a
-message box about this.
-
-`Display error id column "Id"`: Show error id in results
-
-`Enable inline suppressions`: You can suppress warnings with comments. See the
-Cppcheck manual (http://cppcheck.sf.net/manual.pdf) for more information about
-those.
-
-`Check for inconclusive errors also`: When full analysis of the code can not
-determine if there should be a warning or not, it is inconclusive. Normally
-Cppcheck does not warn then.
-
-`Show statistics on check completion`: Show statistics in a window when
-analysis finish.
-
-`Show internal warnings in log`: Internal warnings (for debugging) is shown
-in the `Analysis log`.
-
-`Applications`: Configure external editor to open from context menu when you
-right click on a warning.
-
-`Save all errors when creating report`: If hidden warnings should be saved or
-not.
-
-`Save full path to files in report`: If you use `Root path` the warnings on the
-screen will not have the full path.
-
-`Language`: Configure language to use for GUI.
-
-`Python binary`: To be able to execute addons, Cppcheck needs to know where
-python is. Unless you configure something, Cppcheck will try to execute python
-in your PATH.
-
-`Misra rule texts`: Only needed if you want to use the Misra addon. Cppcheck is
-not legally allowed to distribute the Misra rule texts and these must be
-provided by users. The Misra rule texts are proprietary. An example rule text
-file can be found here: https://github.com/danmar/cppcheck/blob/main/addons/test/misra/misra2012_rules_dummy_ascii.txt
-
-`Clang path`: The path to `clang` binary. If no path is provided then system
-PATH is used.
-
-`Visual studio headers`: If you want to use the Visual Studio headers in the
-analysis you can provide the path(s) here. Hint: Open a visual studio command
-prompt and type `SET INCLUDE`. Then copy/paste the paths.
-
-`Code editor style`: The visual theme to use for the code editor that is used
-when you investigate results.
-
-
-
-# Looking at results
-
-When you have run the analysis it is time to look at the results.
-
-If you click on a warning then the corresponding code will be shown in the
-"Warning details" at the bottom.
-
-You can right click warnings to get options. The difference of "hiding" a
-warning and "suppressing" a warning is that the suppression is permanent and
-hiding the warning is only temporary.
-
-
-# Tagging warnings
-
-You can manually categorize warnings.
-
-You choose the names of the categories yourself in the project file dialog.
-
-If tag names are configured then when you look at results you can right click
-on a warning and tag it.
-
-
-
From 2ace2b006a533e1ad75b4e491296101e27d7c2ce Mon Sep 17 00:00:00 2001
From: Rikard Falkeborn
Date: Thu, 9 Jul 2020 22:40:52 +0200
Subject: [PATCH 40/58] Refactor: Use visitAstNodes in checkleakautovar
---
lib/checkleakautovar.cpp | 23 +++++++++--------------
1 file changed, 9 insertions(+), 14 deletions(-)
diff --git a/lib/checkleakautovar.cpp b/lib/checkleakautovar.cpp
index 0991bfdf0..a6c681814 100644
--- a/lib/checkleakautovar.cpp
+++ b/lib/checkleakautovar.cpp
@@ -463,27 +463,21 @@ void CheckLeakAutoVar::checkScope(const Token * const startToken,
VarInfo varInfo1(*varInfo); // VarInfo for if code
VarInfo varInfo2(*varInfo); // VarInfo for else code
- // Recursively scan variable comparisons in condition
- std::stack tokens;
// Skip expressions before commas
const Token * astOperand2AfterCommas = tok->next()->astOperand2();
while (Token::simpleMatch(astOperand2AfterCommas, ","))
astOperand2AfterCommas = astOperand2AfterCommas->astOperand2();
- tokens.push(astOperand2AfterCommas);
- while (!tokens.empty()) {
- const Token *tok3 = tokens.top();
- tokens.pop();
+
+ // Recursively scan variable comparisons in condition
+ visitAstNodes(astOperand2AfterCommas, [&](const Token *tok3) {
if (!tok3)
- continue;
+ return ChildrenToVisit::none;
if (tok3->str() == "&&" || tok3->str() == "||") {
// FIXME: handle && ! || better
- tokens.push(tok3->astOperand1());
- tokens.push(tok3->astOperand2());
- continue;
+ return ChildrenToVisit::op1_and_op2;
}
if (tok3->str() == "(" && Token::Match(tok3->astOperand1(), "UNLIKELY|LIKELY")) {
- tokens.push(tok3->astOperand2());
- continue;
+ return ChildrenToVisit::op2;
} else if (tok3->str() == "(" && Token::Match(tok3->previous(), "%name%")) {
const std::vector params = getArguments(tok3->previous());
for (const Token *par : params) {
@@ -500,7 +494,7 @@ void CheckLeakAutoVar::checkScope(const Token * const startToken,
varInfo2.erase(vartok->varId());
}
}
- continue;
+ return ChildrenToVisit::none;
}
const Token *vartok = nullptr;
@@ -517,7 +511,8 @@ void CheckLeakAutoVar::checkScope(const Token * const startToken,
} else if (astIsVariableComparison(tok3, "==", "-1", &vartok)) {
varInfo1.erase(vartok->varId());
}
- }
+ return ChildrenToVisit::none;
+ });
checkScope(closingParenthesis->next(), &varInfo1, notzero, recursiveCount);
closingParenthesis = closingParenthesis->linkAt(1);
From 82fe6193fa6a36e5411208550b42cd9d278f7a25 Mon Sep 17 00:00:00 2001
From: Rikard Falkeborn
Date: Thu, 9 Jul 2020 23:08:04 +0200
Subject: [PATCH 41/58] Refactor: Use visitAstNodes in checkstring
---
lib/checkstring.cpp | 19 +++++++------------
1 file changed, 7 insertions(+), 12 deletions(-)
diff --git a/lib/checkstring.cpp b/lib/checkstring.cpp
index a87d5cd0c..576be7a1b 100644
--- a/lib/checkstring.cpp
+++ b/lib/checkstring.cpp
@@ -355,37 +355,32 @@ void CheckString::overlappingStrcmp()
continue;
std::list equals0;
std::list notEquals0;
- std::stack tokens;
- tokens.push(tok);
- while (!tokens.empty()) {
- const Token * const t = tokens.top();
- tokens.pop();
+ visitAstNodes(tok, [&](const Token * t) {
if (!t)
- continue;
+ return ChildrenToVisit::none;
if (t->str() == "||") {
- tokens.push(t->astOperand1());
- tokens.push(t->astOperand2());
- continue;
+ return ChildrenToVisit::op1_and_op2;
}
if (t->str() == "==") {
if (Token::simpleMatch(t->astOperand1(), "(") && Token::simpleMatch(t->astOperand2(), "0"))
equals0.push_back(t->astOperand1());
else if (Token::simpleMatch(t->astOperand2(), "(") && Token::simpleMatch(t->astOperand1(), "0"))
equals0.push_back(t->astOperand2());
- continue;
+ return ChildrenToVisit::none;
}
if (t->str() == "!=") {
if (Token::simpleMatch(t->astOperand1(), "(") && Token::simpleMatch(t->astOperand2(), "0"))
notEquals0.push_back(t->astOperand1());
else if (Token::simpleMatch(t->astOperand2(), "(") && Token::simpleMatch(t->astOperand1(), "0"))
notEquals0.push_back(t->astOperand2());
- continue;
+ return ChildrenToVisit::none;
}
if (t->str() == "!" && Token::simpleMatch(t->astOperand1(), "("))
equals0.push_back(t->astOperand1());
else if (t->str() == "(")
notEquals0.push_back(t);
- }
+ return ChildrenToVisit::none;
+ });
for (const Token *eq0 : equals0) {
for (const Token * ne0 : notEquals0) {
From 9ced26a7a112bf1d50adb6fcd41db5a7ec77640c Mon Sep 17 00:00:00 2001
From: Rikard Falkeborn
Date: Fri, 10 Jul 2020 00:50:57 +0200
Subject: [PATCH 42/58] Refactor: Use visitAstNodes in checkcondition
---
lib/checkcondition.cpp | 46 ++++++++++++++++--------------------------
1 file changed, 17 insertions(+), 29 deletions(-)
diff --git a/lib/checkcondition.cpp b/lib/checkcondition.cpp
index 22f166646..97dad288e 100644
--- a/lib/checkcondition.cpp
+++ b/lib/checkcondition.cpp
@@ -688,16 +688,11 @@ void CheckCondition::multiCondition2()
ErrorPath errorPath;
if (type == MULTICONDITIONTYPE::INNER) {
- std::stack tokens1;
- tokens1.push(cond1);
- while (!tokens1.empty()) {
- const Token *firstCondition = tokens1.top();
- tokens1.pop();
+ visitAstNodes(cond1, [&](const Token* firstCondition) {
if (!firstCondition)
- continue;
+ return ChildrenToVisit::none;
if (firstCondition->str() == "&&") {
- tokens1.push(firstCondition->astOperand1());
- tokens1.push(firstCondition->astOperand2());
+ return ChildrenToVisit::op1_and_op2;
} else if (!firstCondition->hasKnownIntValue()) {
if (!isReturnVar && isOppositeCond(false, mTokenizer->isCPP(), firstCondition, cond2, mSettings->library, true, true, &errorPath)) {
if (!isAliased(vars))
@@ -706,7 +701,8 @@ void CheckCondition::multiCondition2()
identicalInnerConditionError(firstCondition, cond2, errorPath);
}
}
- }
+ return ChildrenToVisit::none;
+ });
} else {
visitAstNodes(cond2, [&](const Token *secondCondition) {
if (secondCondition->str() == "||" || secondCondition->str() == "&&")
@@ -1436,20 +1432,15 @@ void CheckCondition::alwaysTrueFalse()
// Don't warn when there are expanded macros..
bool isExpandedMacro = false;
- std::stack tokens;
- tokens.push(tok);
- while (!tokens.empty()) {
- const Token *tok2 = tokens.top();
- tokens.pop();
+ visitAstNodes(tok, [&](const Token * tok2) {
if (!tok2)
- continue;
- tokens.push(tok2->astOperand1());
- tokens.push(tok2->astOperand2());
+ return ChildrenToVisit::none;
if (tok2->isExpandedMacro()) {
isExpandedMacro = true;
- break;
+ return ChildrenToVisit::done;
}
- }
+ return ChildrenToVisit::op1_and_op2;
+ });
if (isExpandedMacro)
continue;
for (const Token *parent = tok; parent; parent = parent->astParent()) {
@@ -1464,24 +1455,21 @@ void CheckCondition::alwaysTrueFalse()
// don't warn when condition checks sizeof result
bool hasSizeof = false;
bool hasNonNumber = false;
- tokens.push(tok);
- while (!tokens.empty()) {
- const Token *tok2 = tokens.top();
- tokens.pop();
+ visitAstNodes(tok, [&](const Token * tok2) {
if (!tok2)
- continue;
+ return ChildrenToVisit::none;
if (tok2->isNumber())
- continue;
+ return ChildrenToVisit::none;
if (Token::simpleMatch(tok2->previous(), "sizeof (")) {
hasSizeof = true;
- continue;
+ return ChildrenToVisit::none;
}
if (tok2->isComparisonOp() || tok2->isArithmeticalOp()) {
- tokens.push(tok2->astOperand1());
- tokens.push(tok2->astOperand2());
+ return ChildrenToVisit::op1_and_op2;
} else
hasNonNumber = true;
- }
+ return ChildrenToVisit::none;
+ });
if (!hasNonNumber && hasSizeof)
continue;
From ed36856451b0a77ea0d5c0a66f4f5ab3377238db Mon Sep 17 00:00:00 2001
From: Rikard Falkeborn
Date: Sat, 11 Jul 2020 00:02:28 +0200
Subject: [PATCH 43/58] Refactor: Use visitAstNodes in checkuninitvar
---
lib/checkuninitvar.cpp | 17 ++++++-----------
1 file changed, 6 insertions(+), 11 deletions(-)
diff --git a/lib/checkuninitvar.cpp b/lib/checkuninitvar.cpp
index e46f94304..c1b9d6454 100644
--- a/lib/checkuninitvar.cpp
+++ b/lib/checkuninitvar.cpp
@@ -881,22 +881,17 @@ bool CheckUninitVar::checkLoopBody(const Token *tok, const Variable& var, const
usetok = tok;
else if (tok->strAt(1) == "=") {
bool varIsUsedInRhs = false;
- std::stack tokens;
- tokens.push(tok->next()->astOperand2());
- while (!tokens.empty()) {
- const Token *t = tokens.top();
- tokens.pop();
+ visitAstNodes(tok->next()->astOperand2(), [&](const Token * t) {
if (!t)
- continue;
+ return ChildrenToVisit::none;
if (t->varId() == var.declarationId()) {
varIsUsedInRhs = true;
- break;
+ return ChildrenToVisit::done;
}
if (Token::simpleMatch(t->previous(),"sizeof ("))
- continue;
- tokens.push(t->astOperand1());
- tokens.push(t->astOperand2());
- }
+ return ChildrenToVisit::none;
+ return ChildrenToVisit::op1_and_op2;
+ });
if (!varIsUsedInRhs)
return true;
} else {
From 7973fd843c46c3b2a7e1ff7fb4955facb727e46d Mon Sep 17 00:00:00 2001
From: Rikard Falkeborn
Date: Mon, 20 Jul 2020 11:15:18 +0200
Subject: [PATCH 44/58] Refactor: Simplify checkSignConversion
The loop only checks astoperand1 and astoperand2. Simplify the condition
to loop over these instead of using a stack. Also, add a testcase for
when astoperand2 is negative.
---
lib/checktype.cpp | 8 ++------
test/testtype.cpp | 3 +++
2 files changed, 5 insertions(+), 6 deletions(-)
diff --git a/lib/checktype.cpp b/lib/checktype.cpp
index 6ac2709b6..e46a8142d 100644
--- a/lib/checktype.cpp
+++ b/lib/checktype.cpp
@@ -239,12 +239,8 @@ void CheckType::checkSignConversion()
continue;
// Check if an operand can be negative..
- std::stack tokens;
- tokens.push(tok->astOperand1());
- tokens.push(tok->astOperand2());
- while (!tokens.empty()) {
- const Token *tok1 = tokens.top();
- tokens.pop();
+ const Token * astOperands[] = { tok->astOperand1(), tok->astOperand2() };
+ for (const Token * tok1 : astOperands) {
if (!tok1)
continue;
const ValueFlow::Value *negativeValue = tok1->getValueLE(-1,mSettings);
diff --git a/test/testtype.cpp b/test/testtype.cpp
index e5ea6f2bd..4a121a09a 100644
--- a/test/testtype.cpp
+++ b/test/testtype.cpp
@@ -252,6 +252,9 @@ private:
check("x = -4 * (unsigned)y;");
ASSERT_EQUALS("[test.cpp:1]: (warning) Expression '-4' has a negative value. That is converted to an unsigned value and used in an unsigned calculation.\n", errout.str());
+ check("x = (unsigned)y * -4;");
+ ASSERT_EQUALS("[test.cpp:1]: (warning) Expression '-4' has a negative value. That is converted to an unsigned value and used in an unsigned calculation.\n", errout.str());
+
check("unsigned int dostuff(int x) {\n" // x is signed
" if (x==0) {}\n"
" return (x-1)*sizeof(int);\n"
From 09241030c38e435d046e69e04ae0cd1878cc1db5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Mon, 20 Jul 2020 11:59:28 +0200
Subject: [PATCH 45/58] GUI: Add online help
---
gui/gui.pro | 4 ++
gui/help/index.html | 52 ++++++++++++++++++
gui/help/online-help.qhp | 22 ++++++--
gui/helpdialog.cpp | 52 ++++++++++++++++++
gui/helpdialog.h | 36 +++++++++++++
gui/helpdialog.ui | 110 +++++++++++++++++++++++++++++++++++++++
gui/mainwindow.cpp | 12 +++--
7 files changed, 279 insertions(+), 9 deletions(-)
create mode 100644 gui/help/index.html
create mode 100644 gui/helpdialog.cpp
create mode 100644 gui/helpdialog.h
create mode 100644 gui/helpdialog.ui
diff --git a/gui/gui.pro b/gui/gui.pro
index 7052e4b90..b8d53034d 100644
--- a/gui/gui.pro
+++ b/gui/gui.pro
@@ -10,6 +10,7 @@ INCLUDEPATH += . \
../externals/z3/include
QT += widgets
QT += printsupport
+QT += help
contains(LINKCORE, [yY][eE][sS]) {
LIBS += -l../bin/cppcheck-core
@@ -61,6 +62,7 @@ FORMS = about.ui \
application.ui \
file.ui \
functioncontractdialog.ui \
+ helpdialog.ui \
mainwindow.ui \
projectfiledialog.ui \
resultsview.ui \
@@ -110,6 +112,7 @@ HEADERS += aboutdialog.h \
filelist.h \
fileviewdialog.h \
functioncontractdialog.h \
+ helpdialog.h \
mainwindow.h \
platforms.h \
printablereport.h \
@@ -150,6 +153,7 @@ SOURCES += aboutdialog.cpp \
filelist.cpp \
fileviewdialog.cpp \
functioncontractdialog.cpp \
+ helpdialog.cpp \
main.cpp \
mainwindow.cpp\
platforms.cpp \
diff --git a/gui/help/index.html b/gui/help/index.html
new file mode 100644
index 000000000..b466002dc
--- /dev/null
+++ b/gui/help/index.html
@@ -0,0 +1,52 @@
+
+
+Cppcheck GUI
+
+
+
+Cppcheck GUI
+
+With the Cppcheck GUI you can analyze your code.
+
+Quick walk through
+
+Step 1: Create a project.
+
+Create a new project.
+
+In the Paths and Defines tab, it is recommended that you import your project file at the top.
+
+In the Types and Functions tab, try to activate all 3rd party libraries you use (windows, posix, ...).
+
+In the Analysis tab, leave the default settings to start with.
+
+In the Warnings options tab, leave the default settings to start with.
+
+In the Addons tab, leave the default settings to start with.
+
+
+Step 2: Analyze code.
+
+When the project file has been created, the analysis will start automatically.
+
+While analysis is performed in the background, you can investigate the warnings.
+
+
+Step 3: Investigate warnings.
+
+In the toolbar you choose what types of warnings you want to see (error/warning/style/performance/portability/information).
+
+All warnings are shown in a list. If you select a warning in the list, then details about that warning is shown below the list.
+
+If you right click on warning(s) then you get a context menu.
+
+
+Step 4: Configuration.
+
+It is possible to improve configuration to get better analysis. The automatic assumptions are conservative and through configuration those automatic assumptions can be avoided.
+
+TODO: library, contracts
+
+
+
+
diff --git a/gui/help/online-help.qhp b/gui/help/online-help.qhp
index 16ab79981..4bbe8074d 100644
--- a/gui/help/online-help.qhp
+++ b/gui/help/online-help.qhp
@@ -4,11 +4,25 @@
doc
-
+
-
+
+
+
+
- manual.html
+ index.html
+ investigating-warnings.html
+ preferences.html
+ projectfiledialog.html
+ standalone-analysis.html
+ tagging.html
-
+
diff --git a/gui/helpdialog.cpp b/gui/helpdialog.cpp
new file mode 100644
index 000000000..920671834
--- /dev/null
+++ b/gui/helpdialog.cpp
@@ -0,0 +1,52 @@
+#include "helpdialog.h"
+#include "ui_helpdialog.h"
+
+#include
+#include
+#include
+
+void HelpBrowser::setHelpEngine(QHelpEngine *helpEngine)
+{
+ mHelpEngine = helpEngine;
+}
+
+QVariant HelpBrowser::loadResource(int type, const QUrl &name){
+ if (name.scheme() == "qthelp") {
+ QString url(name.toString());
+ while (url.indexOf("/./") > 0)
+ url.remove(url.indexOf("/./"), 2);
+ return QVariant(mHelpEngine->fileData(QUrl(url)));
+ }
+ return QTextBrowser::loadResource(type, name);
+}
+
+HelpDialog::HelpDialog(QWidget *parent) :
+ QDialog(parent),
+ mUi(new Ui::HelpDialog)
+{
+ mUi->setupUi(this);
+
+ mHelpEngine = new QHelpEngine(QApplication::applicationDirPath() + "/online-help.qhc");
+ mHelpEngine->setupData();
+
+ mUi->contents->addWidget(mHelpEngine->contentWidget());
+ mUi->index->addWidget(mHelpEngine->indexWidget());
+
+ mUi->textBrowser->setHelpEngine(mHelpEngine);
+
+ mUi->textBrowser->setSource(QUrl("qthelp://cppcheck.sourceforge.net/doc/index.html"));
+ connect(mHelpEngine->contentWidget(),
+ SIGNAL(linkActivated(QUrl)),
+ mUi->textBrowser,
+ SLOT(setSource(QUrl)));
+
+ connect(mHelpEngine->indexWidget(),
+ SIGNAL(linkActivated(QUrl, QString)),
+ mUi->textBrowser,
+ SLOT(setSource(QUrl)));
+}
+
+HelpDialog::~HelpDialog()
+{
+ delete mUi;
+}
diff --git a/gui/helpdialog.h b/gui/helpdialog.h
new file mode 100644
index 000000000..36e1e7954
--- /dev/null
+++ b/gui/helpdialog.h
@@ -0,0 +1,36 @@
+#ifndef HELPDIALOG_H
+#define HELPDIALOG_H
+
+#include
+#include
+
+namespace Ui {
+class HelpDialog;
+}
+
+class QHelpEngine;
+
+class HelpBrowser : public QTextBrowser
+{
+public:
+ HelpBrowser(QWidget* parent = 0) : QTextBrowser(parent), mHelpEngine(nullptr) {}
+ void setHelpEngine(QHelpEngine *helpEngine);
+ QVariant loadResource (int type, const QUrl& name);
+private:
+ QHelpEngine* mHelpEngine;
+};
+
+class HelpDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit HelpDialog(QWidget *parent = nullptr);
+ ~HelpDialog();
+
+private:
+ Ui::HelpDialog *mUi;
+ QHelpEngine* mHelpEngine;
+};
+
+#endif // HELPDIALOG_H
diff --git a/gui/helpdialog.ui b/gui/helpdialog.ui
new file mode 100644
index 000000000..bc5ccc706
--- /dev/null
+++ b/gui/helpdialog.ui
@@ -0,0 +1,110 @@
+
+
+ HelpDialog
+
+
+
+ 0
+ 0
+ 635
+ 446
+
+
+
+ Cppcheck GUI help
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+
+ 200
+ 16777215
+
+
+
+ 0
+
+
+
+ Contents
+
+
+
-
+
+
+
+
+
+
+ Index
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Close
+
+
+
+
+
+
+
+ HelpBrowser
+ QTextBrowser
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ HelpDialog
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ HelpDialog
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp
index d98ea4b6e..5a1e8189f 100644
--- a/gui/mainwindow.cpp
+++ b/gui/mainwindow.cpp
@@ -34,20 +34,21 @@
#include "applicationlist.h"
#include "aboutdialog.h"
#include "common.h"
-#include "threadhandler.h"
+#include "filelist.h"
#include "fileviewdialog.h"
#include "functioncontractdialog.h"
+#include "helpdialog.h"
+#include "librarydialog.h"
#include "projectfile.h"
#include "projectfiledialog.h"
#include "report.h"
#include "scratchpad.h"
+#include "showtypes.h"
#include "statsdialog.h"
#include "settingsdialog.h"
+#include "threadhandler.h"
#include "threadresult.h"
#include "translationhandler.h"
-#include "filelist.h"
-#include "showtypes.h"
-#include "librarydialog.h"
static const QString OnlineHelpURL("http://cppcheck.net/manual.html");
static const QString compile_commands_json("compile_commands.json");
@@ -1465,7 +1466,8 @@ void MainWindow::openHelpContents()
void MainWindow::openOnlineHelp()
{
- QDesktopServices::openUrl(QUrl(OnlineHelpURL));
+ HelpDialog *helpDialog = new HelpDialog;
+ helpDialog->showMaximized();
}
void MainWindow::openProjectFile()
From 89ba12d9b38dece6396b92e94cc905672027010d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Mon, 20 Jul 2020 12:04:51 +0200
Subject: [PATCH 46/58] Remove old build.bat file
---
build.bat | 72 -------------------------------------------------------
1 file changed, 72 deletions(-)
delete mode 100644 build.bat
diff --git a/build.bat b/build.bat
deleted file mode 100644
index 9c075594e..000000000
--- a/build.bat
+++ /dev/null
@@ -1,72 +0,0 @@
-@echo off
-REM A simple script to build different cppcheck targets from project root
-REM folder. This script can be run from VS prompt or Qt prompt.
-REM
-REM Usage: build [release|debug]
-REM where is any of cppcheck/gui/tests/all
-REM release or debug is the configuration
-REM all-target builds both cppcheck and gui.
-REM
-REM Run the command before build.bat to enable rules using pcre:
-REM set HAVE_RULES=yes
-REM
-REM TODO:
-REM - run tests too
-
-pushd %~dp0
-
-if "%1" == "" goto help
-
-REM Qt prompt sets QMAKESPEC
-if "%QMAKESPEC%" == "" (
-REM parse qmakespec to see if it's some msvc
- if "%QMAKESPEC:~6,4%" == "msvc" (
- set MAKE=nmake
- ) else (
- set MAKE=mingw32-make
- )
-) else (
- set MAKE=nmake
-)
-
-if "%2" == "" set TARGET=release
-if "%2" == "debug" set TARGET=debug
-if "%2" == "release" set TARGET=release
-
-if "%1" == "all" goto cppcheck
-if "%1" == "cppcheck" goto cppcheck
-if "%1" == "gui" goto gui
-if "%1" == "tests" goto tests
-goto help
-
-:cppcheck
-pushd cli
-qmake -config %TARGET% HAVE_RULES=%HAVE_RULES%
-%MAKE%
-popd
-if "%1" == "all" goto gui
-goto end
-
-:gui
-pushd gui
-qmake -config %TARGET% HAVE_RULES=%HAVE_RULES%
-%MAKE%
-lupdate -no-obsolete gui.pro
-lrelease gui.pro
-popd
-goto end
-
-:tests
-pushd test
-qmake -config %TARGET% HAVE_RULES=%HAVE_RULES%
-%MAKE%
-popd
-goto end
-
-:help
-echo Syntax: build ^ [debug^|release]
-echo where ^ is any of cppcheck/gui/tests/all
-echo debug or release define used configuration
-echo all- target builds both cppcheck and gui.
-
-:end
From 65742b9779135e7ddcf94040368ae7418df60fef Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Mon, 20 Jul 2020 12:43:08 +0200
Subject: [PATCH 47/58] gui.pro: build help
---
gui/gui.pro | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/gui/gui.pro b/gui/gui.pro
index b8d53034d..6fe367f7e 100644
--- a/gui/gui.pro
+++ b/gui/gui.pro
@@ -12,6 +12,13 @@ QT += widgets
QT += printsupport
QT += help
+# Build online help
+onlinehelp.target = online-help.qhc
+#onlinehelp.depends = $$PWD/help/online-help.qhp
+onlinehelp.commands = qhelpgenerator $$PWD/help/online-help.qhp -o online-help.qch ; qhelpgenerator $$PWD/help/online-help.qhcp -o online-help.qhc
+QMAKE_EXTRA_TARGETS += onlinehelp
+PRE_TARGETDEPS += online-help.qhc
+
contains(LINKCORE, [yY][eE][sS]) {
LIBS += -l../bin/cppcheck-core
DEFINES += CPPCHECKLIB_IMPORT
From bdb7db0fd5a69e2c6636792ea429fc54ea1749b1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Mon, 20 Jul 2020 19:04:47 +0200
Subject: [PATCH 48/58] Travis: Try to install qt help
---
.travis.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.travis.yml b/.travis.yml
index 1552c56a5..b901ba75d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -20,7 +20,7 @@ env:
before_install:
# install needed deps
- travis_retry sudo apt-get update -qq
- - travis_retry sudo apt-get install -qq python3-pip qt5-default qt5-qmake qtbase5-dev qtcreator libxml2-utils libpcre3 gdb unzip wx-common xmlstarlet python3-dev liblua5.3-dev libcurl3 libcairo2-dev libsigc++-2.0-dev tidy libopencv-dev libz3-dev
+ - travis_retry sudo apt-get install -qq python3-pip qt5-default qt5-qmake qtbase5-dev qtcreator qttools5-dev qttools5-dev-tools libxml2-utils libpcre3 gdb unzip wx-common xmlstarlet python3-dev liblua5.3-dev libcurl3 libcairo2-dev libsigc++-2.0-dev tidy libopencv-dev libz3-dev
# Python 2 modules
- travis_retry python2 -m pip install --user pytest==4.6.4
- travis_retry python2 -m pip install --user pylint
From 1c39bed5b0ec5b761d4d5d72812bd7cf2bd065ff Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Mon, 20 Jul 2020 19:18:06 +0200
Subject: [PATCH 49/58] buildhelp.bat: remove old script
---
gui/help/buildhelp.bat | 10 ----------
1 file changed, 10 deletions(-)
delete mode 100644 gui/help/buildhelp.bat
diff --git a/gui/help/buildhelp.bat b/gui/help/buildhelp.bat
deleted file mode 100644
index b0cfbcc0d..000000000
--- a/gui/help/buildhelp.bat
+++ /dev/null
@@ -1,10 +0,0 @@
-@echo off
-
-pushd %~dp0
-
-if exist online-help.qhc del online-help.qhc
-if exist online-help.qch del online-help.qch
-
-qcollectiongenerator online-help.qhcp -o online-help.qhc
-
-popd
From d7f9dc25cd1cf3b5470bb014f2d07f8262a76153 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Mon, 20 Jul 2020 19:19:37 +0200
Subject: [PATCH 50/58] Quick fix for Travis problems. Skip building gui
online-help.
---
gui/gui.pro | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/gui/gui.pro b/gui/gui.pro
index 6fe367f7e..3fd8663fb 100644
--- a/gui/gui.pro
+++ b/gui/gui.pro
@@ -13,11 +13,10 @@ QT += printsupport
QT += help
# Build online help
-onlinehelp.target = online-help.qhc
-#onlinehelp.depends = $$PWD/help/online-help.qhp
-onlinehelp.commands = qhelpgenerator $$PWD/help/online-help.qhp -o online-help.qch ; qhelpgenerator $$PWD/help/online-help.qhcp -o online-help.qhc
-QMAKE_EXTRA_TARGETS += onlinehelp
-PRE_TARGETDEPS += online-help.qhc
+#onlinehelp.target = online-help.qhc
+#onlinehelp.commands = qhelpgenerator $$PWD/help/online-help.qhp -o online-help.qch ; qhelpgenerator $$PWD/help/online-help.qhcp -o online-help.qhc
+#QMAKE_EXTRA_TARGETS += onlinehelp
+#PRE_TARGETDEPS += online-help.qhc
contains(LINKCORE, [yY][eE][sS]) {
LIBS += -l../bin/cppcheck-core
From 5fca5830c53da2d68d9a5ed10eb15d26345c8565 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Mon, 20 Jul 2020 21:41:46 +0200
Subject: [PATCH 51/58] astyle formatting
[ci skip]
---
gui/helpdialog.cpp | 3 ++-
gui/helpdialog.h | 10 ++++------
2 files changed, 6 insertions(+), 7 deletions(-)
diff --git a/gui/helpdialog.cpp b/gui/helpdialog.cpp
index 920671834..68b306509 100644
--- a/gui/helpdialog.cpp
+++ b/gui/helpdialog.cpp
@@ -10,7 +10,8 @@ void HelpBrowser::setHelpEngine(QHelpEngine *helpEngine)
mHelpEngine = helpEngine;
}
-QVariant HelpBrowser::loadResource(int type, const QUrl &name){
+QVariant HelpBrowser::loadResource(int type, const QUrl &name)
+{
if (name.scheme() == "qthelp") {
QString url(name.toString());
while (url.indexOf("/./") > 0)
diff --git a/gui/helpdialog.h b/gui/helpdialog.h
index 36e1e7954..e37784437 100644
--- a/gui/helpdialog.h
+++ b/gui/helpdialog.h
@@ -5,23 +5,21 @@
#include
namespace Ui {
-class HelpDialog;
+ class HelpDialog;
}
class QHelpEngine;
-class HelpBrowser : public QTextBrowser
-{
+class HelpBrowser : public QTextBrowser {
public:
HelpBrowser(QWidget* parent = 0) : QTextBrowser(parent), mHelpEngine(nullptr) {}
void setHelpEngine(QHelpEngine *helpEngine);
- QVariant loadResource (int type, const QUrl& name);
+ QVariant loadResource(int type, const QUrl& name);
private:
QHelpEngine* mHelpEngine;
};
-class HelpDialog : public QDialog
-{
+class HelpDialog : public QDialog {
Q_OBJECT
public:
From a68d9e75ef4024c03a4673524033afc6d8529795 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Mon, 20 Jul 2020 22:06:07 +0200
Subject: [PATCH 52/58] GUI: In ProjectFileDialog; updated choice of vs
configurations
---
gui/projectfiledialog.cpp | 64 ++++++++++++++++++++++++---------------
gui/projectfiledialog.h | 4 +--
gui/projectfiledialog.ui | 12 +-------
3 files changed, 42 insertions(+), 38 deletions(-)
diff --git a/gui/projectfiledialog.cpp b/gui/projectfiledialog.cpp
index 70452845a..c084ac658 100644
--- a/gui/projectfiledialog.cpp
+++ b/gui/projectfiledialog.cpp
@@ -62,6 +62,8 @@ static const int numberOfBuiltinPlatforms = sizeof(builtinPlatforms) / sizeof(bu
QStringList ProjectFileDialog::getProjectConfigs(const QString &fileName)
{
+ if (!fileName.endsWith(".sln") && !fileName.endsWith(".vcxproj"))
+ return QStringList();
QStringList ret;
ImportProject importer;
Settings projSettings;
@@ -244,8 +246,12 @@ static void updateAddonCheckBox(QCheckBox *cb, const ProjectFile *projectFile, c
void ProjectFileDialog::checkAllVSConfigs()
{
- if (mUI.mChkAllVsConfigs->isChecked())
- mUI.mListVsConfigs->selectAll();
+ if (mUI.mChkAllVsConfigs->isChecked()) {
+ for (int row = 0; row < mUI.mListVsConfigs->count(); ++row) {
+ QListWidgetItem *item = mUI.mListVsConfigs->item(row);
+ item->setCheckState(Qt::Checked);
+ }
+ }
mUI.mListVsConfigs->setEnabled(!mUI.mChkAllVsConfigs->isChecked());
}
@@ -259,6 +265,14 @@ void ProjectFileDialog::loadFromProjectFile(const ProjectFile *projectFile)
setCheckPaths(projectFile->getCheckPaths());
setImportProject(projectFile->getImportProject());
mUI.mChkAllVsConfigs->setChecked(projectFile->getAnalyzeAllVsConfigs());
+ setProjectConfigurations(getProjectConfigs(mUI.mEditImportProject->text()));
+ for (int row = 0; row < mUI.mListVsConfigs->count(); ++row) {
+ QListWidgetItem *item = mUI.mListVsConfigs->item(row);
+ if (projectFile->getAnalyzeAllVsConfigs() || projectFile->getVsConfigurations().contains(item->text()))
+ item->setCheckState(Qt::Checked);
+ else
+ item->setCheckState(Qt::Unchecked);
+ }
mUI.mCheckHeaders->setChecked(projectFile->getCheckHeaders());
mUI.mCheckUnusedTemplates->setChecked(projectFile->getCheckUnusedTemplates());
mUI.mMaxCtuDepth->setValue(projectFile->getMaxCtuDepth());
@@ -342,17 +356,6 @@ void ProjectFileDialog::loadFromProjectFile(const ProjectFile *projectFile)
}
mUI.mEditTags->setText(projectFile->getTags().join(';'));
updatePathsAndDefines();
- if (mUI.mEditImportProject->text().endsWith(".sln") || mUI.mEditImportProject->text().endsWith(".vcxproj")) {
- setVsConfigurations(getProjectConfigs(mUI.mEditImportProject->text()));
- foreach (const QString &cfg, projectFile->getVsConfigurations()) {
- QList items = mUI.mListVsConfigs->findItems(cfg, Qt::MatchFlag::MatchExactly);
- items[0]->setSelected(true);
- }
- } else {
- mUI.mListVsConfigs->clear();
- mUI.mListVsConfigs->setEnabled(false);
- }
-
}
void ProjectFileDialog::saveToProjectFile(ProjectFile *projectFile) const
@@ -361,6 +364,7 @@ void ProjectFileDialog::saveToProjectFile(ProjectFile *projectFile) const
projectFile->setBuildDir(getBuildDir());
projectFile->setImportProject(getImportProject());
projectFile->setAnalyzeAllVsConfigs(mUI.mChkAllVsConfigs->isChecked());
+ projectFile->setVSConfigurations(getProjectConfigurations());
projectFile->setCheckHeaders(mUI.mCheckHeaders->isChecked());
projectFile->setCheckUnusedTemplates(mUI.mCheckUnusedTemplates->isChecked());
projectFile->setMaxCtuDepth(mUI.mMaxCtuDepth->value());
@@ -414,7 +418,6 @@ void ProjectFileDialog::saveToProjectFile(ProjectFile *projectFile) const
projectFile->setClangAnalyzer(mUI.mToolClangAnalyzer->isChecked());
projectFile->setClangTidy(mUI.mToolClangTidy->isChecked());
projectFile->setTags(mUI.mEditTags->text().split(";", QString::SkipEmptyParts));
- projectFile->setVSConfigurations(getVsConfigurations());
}
void ProjectFileDialog::ok()
@@ -460,6 +463,7 @@ void ProjectFileDialog::updatePathsAndDefines()
{
const QString &fileName = mUI.mEditImportProject->text();
bool importProject = !fileName.isEmpty();
+ bool hasConfigs = fileName.endsWith(".sln") || fileName.endsWith(".vcxproj");
mUI.mBtnClearImportProject->setEnabled(importProject);
mUI.mListCheckPaths->setEnabled(!importProject);
mUI.mListIncludeDirs->setEnabled(!importProject);
@@ -473,9 +477,9 @@ void ProjectFileDialog::updatePathsAndDefines()
mUI.mBtnRemoveInclude->setEnabled(!importProject);
mUI.mBtnIncludeUp->setEnabled(!importProject);
mUI.mBtnIncludeDown->setEnabled(!importProject);
- mUI.mChkAllVsConfigs->setEnabled(fileName.endsWith(".sln") || fileName.endsWith(".vcxproj"));
- mUI.mListVsConfigs->setEnabled(fileName.endsWith(".sln") || fileName.endsWith(".vcxproj"));
- if (!mUI.mListVsConfigs->isEnabled())
+ mUI.mChkAllVsConfigs->setEnabled(hasConfigs);
+ mUI.mListVsConfigs->setEnabled(hasConfigs && !mUI.mChkAllVsConfigs->isChecked());
+ if (!hasConfigs)
mUI.mListVsConfigs->clear();
}
@@ -499,24 +503,34 @@ void ProjectFileDialog::browseImportProject()
if (!fileName.isEmpty()) {
mUI.mEditImportProject->setText(dir.relativeFilePath(fileName));
updatePathsAndDefines();
- setVsConfigurations(getProjectConfigs(fileName));
- mUI.mListVsConfigs->selectAll();
+ setProjectConfigurations(getProjectConfigs(fileName));
+ for (int row = 0; row < mUI.mListVsConfigs->count(); ++row) {
+ QListWidgetItem *item = mUI.mListVsConfigs->item(row);
+ item->setCheckState(Qt::Checked);
+ }
}
}
-QStringList ProjectFileDialog::getVsConfigurations() const
+QStringList ProjectFileDialog::getProjectConfigurations() const
{
QStringList configs;
- foreach (QListWidgetItem *item, mUI.mListVsConfigs->selectedItems())
- configs << item->text();
-
+ for (int row = 0; row < mUI.mListVsConfigs->count(); ++row) {
+ QListWidgetItem *item = mUI.mListVsConfigs->item(row);
+ if (item->checkState() == Qt::Checked)
+ configs << item->text();
+ }
return configs;
}
-void ProjectFileDialog::setVsConfigurations(const QStringList &configs)
+void ProjectFileDialog::setProjectConfigurations(const QStringList &configs)
{
mUI.mListVsConfigs->clear();
- mUI.mListVsConfigs->addItems(configs);
+ mUI.mListVsConfigs->setEnabled(!configs.isEmpty() && !mUI.mChkAllVsConfigs->isChecked());
+ foreach (const QString &cfg, configs) {
+ QListWidgetItem* item = new QListWidgetItem(cfg, mUI.mListVsConfigs);
+ item->setFlags(item->flags() | Qt::ItemIsUserCheckable); // set checkable flag
+ item->setCheckState(Qt::Unchecked);
+ }
}
QString ProjectFileDialog::getImportProject() const
diff --git a/gui/projectfiledialog.h b/gui/projectfiledialog.h
index fef320a4f..d7ad84d24 100644
--- a/gui/projectfiledialog.h
+++ b/gui/projectfiledialog.h
@@ -58,8 +58,8 @@ private:
*/
QString getRootPath() const;
- QStringList getVsConfigurations() const;
- void setVsConfigurations(const QStringList &configs);
+ QStringList getProjectConfigurations() const;
+ void setProjectConfigurations(const QStringList &configs);
QString getImportProject() const;
diff --git a/gui/projectfiledialog.ui b/gui/projectfiledialog.ui
index 31884ce91..879ca9261 100644
--- a/gui/projectfiledialog.ui
+++ b/gui/projectfiledialog.ui
@@ -130,17 +130,7 @@
-
-
-
-
- 16777215
- 100
-
-
-
- QAbstractItemView::MultiSelection
-
-
+
From ae0ad171520fcfc63df2108d5bf94fdfa3cc4260 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?=
Date: Tue, 21 Jul 2020 10:30:50 +0200
Subject: [PATCH 53/58] GUI: quick walk through in online-help
---
gui/help/images/walkthrough-analysis.png | Bin 0 -> 142639 bytes
.../images/walkthrough-import-project.png | Bin 0 -> 12437 bytes
gui/help/images/walkthrough-library.png | Bin 0 -> 4743 bytes
gui/help/images/walkthrough-new-project.png | Bin 0 -> 10712 bytes
.../images/walkthrough-toolbar-severities.png | Bin 0 -> 6693 bytes
gui/help/images/walkthrough-warning-menu.png | Bin 0 -> 10339 bytes
gui/help/index.html | 39 ----------
gui/help/online-help.qhp | 8 ++
gui/help/walkthrough.html | 69 ++++++++++++++++++
gui/helpdialog.ui | 45 +-----------
10 files changed, 78 insertions(+), 83 deletions(-)
create mode 100644 gui/help/images/walkthrough-analysis.png
create mode 100644 gui/help/images/walkthrough-import-project.png
create mode 100644 gui/help/images/walkthrough-library.png
create mode 100644 gui/help/images/walkthrough-new-project.png
create mode 100644 gui/help/images/walkthrough-toolbar-severities.png
create mode 100644 gui/help/images/walkthrough-warning-menu.png
create mode 100644 gui/help/walkthrough.html
diff --git a/gui/help/images/walkthrough-analysis.png b/gui/help/images/walkthrough-analysis.png
new file mode 100644
index 0000000000000000000000000000000000000000..94efb2890d80f5d4a1ea5e1c0d127b81b5228b76
GIT binary patch
literal 142639
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⪙@vuiUSx9bjOp5d|oo1Poi*BXU02B-fF(cdDnE0I5i#Jety
zpA1wlD5cTEijGU2QHIjE27ezci_F_0Je3>3^54T;TF7u*PTx#l2^=o4<`}39D18sk
zCnSjM}z?Z1|
z-5!XX4)hmwIX24?5`0c8K#+392%Tvif68=Q=zj&m!Vb0nJ;49G9uTT?5MYrSl1p5v
z)*D2is9M7s^txmBXTB9fB%_34w@2TBJ}V4~l)Nfds)koYA0@$1{^!^H+uNZBathMW
zb>wwvAy5<*I;&9M^CfrsZ_)j?pby5btO^3l{~4fvN#zU=0Iica{=C#26?@JsUTFyM
zWSNaOK#qCUa(kxB0Ta%d(5%tm!yj6M`bxt