Bug hunting: basic handling of contracts through GUI

This commit is contained in:
Daniel Marjamäki 2020-04-27 09:08:50 +02:00
parent 72e0a4be29
commit f7096a2232
18 changed files with 211 additions and 14 deletions

View File

@ -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()))

View File

@ -80,6 +80,7 @@ public:
QString tool() const;
QString file0;
QString function;
QString errorId;
Severity::SeverityType severity;
bool inconclusive;

View File

@ -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();
}
}

View File

@ -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 */

View File

@ -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) {

View File

@ -19,6 +19,7 @@
#ifndef PROJECT_FILE_H
#define PROJECT_FILE_H
#include <map>
#include <QObject>
#include <QString>
#include <QStringList>
@ -200,6 +201,10 @@ public:
mMaxCtuDepth = maxCtuDepth;
}
const std::map<std::string,std::string> 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<std::string, std::string> mFunctionContracts;
/**
* @brief Platform
*/

View File

@ -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<QString, QVariant> 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)

View File

@ -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.
*

View File

@ -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);

View File

@ -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.

View File

@ -314,8 +314,10 @@ public:
std::list<FileLocation> 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;

View File

@ -158,8 +158,8 @@ namespace {
class Data : public ExprEngine::DataBase {
public:
Data(int *symbolValueIndex, const Tokenizer *tokenizer, const Settings *settings, const std::vector<ExprEngine::Callback> &callbacks, TrackExecution *trackExecution)
: DataBase(settings)
Data(int *symbolValueIndex, const Tokenizer *tokenizer, const Settings *settings, const std::string &currentFunction, const std::vector<ExprEngine::Callback> &callbacks, TrackExecution *trackExecution)
: DataBase(currentFunction, settings)
, symbolValueIndex(symbolValueIndex)
, tokenizer(tokenizer)
, callbacks(callbacks)
@ -172,6 +172,28 @@ namespace {
const std::vector<ExprEngine::Callback> &callbacks;
std::vector<ExprEngine::ValuePtr> 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<SymbolDatabase*>(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<const Token*> 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);
}
};

View File

@ -71,8 +71,12 @@ namespace ExprEngine {
class DataBase {
public:
explicit DataBase(const Settings *settings) : settings(settings) {}
explicit DataBase(const std::string &currentFunction, 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;

View File

@ -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;
}

View File

@ -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";

View File

@ -173,6 +173,8 @@ public:
/** @brief Force checking the files with "too many" configurations (--force). */
bool force;
std::map<std::string, std::string> functionContracts;
/** @brief List of include paths, e.g. "my/includes/" which should be used
for finding include files inside source files. (-I) */
std::list<std::string> includePaths;

View File

@ -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<Tokenizer *>(mTokenizer)->list.front();
if (!tokens)
tokens = const_cast<Tokenizer *>(mTokenizer)->list.front();
for (Token *tok = tokens; tok; tok = tok->next())
tok->setValueType(nullptr);

View File

@ -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.