Variable contracts

This commit is contained in:
Daniel Marjamäki 2020-08-22 11:37:44 +02:00
parent 02287d9d34
commit 6277eece67
19 changed files with 624 additions and 23 deletions

View File

@ -77,7 +77,8 @@ FORMS = about.ui \
librarydialog.ui \ librarydialog.ui \
libraryaddfunctiondialog.ui \ libraryaddfunctiondialog.ui \
libraryeditargdialog.ui \ libraryeditargdialog.ui \
newsuppressiondialog.ui newsuppressiondialog.ui \
variablecontractsdialog.ui
TRANSLATIONS = cppcheck_de.ts \ TRANSLATIONS = cppcheck_de.ts \
cppcheck_es.ts \ cppcheck_es.ts \
@ -134,6 +135,7 @@ HEADERS += aboutdialog.h \
threadresult.h \ threadresult.h \
translationhandler.h \ translationhandler.h \
txtreport.h \ txtreport.h \
variablecontractsdialog.h \
xmlreport.h \ xmlreport.h \
xmlreportv2.h \ xmlreportv2.h \
librarydialog.h \ librarydialog.h \
@ -176,6 +178,7 @@ SOURCES += aboutdialog.cpp \
threadresult.cpp \ threadresult.cpp \
translationhandler.cpp \ translationhandler.cpp \
txtreport.cpp \ txtreport.cpp \
variablecontractsdialog.cpp \
xmlreport.cpp \ xmlreport.cpp \
xmlreportv2.cpp \ xmlreportv2.cpp \
librarydialog.cpp \ librarydialog.cpp \

View File

@ -49,6 +49,7 @@
#include "threadhandler.h" #include "threadhandler.h"
#include "threadresult.h" #include "threadresult.h"
#include "translationhandler.h" #include "translationhandler.h"
#include "variablecontractsdialog.h"
static const QString OnlineHelpURL("http://cppcheck.net/manual.html"); static const QString OnlineHelpURL("http://cppcheck.net/manual.html");
static const QString compile_commands_json("compile_commands.json"); 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::checkSelected, this, &MainWindow::performSelectedFilesCheck);
connect(mUI.mResults, &ResultsView::suppressIds, this, &MainWindow::suppressIds); connect(mUI.mResults, &ResultsView::suppressIds, this, &MainWindow::suppressIds);
connect(mUI.mResults, &ResultsView::editFunctionContract, this, &MainWindow::editFunctionContract); 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); connect(mUI.mMenuView, &QMenu::aboutToShow, this, &MainWindow::aboutToShowViewMenu);
// File menu // File menu
@ -351,7 +355,8 @@ void MainWindow::loadSettings()
} }
} }
updateContractsTab(); updateFunctionContractsTab();
updateVariableContractsTab();
} }
void MainWindow::saveSettings() const void MainWindow::saveSettings() const
@ -611,7 +616,7 @@ QStringList MainWindow::selectFilesToAnalyze(QFileDialog::FileMode mode)
return selected; return selected;
} }
void MainWindow::updateContractsTab() void MainWindow::updateFunctionContractsTab()
{ {
QStringList addedContracts; QStringList addedContracts;
if (mProjectFile) { if (mProjectFile) {
@ -619,7 +624,23 @@ void MainWindow::updateContractsTab()
addedContracts << QString::fromStdString(it.first); 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() void MainWindow::analyzeFiles()
@ -865,6 +886,9 @@ Settings MainWindow::getCppcheckSettings()
result.functionContracts = mProjectFile->getFunctionContracts(); result.functionContracts = mProjectFile->getFunctionContracts();
for (const auto vc: mProjectFile->getVariableContracts())
result.variableContracts[vc.first.toStdString()] = vc.second;
const QStringList undefines = mProjectFile->getUndefines(); const QStringList undefines = mProjectFile->getUndefines();
foreach (QString undefine, undefines) foreach (QString undefine, undefines)
result.userUndefs.insert(undefine.toStdString()); result.userUndefs.insert(undefine.toStdString());
@ -1513,7 +1537,9 @@ void MainWindow::loadProjectFile(const QString &filePath)
delete mProjectFile; delete mProjectFile;
mProjectFile = new ProjectFile(filePath, this); mProjectFile = new ProjectFile(filePath, this);
mProjectFile->setActiveProject(); mProjectFile->setActiveProject();
updateContractsTab(); mUI.mResults->showContracts(mProjectFile->bugHunting);
updateFunctionContractsTab();
updateVariableContractsTab();
if (!loadLastResults()) if (!loadLastResults())
analyzeProject(mProjectFile); analyzeProject(mProjectFile);
} }
@ -1639,12 +1665,14 @@ void MainWindow::newProjectFile()
ProjectFileDialog dlg(mProjectFile, this); ProjectFileDialog dlg(mProjectFile, this);
if (dlg.exec() == QDialog::Accepted) { if (dlg.exec() == QDialog::Accepted) {
addProjectMRU(filepath); addProjectMRU(filepath);
mUI.mResults->showContracts(mProjectFile->bugHunting);
analyzeProject(mProjectFile); analyzeProject(mProjectFile);
} else { } else {
closeProjectFile(); closeProjectFile();
} }
updateContractsTab(); updateFunctionContractsTab();
updateVariableContractsTab();
} }
void MainWindow::closeProjectFile() void MainWindow::closeProjectFile()
@ -1652,6 +1680,8 @@ void MainWindow::closeProjectFile()
delete mProjectFile; delete mProjectFile;
mProjectFile = nullptr; mProjectFile = nullptr;
mUI.mResults->clear(true); mUI.mResults->clear(true);
mUI.mResults->clearContracts();
mUI.mResults->showContracts(false);
enableProjectActions(false); enableProjectActions(false);
enableProjectOpenActions(true); enableProjectOpenActions(true);
formatAndSetTitle(); formatAndSetTitle();
@ -1672,6 +1702,7 @@ void MainWindow::editProjectFile()
ProjectFileDialog dlg(mProjectFile, this); ProjectFileDialog dlg(mProjectFile, this);
if (dlg.exec() == QDialog::Accepted) { if (dlg.exec() == QDialog::Accepted) {
mProjectFile->write(); mProjectFile->write();
mUI.mResults->showContracts(mProjectFile->bugHunting);
analyzeProject(mProjectFile); analyzeProject(mProjectFile);
} }
} }
@ -1858,5 +1889,35 @@ void MainWindow::editFunctionContract(QString function)
mProjectFile->write(); 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();
}
} }

View File

@ -73,8 +73,11 @@ public:
public slots: public slots:
/** Update "Contracts" tab */ /** Update "Functions" tab */
void updateContractsTab(); void updateFunctionContractsTab();
/** Update "Variables" tab */
void updateVariableContractsTab();
/** @brief Slot for analyze files menu item */ /** @brief Slot for analyze files menu item */
void analyzeFiles(); void analyzeFiles();
@ -227,6 +230,16 @@ protected slots:
/** Edit contract for function */ /** Edit contract for function */
void editFunctionContract(QString 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: private:
/** Get filename for last results */ /** Get filename for last results */

View File

@ -59,6 +59,7 @@ void ProjectFile::clear()
mPaths.clear(); mPaths.clear();
mExcludedPaths.clear(); mExcludedPaths.clear();
mFunctionContracts.clear(); mFunctionContracts.clear();
mVariableContracts.clear();
mLibraries.clear(); mLibraries.clear();
mPlatform.clear(); mPlatform.clear();
mSuppressions.clear(); mSuppressions.clear();
@ -156,6 +157,10 @@ bool ProjectFile::read(const QString &filename)
if (xmlReader.name() == CppcheckXml::FunctionContracts) if (xmlReader.name() == CppcheckXml::FunctionContracts)
readFunctionContracts(xmlReader); readFunctionContracts(xmlReader);
// Variable constraints
if (xmlReader.name() == CppcheckXml::VariableContractsElementName)
readVariableContracts(xmlReader);
// Find libraries list from inside project element // Find libraries list from inside project element
if (xmlReader.name() == CppcheckXml::LibrariesElementName) if (xmlReader.name() == CppcheckXml::LibrariesElementName)
readStringList(mLibraries, xmlReader, CppcheckXml::LibraryElementName); readStringList(mLibraries, xmlReader, CppcheckXml::LibraryElementName);
@ -531,6 +536,42 @@ void ProjectFile::readFunctionContracts(QXmlStreamReader &reader)
} while (!allRead); } 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) void ProjectFile::readVsConfigurations(QXmlStreamReader &reader)
{ {
QXmlStreamReader::TokenType type; QXmlStreamReader::TokenType type;
@ -926,6 +967,20 @@ bool ProjectFile::write(const QString &filename)
xmlWriter.writeEndElement(); 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()) { if (!mSuppressions.isEmpty()) {
xmlWriter.writeStartElement(CppcheckXml::SuppressionsElementName); xmlWriter.writeStartElement(CppcheckXml::SuppressionsElementName);
foreach (const Suppressions::Suppression &suppression, mSuppressions) { foreach (const Suppressions::Suppression &suppression, mSuppressions) {

View File

@ -230,6 +230,24 @@ public:
return mFunctionContracts; return mFunctionContracts;
} }
const std::map<QString, Settings::VariableContracts>& 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. * @brief Get filename for the project file.
* @return file name. * @return file name.
@ -425,6 +443,12 @@ protected:
*/ */
void readFunctionContracts(QXmlStreamReader &reader); void readFunctionContracts(QXmlStreamReader &reader);
/**
* @brief Read variable constraints.
* @param reader XML stream reader.
*/
void readVariableContracts(QXmlStreamReader &reader);
/** /**
* @brief Read lists of Visual Studio configurations * @brief Read lists of Visual Studio configurations
* @param reader XML stream reader. * @param reader XML stream reader.
@ -542,6 +566,8 @@ private:
std::map<std::string, std::string> mFunctionContracts; std::map<std::string, std::string> mFunctionContracts;
std::map<QString, Settings::VariableContracts> mVariableContracts;
/** /**
* @brief Platform * @brief Platform
*/ */

View File

@ -61,8 +61,17 @@ ResultsView::ResultsView(QWidget * parent) :
connect(this, &ResultsView::collapseAllResults, mUI.mTree, &ResultsTree::collapseAll); connect(this, &ResultsView::collapseAllResults, mUI.mTree, &ResultsTree::collapseAll);
connect(this, &ResultsView::expandAllResults, mUI.mTree, &ResultsTree::expandAll); connect(this, &ResultsView::expandAllResults, mUI.mTree, &ResultsTree::expandAll);
connect(this, &ResultsView::showHiddenResults, mUI.mTree, &ResultsTree::showHiddenResults); connect(this, &ResultsView::showHiddenResults, mUI.mTree, &ResultsTree::showHiddenResults);
// Function contracts
connect(mUI.mListAddedContracts, &QListWidget::itemDoubleClicked, this, &ResultsView::contractDoubleClicked); connect(mUI.mListAddedContracts, &QListWidget::itemDoubleClicked, this, &ResultsView::contractDoubleClicked);
connect(mUI.mListMissingContracts, &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); mUI.mListLog->setContextMenuPolicy(Qt::CustomContextMenu);
@ -90,7 +99,7 @@ ResultsView::~ResultsView()
//dtor //dtor
} }
void ResultsView::setAddedContracts(const QStringList &addedContracts) void ResultsView::setAddedFunctionContracts(const QStringList &addedContracts)
{ {
mUI.mListAddedContracts->clear(); mUI.mListAddedContracts->clear();
mUI.mListAddedContracts->addItems(addedContracts); 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) void ResultsView::clear(bool results)
{ {
if (results) { if (results) {
@ -127,6 +147,22 @@ void ResultsView::clearRecheckFile(const QString &filename)
mUI.mTree->clearRecheckFile(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) void ResultsView::progress(int value, const QString& description)
{ {
mUI.mProgress->setValue(value); mUI.mProgress->setValue(value);
@ -458,12 +494,16 @@ void ResultsView::debugError(const ErrorItem &item)
void ResultsView::bughuntingReportLine(const QString& line) void ResultsView::bughuntingReportLine(const QString& line)
{ {
for (const QString& s: line.split("\n")) { for (const QString& s: line.split("\n")) {
if (s.isEmpty()) if (s.startsWith("[intvar] ")) {
continue; const QString varname = s.mid(9);
if (s.startsWith("[missing contract] ")) { if (!mVariableContracts.contains(varname)) {
mVariableContracts.insert(varname);
mUI.mListMissingVariables->addItem(varname);
}
} else if (s.startsWith("[missing contract] ")) {
const QString functionName = s.mid(19); const QString functionName = s.mid(19);
if (!mContracts.contains(functionName)) { if (!mFunctionContracts.contains(functionName)) {
mContracts.insert(functionName); mFunctionContracts.insert(functionName);
mUI.mListMissingContracts->addItem(functionName); mUI.mListMissingContracts->addItem(functionName);
} }
} }
@ -502,6 +542,21 @@ void ResultsView::contractDoubleClicked(QListWidgetItem* item)
emit editFunctionContract(item->text()); 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) void ResultsView::on_mListLog_customContextMenuRequested(const QPoint &pos)
{ {
if (mUI.mListLog->count() <= 0) if (mUI.mListLog->count() <= 0)
@ -516,3 +571,32 @@ void ResultsView::on_mListLog_customContextMenuRequested(const QPoint &pos)
contextMenu.exec(globalPos); contextMenu.exec(globalPos);
} }
bool ResultsView::eventFilter(QObject *target, QEvent *event)
{
if (event->type() == QEvent::KeyPress) {
if (target == mUI.mListAddedVariables) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(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<QKeyEvent *>(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);
}

View File

@ -50,7 +50,8 @@ public:
virtual ~ResultsView(); virtual ~ResultsView();
ResultsView &operator=(const ResultsView &) = delete; 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. * @brief Clear results and statistics and reset progressinfo.
@ -68,6 +69,9 @@ public:
*/ */
void clearRecheckFile(const QString &filename); void clearRecheckFile(const QString &filename);
/** Clear the contracts */
void clearContracts();
/** /**
* @brief Write statistics in file * @brief Write statistics in file
* *
@ -196,6 +200,9 @@ public:
return &mUI.mTree->mShowSeverities; return &mUI.mTree->mShowSeverities;
} }
/** Show/hide the contract tabs */
void showContracts(bool visible);
signals: signals:
/** /**
@ -224,6 +231,15 @@ signals:
/** Edit contract for function */ /** Edit contract for function */
void editFunctionContract(QString 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 * @brief Show/hide certain type of errors
* Refreshes the tree. * Refreshes the tree.
@ -342,6 +358,11 @@ public slots:
/** \brief Contract was double clicked => edit it */ /** \brief Contract was double clicked => edit it */
void contractDoubleClicked(QListWidgetItem* item); void contractDoubleClicked(QListWidgetItem* item);
/** \brief Variable was double clicked => edit it */
void variableDoubleClicked(QListWidgetItem* item);
void editVariablesFilter(const QString &text);
protected: protected:
/** /**
* @brief Should we show a "No errors found dialog" every time no errors were found? * @brief Should we show a "No errors found dialog" every time no errors were found?
@ -351,6 +372,8 @@ protected:
Ui::ResultsView mUI; Ui::ResultsView mUI;
CheckStatistics *mStatistics; CheckStatistics *mStatistics;
bool eventFilter(QObject *target, QEvent *event);
private slots: private slots:
/** /**
* @brief Custom context menu for Analysis Log * @brief Custom context menu for Analysis Log
@ -358,7 +381,8 @@ private slots:
*/ */
void on_mListLog_customContextMenuRequested(const QPoint &pos); void on_mListLog_customContextMenuRequested(const QPoint &pos);
private: private:
QSet<QString> mContracts; QSet<QString> mFunctionContracts;
QSet<QString> mVariableContracts;
/** Current file shown in the code editor */ /** Current file shown in the code editor */
QString mCurrentFileName; QString mCurrentFileName;

View File

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>459</width> <width>459</width>
<height>358</height> <height>391</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
@ -153,9 +153,9 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="mTabContracts"> <widget class="QWidget" name="mTabFunctionContracts">
<attribute name="title"> <attribute name="title">
<string>Contracts</string> <string>Functions</string>
</attribute> </attribute>
<layout class="QVBoxLayout" name="verticalLayout_6"> <layout class="QVBoxLayout" name="verticalLayout_6">
<item> <item>
@ -191,6 +191,71 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="mTabVariableContracts">
<attribute name="title">
<string>Variables</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Only show variable names that contain text:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="mEditVariablesFilter"/>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Configured contracts:</string>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="mListAddedVariables">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Missing contracts:</string>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="mListMissingVariables">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget> </widget>
</widget> </widget>
</item> </item>

View File

@ -0,0 +1,50 @@
#include "variablecontractsdialog.h"
#include "ui_variablecontractsdialog.h"
#include <QRegExpValidator>
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();
}

View File

@ -0,0 +1,26 @@
#ifndef VARIABLECONSTRAINTSDIALOG_H
#define VARIABLECONSTRAINTSDIALOG_H
#include <QDialog>
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

View File

@ -0,0 +1,108 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>VariableContractsDialog</class>
<widget class="QDialog" name="VariableContractsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>172</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>You can specify min and max value for the variable here</string>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Min</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="mMinValue"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Max</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="mMaxValue"/>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>25</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>VariableContractsDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>VariableContractsDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -505,14 +505,44 @@ static void checkAssignment(const Token *tok, const ExprEngine::Value &value, Ex
const Token *vartok = lhs->variable()->nameToken(); 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; MathLib::bigint low;
if (vartok->getCppcheckAttribute(TokenImpl::CppcheckAttributes::Type::LOW, &low)) { if (getMinMaxValue(TokenImpl::CppcheckAttributes::Type::LOW, &low)) {
if (value.isLessThan(dataBase, 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); 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; MathLib::bigint high;
if (vartok->getCppcheckAttribute(TokenImpl::CppcheckAttributes::Type::HIGH, &high)) { if (getMinMaxValue(TokenImpl::CppcheckAttributes::Type::HIGH, &high)) {
if (value.isGreaterThan(dataBase, 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); 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);
} }

View File

@ -2641,6 +2641,28 @@ void ExprEngine::executeFunction(const Scope *functionScope, ErrorLogger *errorL
// Write a report // Write a report
if (bugHuntingReport) { if (bugHuntingReport) {
std::set<std::string> 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()) for (const std::string &f: trackExecution.getMissingContracts())
report << "[missing contract] " << f << std::endl; report << "[missing contract] " << f << std::endl;
} }

View File

@ -1068,6 +1068,16 @@ bool ImportProject::importCppcheckGuiProject(std::istream &istr, Settings *setti
temp.functionContracts[function] = expects; 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) } else if (strcmp(node->Name(), CppcheckXml::IgnoreElementName) == 0)
guiProject.excludedPaths = readXmlStringList(node, "", CppcheckXml::IgnorePathName, CppcheckXml::IgnorePathNameAttrib); guiProject.excludedPaths = readXmlStringList(node, "", CppcheckXml::IgnorePathName, CppcheckXml::IgnorePathNameAttrib);
else if (strcmp(node->Name(), CppcheckXml::LibrariesElementName) == 0) else if (strcmp(node->Name(), CppcheckXml::LibrariesElementName) == 0)

View File

@ -150,6 +150,11 @@ namespace CppcheckXml {
const char FunctionContract[] = "contract"; const char FunctionContract[] = "contract";
const char ContractFunction[] = "function"; const char ContractFunction[] = "function";
const char ContractExpects[] = "expects"; 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 LibrariesElementName[] = "libraries";
const char LibraryElementName[] = "library"; const char LibraryElementName[] = "library";
const char PlatformElementName[] = "platform"; const char PlatformElementName[] = "platform";

View File

@ -176,6 +176,12 @@ public:
std::map<std::string, std::string> functionContracts; std::map<std::string, std::string> functionContracts;
struct VariableContracts {
std::string minValue;
std::string maxValue;
};
std::map<std::string, VariableContracts> variableContracts;
/** @brief List of include paths, e.g. "my/includes/" which should be used /** @brief List of include paths, e.g. "my/includes/" which should be used
for finding include files inside source files. (-I) */ for finding include files inside source files. (-I) */
std::list<std::string> includePaths; std::list<std::string> includePaths;

View File

@ -561,6 +561,9 @@ public:
bool getCppcheckAttribute(TokenImpl::CppcheckAttributes::Type type, MathLib::bigint *value) const { bool getCppcheckAttribute(TokenImpl::CppcheckAttributes::Type type, MathLib::bigint *value) const {
return mImpl->getCppcheckAttribute(type, value); return mImpl->getCppcheckAttribute(type, value);
} }
bool hasCppcheckAttributes() const {
return nullptr != mImpl->mCppcheckAttributes;
}
bool isControlFlowKeyword() const { bool isControlFlowKeyword() const {
return getFlag(fIsControlFlowKeyword); return getFlag(fIsControlFlowKeyword);
} }

View File

@ -927,7 +927,7 @@ Cppcheck will warn:
There are two ways: 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. - 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 ## Incomplete analysis

View File

@ -35,6 +35,7 @@ private:
#ifdef USE_Z3 #ifdef USE_Z3
settings.inconclusive = true; settings.inconclusive = true;
LOAD_LIB_2(settings.library, "std.cfg"); LOAD_LIB_2(settings.library, "std.cfg");
TEST_CASE(checkAssignment);
TEST_CASE(uninit); TEST_CASE(uninit);
TEST_CASE(uninit_array); TEST_CASE(uninit_array);
TEST_CASE(uninit_function_par); TEST_CASE(uninit_function_par);
@ -53,6 +54,15 @@ private:
ExprEngine::runChecks(this, &tokenizer, &settings); 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() { void uninit() {
check("void foo() { int x; x = x + 1; }"); check("void foo() { int x; x = x + 1; }");
ASSERT_EQUALS("[test.cpp:1]: (error) Cannot determine that 'x' is initialized\n", errout.str()); ASSERT_EQUALS("[test.cpp:1]: (error) Cannot determine that 'x' is initialized\n", errout.str());