diff --git a/gui/erroritem.cpp b/gui/erroritem.cpp index d8692559e..b0ef6a1e2 100644 --- a/gui/erroritem.cpp +++ b/gui/erroritem.cpp @@ -40,7 +40,9 @@ ErrorItem::ErrorItem() } ErrorItem::ErrorItem(const ErrorLogger::ErrorMessage &errmsg) - : errorId(QString::fromStdString(errmsg.id)) + : file0(QString::fromStdString(errmsg.file0)) + , function(QString::fromStdString(errmsg.function)) + , errorId(QString::fromStdString(errmsg.id)) , severity(errmsg.severity) , inconclusive(errmsg.inconclusive) , summary(QString::fromStdString(errmsg.shortMessage())) diff --git a/gui/erroritem.h b/gui/erroritem.h index 342f33341..35dbc1f64 100644 --- a/gui/erroritem.h +++ b/gui/erroritem.h @@ -80,6 +80,7 @@ public: QString tool() const; QString file0; + QString function; QString errorId; Severity::SeverityType severity; bool inconclusive; diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index b2099eb86..45e360903 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -142,6 +142,7 @@ MainWindow::MainWindow(TranslationHandler* th, QSettings* settings) : 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::addFunctionContract, this, &MainWindow::addFunctionContract); connect(mUI.mMenuView, &QMenu::aboutToShow, this, &MainWindow::aboutToShowViewMenu); // File menu @@ -844,6 +845,8 @@ Settings MainWindow::getCppcheckSettings() result.bugHunting = mProjectFile->bugHunting; result.bugHuntingReport = " "; + result.functionContracts = mProjectFile->getFunctionContracts(); + const QStringList undefines = mProjectFile->getUndefines(); foreach (QString undefine, undefines) result.userUndefs.insert(undefine.toStdString()); @@ -1790,3 +1793,20 @@ void MainWindow::suppressIds(QStringList ids) mProjectFile->setSuppressions(suppressions); mProjectFile->write(); } + +void MainWindow::addFunctionContract(QString function) +{ + if (!mProjectFile) + return; + bool ok; + const QString expects = QInputDialog::getText(this, + tr("Add contract"), + "Function:" + function + "\nExpects:", + QLineEdit::Normal, + QString(), + &ok); + if (ok) { + mProjectFile->setFunctionContract(function, expects); + mProjectFile->write(); + } +} diff --git a/gui/mainwindow.h b/gui/mainwindow.h index ab8d52bda..e281597e4 100644 --- a/gui/mainwindow.h +++ b/gui/mainwindow.h @@ -225,6 +225,8 @@ protected slots: /** Suppress error ids */ void suppressIds(QStringList ids); + /** Add contract for function */ + void addFunctionContract(QString function); private: /** Get filename for last results */ diff --git a/gui/projectfile.cpp b/gui/projectfile.cpp index e27b9eda7..b3b9a8a30 100644 --- a/gui/projectfile.cpp +++ b/gui/projectfile.cpp @@ -55,6 +55,7 @@ void ProjectFile::clear() mUndefines.clear(); mPaths.clear(); mExcludedPaths.clear(); + mFunctionContracts.clear(); mLibraries.clear(); mPlatform.clear(); mSuppressions.clear(); @@ -145,6 +146,10 @@ bool ProjectFile::read(const QString &filename) if (xmlReader.name() == CppcheckXml::IgnoreElementName) readExcludes(xmlReader); + // Function contracts + if (xmlReader.name() == CppcheckXml::FunctionContracts) + readFunctionContracts(xmlReader); + // Find libraries list from inside project element if (xmlReader.name() == CppcheckXml::LibrariesElementName) readStringList(mLibraries, xmlReader, CppcheckXml::LibraryElementName); @@ -477,6 +482,43 @@ void ProjectFile::readExcludes(QXmlStreamReader &reader) } while (!allRead); } +void ProjectFile::readFunctionContracts(QXmlStreamReader &reader) +{ + QXmlStreamReader::TokenType type; + bool allRead = false; + do { + type = reader.readNext(); + switch (type) { + case QXmlStreamReader::StartElement: + if (reader.name().toString() == CppcheckXml::FunctionContract) { + QXmlStreamAttributes attribs = reader.attributes(); + QString function = attribs.value(QString(), CppcheckXml::ContractFunction).toString(); + QString expects = attribs.value(QString(), CppcheckXml::ContractExpects).toString(); + if (!function.isEmpty() && !expects.isEmpty()) + mFunctionContracts[function.toStdString()] = expects.toStdString(); + } + break; + + case QXmlStreamReader::EndElement: + if (reader.name().toString() == CppcheckXml::FunctionContracts) + allRead = true; + 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 (!allRead); +} + void ProjectFile::readVsConfigurations(QXmlStreamReader &reader) { QXmlStreamReader::TokenType type; @@ -653,6 +695,11 @@ void ProjectFile::setLibraries(const QStringList &libraries) mLibraries = libraries; } +void ProjectFile::setFunctionContract(QString function, QString expects) +{ + mFunctionContracts[function.toStdString()] = expects.toStdString(); +} + void ProjectFile::setPlatform(const QString &platform) { mPlatform = platform; @@ -796,6 +843,17 @@ bool ProjectFile::write(const QString &filename) CppcheckXml::LibrariesElementName, CppcheckXml::LibraryElementName); + if (!mFunctionContracts.empty()) { + xmlWriter.writeStartElement(CppcheckXml::FunctionContracts); + for (const auto contract: mFunctionContracts) { + xmlWriter.writeStartElement(CppcheckXml::FunctionContract); + xmlWriter.writeAttribute(CppcheckXml::ContractFunction, QString::fromStdString(contract.first)); + xmlWriter.writeAttribute(CppcheckXml::ContractExpects, QString::fromStdString(contract.second)); + xmlWriter.writeEndElement(); + } + xmlWriter.writeEndElement(); + } + if (!mSuppressions.isEmpty()) { xmlWriter.writeStartElement(CppcheckXml::SuppressionsElementName); foreach (const Suppressions::Suppression &suppression, mSuppressions) { diff --git a/gui/projectfile.h b/gui/projectfile.h index 84e475147..6dc958f24 100644 --- a/gui/projectfile.h +++ b/gui/projectfile.h @@ -19,6 +19,7 @@ #ifndef PROJECT_FILE_H #define PROJECT_FILE_H +#include #include #include #include @@ -200,6 +201,10 @@ public: mMaxCtuDepth = maxCtuDepth; } + const std::map getFunctionContracts() const { + return mFunctionContracts; + } + /** * @brief Get filename for the project file. * @return file name. @@ -264,6 +269,9 @@ public: */ void setLibraries(const QStringList &libraries); + /** Set contract for a function */ + void setFunctionContract(QString function, QString expects); + /** * @brief Set platform. * @param platform platform. @@ -377,6 +385,12 @@ protected: */ void readExcludes(QXmlStreamReader &reader); + /** + * @brief Read function contracts. + * @param reader XML stream reader. + */ + void readFunctionContracts(QXmlStreamReader &reader); + /** * @brief Read lists of Visual Studio configurations * @param reader XML stream reader. @@ -486,6 +500,8 @@ private: */ QStringList mLibraries; + std::map mFunctionContracts; + /** * @brief Platform */ diff --git a/gui/resultstree.cpp b/gui/resultstree.cpp index 4bccc4076..616e3fa3a 100644 --- a/gui/resultstree.cpp +++ b/gui/resultstree.cpp @@ -191,6 +191,7 @@ bool ResultsTree::addErrorItem(const ErrorItem &item) data["id"] = item.errorId; 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; @@ -212,7 +213,7 @@ bool ResultsTree::addErrorItem(const ErrorItem &item) if (!child_item) continue; - //Add user data to that item + // Add user data to that item QMap child_data; child_data["severity"] = ShowTypes::SeverityToShowType(line.severity); child_data["summary"] = line.summary; @@ -561,6 +562,7 @@ void ResultsTree::contextMenuEvent(QContextMenuEvent * e) QModelIndex index = indexAt(e->pos()); if (index.isValid()) { bool multipleSelection = false; + mSelectionModel = selectionModel(); if (mSelectionModel->selectedRows().count() > 1) multipleSelection = true; @@ -609,12 +611,20 @@ void ResultsTree::contextMenuEvent(QContextMenuEvent * e) menu.addSeparator(); } + const bool bughunting = !multipleSelection && mContextItem->data().toMap().value("id").toString().startsWith("bughunting"); + + if (bughunting) { + QAction *addContract = new QAction(tr("Add contract.."), &menu); + connect(addContract, SIGNAL(triggered()), this, SLOT(addContract())); + menu.addAction(addContract); + menu.addSeparator(); + } + //Create an action for the application QAction *recheckSelectedFiles = new QAction(tr("Recheck"), &menu); 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 *suppress = new QAction(tr("Suppress selected id(s)"), &menu); QAction *opencontainingfolder = new QAction(tr("Open containing folder"), &menu); if (multipleSelection) { @@ -632,7 +642,11 @@ void ResultsTree::contextMenuEvent(QContextMenuEvent * e) menu.addSeparator(); menu.addAction(hide); menu.addAction(hideallid); - menu.addAction(suppress); + if (!bughunting) { + QAction *suppress = new QAction(tr("Suppress selected id(s)"), &menu); + menu.addAction(suppress); + connect(suppress, SIGNAL(triggered()), this, SLOT(suppressSelectedIds())); + } menu.addSeparator(); menu.addAction(opencontainingfolder); @@ -640,7 +654,6 @@ 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(suppress, SIGNAL(triggered()), this, SLOT(suppressSelectedIds())); connect(opencontainingfolder, SIGNAL(triggered()), this, SLOT(openContainingFolder())); if (!mTags.isEmpty()) { @@ -1020,6 +1033,12 @@ void ResultsTree::openContainingFolder() } } +void ResultsTree::addContract() +{ + QString function = mContextItem->data().toMap().value("function").toString(); + emit addFunctionContract(function); +} + void ResultsTree::tagSelectedItems(const QString &tag) { if (!mSelectionModel) diff --git a/gui/resultstree.h b/gui/resultstree.h index bb9a1ee0e..805119297 100644 --- a/gui/resultstree.h +++ b/gui/resultstree.h @@ -208,6 +208,8 @@ signals: /** Suppress Ids */ void suppressIds(QStringList ids); + /** Add contract for function */ + void addFunctionContract(QString function); public slots: /** @@ -282,6 +284,11 @@ protected slots: */ void openContainingFolder(); + /** + * @brief Allow user to add contract to fix bughunting warning + */ + void addContract(); + /** * @brief Slot for selection change in the results tree. * diff --git a/gui/resultsview.cpp b/gui/resultsview.cpp index a79d21480..155d1077d 100644 --- a/gui/resultsview.cpp +++ b/gui/resultsview.cpp @@ -55,6 +55,7 @@ ResultsView::ResultsView(QWidget * parent) : 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::addFunctionContract, this, &ResultsView::addFunctionContract); connect(this, &ResultsView::showResults, mUI.mTree, &ResultsTree::showResults); connect(this, &ResultsView::showCppcheckResults, mUI.mTree, &ResultsTree::showCppcheckResults); connect(this, &ResultsView::showClangResults, mUI.mTree, &ResultsTree::showClangResults); diff --git a/gui/resultsview.h b/gui/resultsview.h index 90aa63101..511ad3cc0 100644 --- a/gui/resultsview.h +++ b/gui/resultsview.h @@ -228,6 +228,9 @@ signals: /** Suppress Ids */ void suppressIds(QStringList ids); + /** Add contract for function */ + void addFunctionContract(QString function); + /** * @brief Show/hide certain type of errors * Refreshes the tree. diff --git a/lib/errorlogger.h b/lib/errorlogger.h index a2d87d262..d91cb35b0 100644 --- a/lib/errorlogger.h +++ b/lib/errorlogger.h @@ -314,8 +314,10 @@ public: std::list callStack; std::string id; - /** source file (not header) */ + /** For GUI rechecking; source file (not header) */ std::string file0; + /** For GUI bug hunting; function name */ + std::string function; Severity::SeverityType severity; CWE cwe; diff --git a/lib/exprengine.cpp b/lib/exprengine.cpp index 259ff5cec..7ca6897df 100644 --- a/lib/exprengine.cpp +++ b/lib/exprengine.cpp @@ -158,8 +158,8 @@ namespace { class Data : public ExprEngine::DataBase { public: - Data(int *symbolValueIndex, const Tokenizer *tokenizer, const Settings *settings, const std::vector &callbacks, TrackExecution *trackExecution) - : DataBase(settings) + Data(int *symbolValueIndex, const Tokenizer *tokenizer, const Settings *settings, const std::string ¤tFunction, const std::vector &callbacks, TrackExecution *trackExecution) + : DataBase(currentFunction, settings) , symbolValueIndex(symbolValueIndex) , tokenizer(tokenizer) , callbacks(callbacks) @@ -172,6 +172,28 @@ namespace { const std::vector &callbacks; std::vector constraints; + void contractConstraints(const Function *function, ExprEngine::ValuePtr (*executeExpression)(const Token*, Data&)) { + const auto it = settings->functionContracts.find(currentFunction); + if (it == settings->functionContracts.end()) + return; + const std::string &expects = it->second; + TokenList tokenList(settings); + std::istringstream istr(expects); + tokenList.createTokens(istr); + tokenList.createAst(); + SymbolDatabase *symbolDatabase = const_cast(tokenizer->getSymbolDatabase()); + for (Token *tok = tokenList.front(); tok; tok = tok->next()) { + for (const Variable &arg: function->argumentList) { + if (arg.name() == tok->str()) { + tok->variable(&arg); + tok->varId(arg.declarationId()); + } + } + } + symbolDatabase->setValueTypeInTokenList(false, tokenList.front()); + constraints.push_back(executeExpression(tokenList.front()->astTop(), *this)); + } + void addError(int linenr) OVERRIDE { mTrackExecution->addError(linenr); } @@ -1756,13 +1778,17 @@ void ExprEngine::executeFunction(const Scope *functionScope, const Tokenizer *to // TODO.. what about functions in headers? return; + const std::string currentFunction = function->fullName(); + int symbolValueIndex = 0; TrackExecution trackExecution; - Data data(&symbolValueIndex, tokenizer, settings, callbacks, &trackExecution); + Data data(&symbolValueIndex, tokenizer, settings, currentFunction, callbacks, &trackExecution); for (const Variable &arg : function->argumentList) data.assignValue(functionScope->bodyStart, arg.declarationId(), createVariableValue(arg, data)); + data.contractConstraints(function, executeExpression1); + try { execute(functionScope->bodyStart, functionScope->bodyEnd, data); } catch (VerifyException &e) { @@ -1829,6 +1855,7 @@ void ExprEngine::runChecks(ErrorLogger *errorLogger, const Tokenizer *tokenizer, std::list callstack{settings->clang ? tok : tok->astParent()}; const char * const id = (tok->valueType() && tok->valueType()->isFloat()) ? "bughuntingDivByZeroFloat" : "bughuntingDivByZero"; ErrorLogger::ErrorMessage errmsg(callstack, &tokenizer->list, Severity::SeverityType::error, id, "There is division, cannot determine that there can't be a division by zero.", CWE(369), false); + errmsg.function = dataBase->currentFunction; errorLogger->reportErr(errmsg); } }; diff --git a/lib/exprengine.h b/lib/exprengine.h index 24cdd7b6c..062fa3e12 100644 --- a/lib/exprengine.h +++ b/lib/exprengine.h @@ -71,8 +71,12 @@ namespace ExprEngine { class DataBase { public: - explicit DataBase(const Settings *settings) : settings(settings) {} + explicit DataBase(const std::string ¤tFunction, const Settings *settings) + : currentFunction(currentFunction) + , settings(settings) { + } virtual std::string getNewSymbolName() = 0; + const std::string currentFunction; const Settings * const settings; virtual void addError(int linenr) { (void)linenr; diff --git a/lib/importproject.cpp b/lib/importproject.cpp index b2980f232..5f5daa171 100644 --- a/lib/importproject.cpp +++ b/lib/importproject.cpp @@ -1018,7 +1018,9 @@ bool ImportProject::importCppcheckGuiProject(std::istream &istr, Settings *setti if (strcmp(node->Name(), CppcheckXml::RootPathName) == 0 && node->Attribute(CppcheckXml::RootPathNameAttrib)) { temp.basePaths.push_back(joinRelativePath(path, node->Attribute(CppcheckXml::RootPathNameAttrib))); temp.relativePaths = true; - } else if (strcmp(node->Name(), CppcheckXml::BuildDirElementName) == 0) + } else if (strcmp(node->Name(), CppcheckXml::BugHunting) == 0) + temp.bugHunting = true; + else if (strcmp(node->Name(), CppcheckXml::BuildDirElementName) == 0) temp.buildDir = joinRelativePath(path, node->GetText() ? node->GetText() : ""); else if (strcmp(node->Name(), CppcheckXml::IncludeDirElementName) == 0) temp.includePaths = readXmlStringList(node, path, CppcheckXml::DirElementName, CppcheckXml::DirNameAttrib); @@ -1033,6 +1035,16 @@ bool ImportProject::importCppcheckGuiProject(std::istream &istr, Settings *setti paths = readXmlStringList(node, path, CppcheckXml::PathName, CppcheckXml::PathNameAttrib); else if (strcmp(node->Name(), CppcheckXml::ExcludeElementName) == 0) guiProject.excludedPaths = readXmlStringList(node, "", CppcheckXml::ExcludePathName, CppcheckXml::ExcludePathNameAttrib); + else if (strcmp(node->Name(), CppcheckXml::FunctionContracts) == 0) { + for (const tinyxml2::XMLElement *child = node->FirstChildElement(); child; child = child->NextSiblingElement()) { + if (strcmp(child->Name(), CppcheckXml::FunctionContract) == 0) { + const char *function = child->Attribute(CppcheckXml::ContractFunction); + const char *expects = child->Attribute(CppcheckXml::ContractExpects); + if (function && expects) + temp.functionContracts[function] = expects; + } + } + } else if (strcmp(node->Name(), CppcheckXml::IgnoreElementName) == 0) guiProject.excludedPaths = readXmlStringList(node, "", CppcheckXml::IgnorePathName, CppcheckXml::IgnorePathNameAttrib); else if (strcmp(node->Name(), CppcheckXml::LibrariesElementName) == 0) @@ -1099,6 +1111,8 @@ bool ImportProject::importCppcheckGuiProject(std::istream &istr, Settings *setti settings->checkUnusedTemplates = temp.checkUnusedTemplates; settings->maxCtuDepth = temp.maxCtuDepth; settings->safeChecks = temp.safeChecks; + settings->bugHunting = temp.bugHunting; + settings->functionContracts = temp.functionContracts; return true; } diff --git a/lib/importproject.h b/lib/importproject.h index aeb67c654..346b9aa3e 100644 --- a/lib/importproject.h +++ b/lib/importproject.h @@ -146,6 +146,10 @@ namespace CppcheckXml { const char ExcludeElementName[] = "exclude"; const char ExcludePathName[] = "path"; const char ExcludePathNameAttrib[] = "name"; + const char FunctionContracts[] = "function-contracts"; + const char FunctionContract[] = "contract"; + const char ContractFunction[] = "function"; + const char ContractExpects[] = "expects"; const char LibrariesElementName[] = "libraries"; const char LibraryElementName[] = "library"; const char PlatformElementName[] = "platform"; diff --git a/lib/settings.h b/lib/settings.h index d1b6d7fe6..96264e2fc 100644 --- a/lib/settings.h +++ b/lib/settings.h @@ -173,6 +173,8 @@ public: /** @brief Force checking the files with "too many" configurations (--force). */ bool force; + std::map functionContracts; + /** @brief List of include paths, e.g. "my/includes/" which should be used for finding include files inside source files. (-I) */ std::list includePaths; diff --git a/lib/symboldatabase.cpp b/lib/symboldatabase.cpp index 254fee065..359413602 100644 --- a/lib/symboldatabase.cpp +++ b/lib/symboldatabase.cpp @@ -2157,6 +2157,18 @@ Function::Function(const Token *tokenDef) { } +std::string Function::fullName() const +{ + std::string ret = name(); + for (const Scope *s = nestedIn; s; s = s->nestedIn) { + if (!s->className.empty()) + ret = s->className + "::" + ret; + } + ret += "("; + for (const Variable &arg : argumentList) + ret += (arg.index() == 0 ? "" : ",") + arg.name(); + return ret + ")"; +} static std::string qualifiedName(const Scope *scope) { @@ -5763,9 +5775,10 @@ static const Function *getOperatorFunction(const Token * const tok) return nullptr; } -void SymbolDatabase::setValueTypeInTokenList(bool reportDebugWarnings) +void SymbolDatabase::setValueTypeInTokenList(bool reportDebugWarnings, Token *tokens) { - Token * tokens = const_cast(mTokenizer)->list.front(); + if (!tokens) + tokens = const_cast(mTokenizer)->list.front(); for (Token *tok = tokens; tok; tok = tok->next()) tok->setValueType(nullptr); diff --git a/lib/symboldatabase.h b/lib/symboldatabase.h index 03f8e0b04..ae869b375 100644 --- a/lib/symboldatabase.h +++ b/lib/symboldatabase.h @@ -743,6 +743,8 @@ public: return tokenDef->str(); } + std::string fullName() const; + nonneg int argCount() const { return argumentList.size(); } @@ -1310,7 +1312,7 @@ public: void validateVariables() const; /** Set valuetype in provided tokenlist */ - void setValueTypeInTokenList(bool reportDebugWarnings); + void setValueTypeInTokenList(bool reportDebugWarnings, Token *tokens=nullptr); /** * Calculates sizeof value for given type.