diff --git a/gui/gui.pro b/gui/gui.pro index 505329e98..cfc4d921d 100644 --- a/gui/gui.pro +++ b/gui/gui.pro @@ -77,7 +77,8 @@ FORMS = about.ui \ librarydialog.ui \ libraryaddfunctiondialog.ui \ libraryeditargdialog.ui \ - newsuppressiondialog.ui + newsuppressiondialog.ui \ + variablecontractsdialog.ui TRANSLATIONS = cppcheck_de.ts \ cppcheck_es.ts \ @@ -134,6 +135,7 @@ HEADERS += aboutdialog.h \ threadresult.h \ translationhandler.h \ txtreport.h \ + variablecontractsdialog.h \ xmlreport.h \ xmlreportv2.h \ librarydialog.h \ @@ -176,6 +178,7 @@ SOURCES += aboutdialog.cpp \ threadresult.cpp \ translationhandler.cpp \ txtreport.cpp \ + variablecontractsdialog.cpp \ xmlreport.cpp \ xmlreportv2.cpp \ librarydialog.cpp \ diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index c08361963..39b812d5b 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -49,6 +49,7 @@ #include "threadhandler.h" #include "threadresult.h" #include "translationhandler.h" +#include "variablecontractsdialog.h" static const QString OnlineHelpURL("http://cppcheck.net/manual.html"); static const QString compile_commands_json("compile_commands.json"); @@ -144,6 +145,9 @@ MainWindow::MainWindow(TranslationHandler* th, QSettings* settings) : connect(mUI.mResults, &ResultsView::checkSelected, this, &MainWindow::performSelectedFilesCheck); connect(mUI.mResults, &ResultsView::suppressIds, this, &MainWindow::suppressIds); connect(mUI.mResults, &ResultsView::editFunctionContract, this, &MainWindow::editFunctionContract); + connect(mUI.mResults, &ResultsView::editVariableContract, this, &MainWindow::editVariableContract); + connect(mUI.mResults, &ResultsView::deleteFunctionContract, this, &MainWindow::deleteFunctionContract); + connect(mUI.mResults, &ResultsView::deleteVariableContract, this, &MainWindow::deleteVariableContract); connect(mUI.mMenuView, &QMenu::aboutToShow, this, &MainWindow::aboutToShowViewMenu); // File menu @@ -351,7 +355,8 @@ void MainWindow::loadSettings() } } - updateContractsTab(); + updateFunctionContractsTab(); + updateVariableContractsTab(); } void MainWindow::saveSettings() const @@ -611,7 +616,7 @@ QStringList MainWindow::selectFilesToAnalyze(QFileDialog::FileMode mode) return selected; } -void MainWindow::updateContractsTab() +void MainWindow::updateFunctionContractsTab() { QStringList addedContracts; if (mProjectFile) { @@ -619,7 +624,23 @@ void MainWindow::updateContractsTab() addedContracts << QString::fromStdString(it.first); } } - mUI.mResults->setAddedContracts(addedContracts); + mUI.mResults->setAddedFunctionContracts(addedContracts); +} + +void MainWindow::updateVariableContractsTab() +{ + QStringList added; + if (mProjectFile) { + for (auto vc: mProjectFile->getVariableContracts()) { + QString line = vc.first; + if (!vc.second.minValue.empty()) + line += " min:" + QString::fromStdString(vc.second.minValue); + if (!vc.second.maxValue.empty()) + line += " max:" + QString::fromStdString(vc.second.maxValue); + added << line; + } + } + mUI.mResults->setAddedVariableContracts(added); } void MainWindow::analyzeFiles() @@ -865,6 +886,9 @@ Settings MainWindow::getCppcheckSettings() result.functionContracts = mProjectFile->getFunctionContracts(); + for (const auto vc: mProjectFile->getVariableContracts()) + result.variableContracts[vc.first.toStdString()] = vc.second; + const QStringList undefines = mProjectFile->getUndefines(); foreach (QString undefine, undefines) result.userUndefs.insert(undefine.toStdString()); @@ -1513,7 +1537,9 @@ void MainWindow::loadProjectFile(const QString &filePath) delete mProjectFile; mProjectFile = new ProjectFile(filePath, this); mProjectFile->setActiveProject(); - updateContractsTab(); + mUI.mResults->showContracts(mProjectFile->bugHunting); + updateFunctionContractsTab(); + updateVariableContractsTab(); if (!loadLastResults()) analyzeProject(mProjectFile); } @@ -1639,12 +1665,14 @@ void MainWindow::newProjectFile() ProjectFileDialog dlg(mProjectFile, this); if (dlg.exec() == QDialog::Accepted) { addProjectMRU(filepath); + mUI.mResults->showContracts(mProjectFile->bugHunting); analyzeProject(mProjectFile); } else { closeProjectFile(); } - updateContractsTab(); + updateFunctionContractsTab(); + updateVariableContractsTab(); } void MainWindow::closeProjectFile() @@ -1652,6 +1680,8 @@ void MainWindow::closeProjectFile() delete mProjectFile; mProjectFile = nullptr; mUI.mResults->clear(true); + mUI.mResults->clearContracts(); + mUI.mResults->showContracts(false); enableProjectActions(false); enableProjectOpenActions(true); formatAndSetTitle(); @@ -1672,6 +1702,7 @@ void MainWindow::editProjectFile() ProjectFileDialog dlg(mProjectFile, this); if (dlg.exec() == QDialog::Accepted) { mProjectFile->write(); + mUI.mResults->showContracts(mProjectFile->bugHunting); analyzeProject(mProjectFile); } } @@ -1858,5 +1889,35 @@ void MainWindow::editFunctionContract(QString function) mProjectFile->write(); } - updateContractsTab(); + updateFunctionContractsTab(); +} + +void MainWindow::editVariableContract(QString var) +{ + if (!mProjectFile) + return; + + VariableContractsDialog dlg(nullptr, var); + if (dlg.exec() == QDialog::Accepted) { + mProjectFile->setVariableContracts(dlg.getVarname(), dlg.getMin(), dlg.getMax()); + mProjectFile->write(); + } + + updateVariableContractsTab(); +} + +void MainWindow::deleteFunctionContract(QString function) +{ + if (mProjectFile) { + mProjectFile->deleteFunctionContract(function); + mProjectFile->write(); + } +} + +void MainWindow::deleteVariableContract(QString var) +{ + if (mProjectFile) { + mProjectFile->deleteVariableContract(var); + mProjectFile->write(); + } } diff --git a/gui/mainwindow.h b/gui/mainwindow.h index 05e277265..913a464e9 100644 --- a/gui/mainwindow.h +++ b/gui/mainwindow.h @@ -73,8 +73,11 @@ public: public slots: - /** Update "Contracts" tab */ - void updateContractsTab(); + /** Update "Functions" tab */ + void updateFunctionContractsTab(); + + /** Update "Variables" tab */ + void updateVariableContractsTab(); /** @brief Slot for analyze files menu item */ void analyzeFiles(); @@ -227,6 +230,16 @@ protected slots: /** Edit contract for function */ void editFunctionContract(QString function); + + /** Edit constraints for variable */ + void editVariableContract(QString var); + + /** Delete contract for function */ + void deleteFunctionContract(QString function); + + /** Edit constraints for variable */ + void deleteVariableContract(QString var); + private: /** Get filename for last results */ diff --git a/gui/projectfile.cpp b/gui/projectfile.cpp index ef93e78b3..810f42212 100644 --- a/gui/projectfile.cpp +++ b/gui/projectfile.cpp @@ -59,6 +59,7 @@ void ProjectFile::clear() mPaths.clear(); mExcludedPaths.clear(); mFunctionContracts.clear(); + mVariableContracts.clear(); mLibraries.clear(); mPlatform.clear(); mSuppressions.clear(); @@ -156,6 +157,10 @@ bool ProjectFile::read(const QString &filename) if (xmlReader.name() == CppcheckXml::FunctionContracts) readFunctionContracts(xmlReader); + // Variable constraints + if (xmlReader.name() == CppcheckXml::VariableContractsElementName) + readVariableContracts(xmlReader); + // Find libraries list from inside project element if (xmlReader.name() == CppcheckXml::LibrariesElementName) readStringList(mLibraries, xmlReader, CppcheckXml::LibraryElementName); @@ -531,6 +536,42 @@ void ProjectFile::readFunctionContracts(QXmlStreamReader &reader) } while (!allRead); } +void ProjectFile::readVariableContracts(QXmlStreamReader &reader) +{ + QXmlStreamReader::TokenType type; + while (true) { + type = reader.readNext(); + switch (type) { + case QXmlStreamReader::StartElement: + if (reader.name().toString() == CppcheckXml::VariableContractItemElementName) { + QXmlStreamAttributes attribs = reader.attributes(); + QString varname = attribs.value(QString(), CppcheckXml::VariableContractVarName).toString(); + QString minValue = attribs.value(QString(), CppcheckXml::VariableContractMin).toString(); + QString maxValue = attribs.value(QString(), CppcheckXml::VariableContractMax).toString(); + setVariableContracts(varname, minValue, maxValue); + } + break; + + case QXmlStreamReader::EndElement: + if (reader.name().toString() == CppcheckXml::VariableContractsElementName) + 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; + } + } +} + void ProjectFile::readVsConfigurations(QXmlStreamReader &reader) { QXmlStreamReader::TokenType type; @@ -926,6 +967,20 @@ bool ProjectFile::write(const QString &filename) xmlWriter.writeEndElement(); } + if (!mVariableContracts.empty()) { + xmlWriter.writeStartElement(CppcheckXml::VariableContractsElementName); + + for (auto vc: mVariableContracts) { + xmlWriter.writeStartElement(CppcheckXml::VariableContractItemElementName); + xmlWriter.writeAttribute(CppcheckXml::VariableContractVarName, vc.first); + xmlWriter.writeAttribute(CppcheckXml::VariableContractMin, QString::fromStdString(vc.second.minValue)); + xmlWriter.writeAttribute(CppcheckXml::VariableContractMax, QString::fromStdString(vc.second.maxValue)); + 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 9f10ee651..f0472c007 100644 --- a/gui/projectfile.h +++ b/gui/projectfile.h @@ -230,6 +230,24 @@ public: return mFunctionContracts; } + const std::map& getVariableContracts() const { + return mVariableContracts; + } + + void setVariableContracts(QString var, QString min, QString max) { + mVariableContracts[var] = Settings::VariableContracts{min.toStdString(), max.toStdString()}; + } + + void deleteFunctionContract(QString function) + { + mFunctionContracts.erase(function.toStdString()); + } + + void deleteVariableContract(QString var) + { + mVariableContracts.erase(var); + } + /** * @brief Get filename for the project file. * @return file name. @@ -425,6 +443,12 @@ protected: */ void readFunctionContracts(QXmlStreamReader &reader); + /** + * @brief Read variable constraints. + * @param reader XML stream reader. + */ + void readVariableContracts(QXmlStreamReader &reader); + /** * @brief Read lists of Visual Studio configurations * @param reader XML stream reader. @@ -542,6 +566,8 @@ private: std::map mFunctionContracts; + std::map mVariableContracts; + /** * @brief Platform */ diff --git a/gui/resultsview.cpp b/gui/resultsview.cpp index b3beb24b7..e2d2f6162 100644 --- a/gui/resultsview.cpp +++ b/gui/resultsview.cpp @@ -61,8 +61,17 @@ ResultsView::ResultsView(QWidget * parent) : connect(this, &ResultsView::collapseAllResults, mUI.mTree, &ResultsTree::collapseAll); connect(this, &ResultsView::expandAllResults, mUI.mTree, &ResultsTree::expandAll); connect(this, &ResultsView::showHiddenResults, mUI.mTree, &ResultsTree::showHiddenResults); + + // Function contracts connect(mUI.mListAddedContracts, &QListWidget::itemDoubleClicked, this, &ResultsView::contractDoubleClicked); connect(mUI.mListMissingContracts, &QListWidget::itemDoubleClicked, this, &ResultsView::contractDoubleClicked); + mUI.mListAddedContracts->installEventFilter(this); + + // Variable contracts + connect(mUI.mListAddedVariables, &QListWidget::itemDoubleClicked, this, &ResultsView::variableDoubleClicked); + connect(mUI.mListMissingVariables, &QListWidget::itemDoubleClicked, this, &ResultsView::variableDoubleClicked); + connect(mUI.mEditVariablesFilter, &QLineEdit::textChanged, this, &ResultsView::editVariablesFilter); + mUI.mListAddedVariables->installEventFilter(this); mUI.mListLog->setContextMenuPolicy(Qt::CustomContextMenu); @@ -90,7 +99,7 @@ ResultsView::~ResultsView() //dtor } -void ResultsView::setAddedContracts(const QStringList &addedContracts) +void ResultsView::setAddedFunctionContracts(const QStringList &addedContracts) { mUI.mListAddedContracts->clear(); mUI.mListAddedContracts->addItems(addedContracts); @@ -101,6 +110,17 @@ void ResultsView::setAddedContracts(const QStringList &addedContracts) } } +void ResultsView::setAddedVariableContracts(const QStringList &added) +{ + mUI.mListAddedVariables->clear(); + mUI.mListAddedVariables->addItems(added); + for (const QString var: added) { + for (auto item: mUI.mListMissingVariables->findItems(var, Qt::MatchExactly)) + delete item; + mVariableContracts.insert(var); + } +} + void ResultsView::clear(bool results) { if (results) { @@ -127,6 +147,22 @@ void ResultsView::clearRecheckFile(const QString &filename) mUI.mTree->clearRecheckFile(filename); } +void ResultsView::clearContracts() +{ + mUI.mListAddedContracts->clear(); + mUI.mListAddedVariables->clear(); + mUI.mListMissingContracts->clear(); + mUI.mListMissingVariables->clear(); + mFunctionContracts.clear(); + mVariableContracts.clear(); +} + +void ResultsView::showContracts(bool visible) +{ + mUI.mTabFunctionContracts->setVisible(visible); + mUI.mTabVariableContracts->setVisible(visible); +} + void ResultsView::progress(int value, const QString& description) { mUI.mProgress->setValue(value); @@ -458,12 +494,16 @@ void ResultsView::debugError(const ErrorItem &item) void ResultsView::bughuntingReportLine(const QString& line) { for (const QString& s: line.split("\n")) { - if (s.isEmpty()) - continue; - if (s.startsWith("[missing contract] ")) { + if (s.startsWith("[intvar] ")) { + const QString varname = s.mid(9); + if (!mVariableContracts.contains(varname)) { + mVariableContracts.insert(varname); + mUI.mListMissingVariables->addItem(varname); + } + } else if (s.startsWith("[missing contract] ")) { const QString functionName = s.mid(19); - if (!mContracts.contains(functionName)) { - mContracts.insert(functionName); + if (!mFunctionContracts.contains(functionName)) { + mFunctionContracts.insert(functionName); mUI.mListMissingContracts->addItem(functionName); } } @@ -502,6 +542,21 @@ void ResultsView::contractDoubleClicked(QListWidgetItem* item) emit editFunctionContract(item->text()); } +void ResultsView::variableDoubleClicked(QListWidgetItem* item) +{ + emit editVariableContract(item->text()); +} + +void ResultsView::editVariablesFilter(const QString &text) +{ + for (auto item: mUI.mListAddedVariables->findItems(".*", Qt::MatchRegExp)) { + QString varname = item->text().mid(0, item->text().indexOf(" ")); + item->setHidden(!varname.contains(text)); + } + for (auto item: mUI.mListMissingVariables->findItems(".*", Qt::MatchRegExp)) + item->setHidden(!item->text().contains(text)); +} + void ResultsView::on_mListLog_customContextMenuRequested(const QPoint &pos) { if (mUI.mListLog->count() <= 0) @@ -516,3 +571,32 @@ void ResultsView::on_mListLog_customContextMenuRequested(const QPoint &pos) contextMenu.exec(globalPos); } + +bool ResultsView::eventFilter(QObject *target, QEvent *event) +{ + if (event->type() == QEvent::KeyPress) { + if (target == mUI.mListAddedVariables) { + QKeyEvent *keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Delete) { + for (auto i: mUI.mListAddedVariables->selectedItems()) { + emit deleteVariableContract(i->text().mid(0, i->text().indexOf(" "))); + delete i; + } + return true; + } + } + + if (target == mUI.mListAddedContracts) { + QKeyEvent *keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Delete) { + for (auto i: mUI.mListAddedContracts->selectedItems()) { + emit deleteFunctionContract(i->text()); + delete i; + } + + return true; + } + } + } + return QObject::eventFilter(target, event); +} diff --git a/gui/resultsview.h b/gui/resultsview.h index 44871a266..0308dac8f 100644 --- a/gui/resultsview.h +++ b/gui/resultsview.h @@ -50,7 +50,8 @@ public: virtual ~ResultsView(); ResultsView &operator=(const ResultsView &) = delete; - void setAddedContracts(const QStringList &addedContracts); + void setAddedFunctionContracts(const QStringList &addedContracts); + void setAddedVariableContracts(const QStringList &added); /** * @brief Clear results and statistics and reset progressinfo. @@ -68,6 +69,9 @@ public: */ void clearRecheckFile(const QString &filename); + /** Clear the contracts */ + void clearContracts(); + /** * @brief Write statistics in file * @@ -196,6 +200,9 @@ public: return &mUI.mTree->mShowSeverities; } + /** Show/hide the contract tabs */ + void showContracts(bool visible); + signals: /** @@ -224,6 +231,15 @@ signals: /** Edit contract for function */ void editFunctionContract(QString function); + /** Delete contract for function */ + void deleteFunctionContract(QString function); + + /** Edit contract for variable */ + void editVariableContract(QString var); + + /** Delete variable contract */ + void deleteVariableContract(QString var); + /** * @brief Show/hide certain type of errors * Refreshes the tree. @@ -342,6 +358,11 @@ public slots: /** \brief Contract was double clicked => edit it */ void contractDoubleClicked(QListWidgetItem* item); + /** \brief Variable was double clicked => edit it */ + void variableDoubleClicked(QListWidgetItem* item); + + void editVariablesFilter(const QString &text); + protected: /** * @brief Should we show a "No errors found dialog" every time no errors were found? @@ -351,6 +372,8 @@ protected: Ui::ResultsView mUI; CheckStatistics *mStatistics; + + bool eventFilter(QObject *target, QEvent *event); private slots: /** * @brief Custom context menu for Analysis Log @@ -358,7 +381,8 @@ private slots: */ void on_mListLog_customContextMenuRequested(const QPoint &pos); private: - QSet mContracts; + QSet mFunctionContracts; + QSet mVariableContracts; /** Current file shown in the code editor */ QString mCurrentFileName; diff --git a/gui/resultsview.ui b/gui/resultsview.ui index cfe8b57d5..92f15e461 100644 --- a/gui/resultsview.ui +++ b/gui/resultsview.ui @@ -7,7 +7,7 @@ 0 0 459 - 358 + 391 @@ -153,9 +153,9 @@ - + - Contracts + Functions @@ -191,6 +191,71 @@ + + + Variables + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Only show variable names that contain text: + + + + + + + + + + + + Configured contracts: + + + + + + + QAbstractItemView::NoEditTriggers + + + + + + + Missing contracts: + + + + + + + QAbstractItemView::NoEditTriggers + + + true + + + + + diff --git a/gui/variablecontractsdialog.cpp b/gui/variablecontractsdialog.cpp new file mode 100644 index 000000000..9e28307b6 --- /dev/null +++ b/gui/variablecontractsdialog.cpp @@ -0,0 +1,50 @@ +#include "variablecontractsdialog.h" +#include "ui_variablecontractsdialog.h" + +#include + +VariableContractsDialog::VariableContractsDialog(QWidget *parent, QString var) : + QDialog(parent), + ui(new Ui::VariableContractsDialog) +{ + ui->setupUi(this); + + mVarName = var.indexOf(" ") < 0 ? var : var.mid(0, var.indexOf(" ")); + + this->setWindowTitle(mVarName); + + auto getMinMax = [](QString var, QString minmax) { + if (var.indexOf(" " + minmax + ":") < 0) + return QString(); + int pos1 = var.indexOf(" " + minmax + ":") + 2 + minmax.length(); + int pos2 = var.indexOf(" ", pos1); + if (pos2 < 0) + return var.mid(pos1); + return var.mid(pos1, pos2-pos1); + }; + + ui->mMinValue->setText(getMinMax(var, "min")); + ui->mMaxValue->setText(getMinMax(var, "max")); + + ui->mMinValue->setValidator(new QRegExpValidator(QRegExp("-?[0-9]*"))); + ui->mMaxValue->setValidator(new QRegExpValidator(QRegExp("-?[0-9]*"))); +} + +VariableContractsDialog::~VariableContractsDialog() +{ + delete ui; +} + +QString VariableContractsDialog::getVarname() const +{ + return mVarName; +} +QString VariableContractsDialog::getMin() const +{ + return ui->mMinValue->text(); +} +QString VariableContractsDialog::getMax() const +{ + return ui->mMaxValue->text(); +} + diff --git a/gui/variablecontractsdialog.h b/gui/variablecontractsdialog.h new file mode 100644 index 000000000..6ec67bece --- /dev/null +++ b/gui/variablecontractsdialog.h @@ -0,0 +1,26 @@ +#ifndef VARIABLECONSTRAINTSDIALOG_H +#define VARIABLECONSTRAINTSDIALOG_H + +#include + +namespace Ui { + class VariableContractsDialog; +} + +class VariableContractsDialog : public QDialog { + Q_OBJECT + +public: + explicit VariableContractsDialog(QWidget *parent, QString var); + ~VariableContractsDialog(); + + QString getVarname() const; + QString getMin() const; + QString getMax() const; + +private: + Ui::VariableContractsDialog *ui; + QString mVarName; +}; + +#endif // VARIABLECONSTRAINTSDIALOG_H diff --git a/gui/variablecontractsdialog.ui b/gui/variablecontractsdialog.ui new file mode 100644 index 000000000..48a5642b7 --- /dev/null +++ b/gui/variablecontractsdialog.ui @@ -0,0 +1,108 @@ + + + VariableContractsDialog + + + + 0 + 0 + 400 + 172 + + + + Dialog + + + + + + You can specify min and max value for the variable here + + + + + + + + + Min + + + + + + + + + + Max + + + + + + + + + + + + Qt::Vertical + + + + 20 + 25 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + VariableContractsDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + VariableContractsDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/lib/bughuntingchecks.cpp b/lib/bughuntingchecks.cpp index afb55c5f7..a39461dc0 100644 --- a/lib/bughuntingchecks.cpp +++ b/lib/bughuntingchecks.cpp @@ -505,14 +505,44 @@ static void checkAssignment(const Token *tok, const ExprEngine::Value &value, Ex const Token *vartok = lhs->variable()->nameToken(); + bool executable = false; + std::string fullName = lhs->variable()->name(); + for (const Scope *s = lhs->variable()->nameToken()->scope(); s->type != Scope::ScopeType::eGlobal; s = s->nestedIn) { + if (s->isExecutable()) { + executable = true; + break; + } + fullName = s->className + "::" + fullName; + } + + auto getMinMaxValue = [=](TokenImpl::CppcheckAttributes::Type type, MathLib::bigint *val) { + if (vartok->getCppcheckAttribute(type, val)) + return true; + if (!executable) { + const auto it = dataBase->settings->variableContracts.find(fullName); + if (it != dataBase->settings->variableContracts.end()) { + const std::string *str; + if (type == TokenImpl::CppcheckAttributes::Type::LOW) + str = &it->second.minValue; + else if (type == TokenImpl::CppcheckAttributes::Type::HIGH) + str = &it->second.maxValue; + else + return false; + *val = MathLib::toLongNumber(*str); + return !str->empty(); + } + } + return false; + }; + MathLib::bigint low; - if (vartok->getCppcheckAttribute(TokenImpl::CppcheckAttributes::Type::LOW, &low)) { + if (getMinMaxValue(TokenImpl::CppcheckAttributes::Type::LOW, &low)) { if (value.isLessThan(dataBase, low)) dataBase->reportError(tok, Severity::SeverityType::error, "bughuntingAssign", "There is assignment, cannot determine that value is greater or equal with " + std::to_string(low), CWE_INCORRECT_CALCULATION, false); } MathLib::bigint high; - if (vartok->getCppcheckAttribute(TokenImpl::CppcheckAttributes::Type::HIGH, &high)) { + if (getMinMaxValue(TokenImpl::CppcheckAttributes::Type::HIGH, &high)) { if (value.isGreaterThan(dataBase, high)) dataBase->reportError(tok, Severity::SeverityType::error, "bughuntingAssign", "There is assignment, cannot determine that value is lower or equal with " + std::to_string(high), CWE_INCORRECT_CALCULATION, false); } diff --git a/lib/exprengine.cpp b/lib/exprengine.cpp index 98f944103..0898db1aa 100644 --- a/lib/exprengine.cpp +++ b/lib/exprengine.cpp @@ -2641,6 +2641,28 @@ void ExprEngine::executeFunction(const Scope *functionScope, ErrorLogger *errorL // Write a report if (bugHuntingReport) { + std::set intvars; + for (const Scope &scope: tokenizer->getSymbolDatabase()->scopeList) { + if (scope.isExecutable()) + continue; + std::string path; + bool valid = true; + for (const Scope *s = &scope; s->type != Scope::ScopeType::eGlobal; s = s->nestedIn) { + if (s->isExecutable()) { + valid = false; + break; + } + path = s->className + "::" + path; + } + if (!valid) + continue; + for (const Variable &var: scope.varlist) { + if (var.nameToken() && !var.nameToken()->hasCppcheckAttributes() && var.valueType() && var.valueType()->pointer == 0 && var.valueType()->constness == 0 && var.valueType()->isIntegral()) + intvars.insert(path + var.name()); + } + } + for (const std::string &v: intvars) + report << "[intvar] " << v << std::endl; for (const std::string &f: trackExecution.getMissingContracts()) report << "[missing contract] " << f << std::endl; } diff --git a/lib/importproject.cpp b/lib/importproject.cpp index bcf272ea4..79164a20d 100644 --- a/lib/importproject.cpp +++ b/lib/importproject.cpp @@ -1068,6 +1068,16 @@ bool ImportProject::importCppcheckGuiProject(std::istream &istr, Settings *setti temp.functionContracts[function] = expects; } } + } else if (strcmp(node->Name(), CppcheckXml::VariableContractsElementName) == 0) { + for (const tinyxml2::XMLElement *child = node->FirstChildElement(); child; child = child->NextSiblingElement()) { + if (strcmp(child->Name(), CppcheckXml::VariableContractItemElementName) == 0) { + const char *name = child->Attribute(CppcheckXml::VariableContractVarName); + const char *min = child->Attribute(CppcheckXml::VariableContractMin); + const char *max = child->Attribute(CppcheckXml::VariableContractMax); + if (name) + temp.variableContracts[name] = Settings::VariableContracts{min?min:"", max?max:""}; + } + } } else if (strcmp(node->Name(), CppcheckXml::IgnoreElementName) == 0) guiProject.excludedPaths = readXmlStringList(node, "", CppcheckXml::IgnorePathName, CppcheckXml::IgnorePathNameAttrib); else if (strcmp(node->Name(), CppcheckXml::LibrariesElementName) == 0) diff --git a/lib/importproject.h b/lib/importproject.h index e39ec8624..9694d6468 100644 --- a/lib/importproject.h +++ b/lib/importproject.h @@ -150,6 +150,11 @@ namespace CppcheckXml { const char FunctionContract[] = "contract"; const char ContractFunction[] = "function"; const char ContractExpects[] = "expects"; + const char VariableContractsElementName[] = "variable-contracts"; + const char VariableContractItemElementName[] = "var"; + const char VariableContractVarName[] = "name"; + const char VariableContractMin[] = "min"; + const char VariableContractMax[] = "max"; const char LibrariesElementName[] = "libraries"; const char LibraryElementName[] = "library"; const char PlatformElementName[] = "platform"; diff --git a/lib/settings.h b/lib/settings.h index 14132e786..170fad833 100644 --- a/lib/settings.h +++ b/lib/settings.h @@ -176,6 +176,12 @@ public: std::map functionContracts; + struct VariableContracts { + std::string minValue; + std::string maxValue; + }; + std::map variableContracts; + /** @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/token.h b/lib/token.h index 6d5718dcb..fd1cfc089 100644 --- a/lib/token.h +++ b/lib/token.h @@ -561,6 +561,9 @@ public: bool getCppcheckAttribute(TokenImpl::CppcheckAttributes::Type type, MathLib::bigint *value) const { return mImpl->getCppcheckAttribute(type, value); } + bool hasCppcheckAttributes() const { + return nullptr != mImpl->mCppcheckAttributes; + } bool isControlFlowKeyword() const { return getFlag(fIsControlFlowKeyword); } diff --git a/man/manual.md b/man/manual.md index c1b0793cd..cc00722db 100644 --- a/man/manual.md +++ b/man/manual.md @@ -927,7 +927,7 @@ Cppcheck will warn: 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. +- Open the "Functions" or "Variables" tab at the bottom of the screen. Find the function or variable 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 diff --git a/test/testbughuntingchecks.cpp b/test/testbughuntingchecks.cpp index 0e2d7ccc1..32762a08b 100644 --- a/test/testbughuntingchecks.cpp +++ b/test/testbughuntingchecks.cpp @@ -35,6 +35,7 @@ private: #ifdef USE_Z3 settings.inconclusive = true; LOAD_LIB_2(settings.library, "std.cfg"); + TEST_CASE(checkAssignment); TEST_CASE(uninit); TEST_CASE(uninit_array); TEST_CASE(uninit_function_par); @@ -53,6 +54,15 @@ private: ExprEngine::runChecks(this, &tokenizer, &settings); } + void checkAssignment() { + check("void foo(int any) { __cppcheck_low__(0) int x; x = any; }"); + ASSERT_EQUALS("[test.cpp:1]: (error) There is assignment, cannot determine that value is greater or equal with 0\n", errout.str()); + + check("struct S { __cppcheck_low__(0) int x; };\n" + "void foo(S *s, int any) { s->x = any; }"); + ASSERT_EQUALS("[test.cpp:2]: (error) There is assignment, cannot determine that value is greater or equal with 0\n", errout.str()); + } + void uninit() { check("void foo() { int x; x = x + 1; }"); ASSERT_EQUALS("[test.cpp:1]: (error) Cannot determine that 'x' is initialized\n", errout.str());