diff --git a/gui/compliancereportdialog.cpp b/gui/compliancereportdialog.cpp new file mode 100644 index 000000000..dac1859ac --- /dev/null +++ b/gui/compliancereportdialog.cpp @@ -0,0 +1,169 @@ +#include "compliancereportdialog.h" +#include "ui_compliancereportdialog.h" + +#include "filelist.h" +#include "projectfile.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void addHeaders(const QString& file1, QSet &allFiles) { + if (allFiles.contains(file1)) + return; + QFile file(file1); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + return; + allFiles << file1; + const QRegularExpression re("^#include[ ]*\"([^\">]+)\".*"); + QTextStream in(&file); + QString line = in.readLine(); + while (!in.atEnd()) { + if (line.startsWith("#include")) { + const QRegularExpressionMatch match = re.match(line); + if (match.hasMatch()) { + QString hfile = match.captured(1); + if (file1.contains("/")) + hfile = file1.mid(0,file1.lastIndexOf("/") + 1) + hfile; + addHeaders(hfile, allFiles); + } + } + line = in.readLine(); + } +} + +static std::vector toStdStringList(const QStringList& from) { + std::vector ret; + std::transform(from.cbegin(), from.cend(), std::back_inserter(ret), [](const QString& e) { + return e.toStdString(); + }); + return ret; +} + +ComplianceReportDialog::ComplianceReportDialog(ProjectFile* projectFile, QString resultsFile) : + QDialog(nullptr), + mUI(new Ui::ComplianceReportDialog), + mProjectFile(projectFile), + mResultsFile(std::move(resultsFile)) +{ + mUI->setupUi(this); + mUI->mEditProjectName->setText(projectFile->getProjectName()); + connect(mUI->buttonBox, &QDialogButtonBox::clicked, this, &ComplianceReportDialog::buttonClicked); +} + +ComplianceReportDialog::~ComplianceReportDialog() +{ + delete mUI; +} + +void ComplianceReportDialog::buttonClicked(QAbstractButton* button) +{ + switch (mUI->buttonBox->standardButton(button)) { + case QDialogButtonBox::StandardButton::Save: + save(); + break; + case QDialogButtonBox::StandardButton::Close: + close(); + break; + default: + break; + }; +} + +void ComplianceReportDialog::save() +{ + const QString outFile = QFileDialog::getSaveFileName(this, + tr("Compliance report"), + QDir::homePath() + "/misra-c-2012-compliance-report.html", + tr("HTML files (*.html)")); + if (outFile.isEmpty()) + return; + + close(); + + const QString& projectName = mUI->mEditProjectName->text(); + const QString& projectVersion = mUI->mEditProjectVersion->text(); + const bool files = mUI->mCheckFiles->isChecked(); + + if (projectName != mProjectFile->getProjectName()) { + mProjectFile->setProjectName(projectName); + mProjectFile->write(); + } + + QTemporaryFile tempFiles; + if (files && tempFiles.open()) { + QTextStream out(&tempFiles); + FileList fileList; + fileList.addPathList(mProjectFile->getCheckPaths()); + if (!mProjectFile->getImportProject().isEmpty()) { + QFileInfo inf(mProjectFile->getFilename()); + + QString prjfile; + if (QFileInfo(mProjectFile->getImportProject()).isAbsolute()) + prjfile = mProjectFile->getImportProject(); + else + prjfile = inf.canonicalPath() + '/' + mProjectFile->getImportProject(); + + ImportProject p; + try { + p.import(prjfile.toStdString()); + } catch (InternalError &e) { + QMessageBox msg(QMessageBox::Critical, + tr("Save compliance report"), + tr("Failed to import '%1', can not show files in compliance report").arg(prjfile), + QMessageBox::Ok, + this); + msg.exec(); + return; + } + + p.ignorePaths(toStdStringList(mProjectFile->getExcludedPaths())); + + QDir dir(inf.absoluteDir()); + for (const ImportProject::FileSettings& fs: p.fileSettings) + fileList.addFile(dir.relativeFilePath(QString::fromStdString(fs.filename))); + } + + QSet allFiles; + for (const QString &sourcefile: fileList.getFileList()) + addHeaders(sourcefile, allFiles); + for (const QString& fileName: allFiles) { + QFile f(fileName); + if (f.open(QFile::ReadOnly)) { + QCryptographicHash hash(QCryptographicHash::Algorithm::Md5); + if (hash.addData(&f)) { + for (auto b: hash.result()) + out << QString::number((unsigned char)b,16); + out << " " << fileName << "\n"; + } + } + } + tempFiles.close(); + } + + QStringList args{"--compliant=misra-c2012-1.1", + "--compliant=misra-c2012-1.2", + "--project-name=" + projectName, + "--project-version=" + projectVersion, + "--output-file=" + outFile}; + if (files) + args << "--files=" + tempFiles.fileName(); + args << mResultsFile; + + const QString appPath = QFileInfo(QCoreApplication::applicationFilePath()).canonicalPath(); + + QProcess process; +#ifdef Q_OS_WIN + process.start(appPath + "/compliance-report.exe", args); +#else + process.start(appPath + "/compliance-report", args); +#endif + process.waitForFinished(); +} diff --git a/gui/compliancereportdialog.h b/gui/compliancereportdialog.h new file mode 100644 index 000000000..e4ac1973d --- /dev/null +++ b/gui/compliancereportdialog.h @@ -0,0 +1,32 @@ +#ifndef COMPLIANCEREPORTDIALOG_H +#define COMPLIANCEREPORTDIALOG_H + +#include + +namespace Ui { + class ComplianceReportDialog; +} + +class ProjectFile; +class QAbstractButton; + +class ComplianceReportDialog final : public QDialog +{ + Q_OBJECT + +public: + explicit ComplianceReportDialog(ProjectFile* projectFile, QString resultsFile); + ~ComplianceReportDialog() final; + +private slots: + void buttonClicked(QAbstractButton* button); + +private: + void save(); + + Ui::ComplianceReportDialog *mUI; + ProjectFile* mProjectFile; + QString mResultsFile; +}; + +#endif // COMPLIANCEREPORTDIALOG_H diff --git a/gui/compliancereportdialog.ui b/gui/compliancereportdialog.ui new file mode 100644 index 000000000..6692855f2 --- /dev/null +++ b/gui/compliancereportdialog.ui @@ -0,0 +1,78 @@ + + + ComplianceReportDialog + + + + 0 + 0 + 403 + 199 + + + + Dialog + + + + + + + + Project name + + + + + + + + + + Project version + + + + + + + + + + + + List of files with md5 checksums + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close|QDialogButtonBox::Save + + + + + + + + diff --git a/gui/gui.pro b/gui/gui.pro index 63cea5ea5..49f6e37fc 100644 --- a/gui/gui.pro +++ b/gui/gui.pro @@ -63,6 +63,7 @@ win32 { RESOURCES = gui.qrc FORMS = about.ui \ applicationdialog.ui \ + compliancereportdialog.ui \ fileview.ui \ helpdialog.ui \ mainwindow.ui \ @@ -122,6 +123,7 @@ HEADERS += aboutdialog.h \ codeeditstyledialog.h \ codeeditor.h \ common.h \ + compliancereportdialog.h \ csvreport.h \ erroritem.h \ filelist.h \ @@ -162,6 +164,7 @@ SOURCES += aboutdialog.cpp \ codeeditstyledialog.cpp \ codeeditor.cpp \ common.cpp \ + compliancereportdialog.cpp \ csvreport.cpp \ erroritem.cpp \ filelist.cpp \ diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 261edad23..9188ffe62 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -25,6 +25,7 @@ #include "cppcheck.h" #include "errortypes.h" #include "filelist.h" +#include "compliancereportdialog.h" #include "fileviewdialog.h" #include "helpdialog.h" #include "importproject.h" @@ -68,6 +69,7 @@ #include #include #include +#include static const QString OnlineHelpURL("https://cppcheck.sourceforge.io/manual.html"); static const QString compile_commands_json("compile_commands.json"); @@ -154,6 +156,7 @@ MainWindow::MainWindow(TranslationHandler* th, QSettings* settings) : connect(mUI->mActionStop, &QAction::triggered, this, &MainWindow::stopAnalysis); connect(mUI->mActionSave, &QAction::triggered, this, &MainWindow::save); + connect(mUI->mActionComplianceReport, &QAction::triggered, this, &MainWindow::complianceReport); // About menu connect(mUI->mActionAbout, &QAction::triggered, this, &MainWindow::about); @@ -191,6 +194,8 @@ MainWindow::MainWindow(TranslationHandler* th, QSettings* settings) : else formatAndSetTitle(); + mUI->mActionComplianceReport->setVisible(isCppcheckPremium()); + enableCheckButtons(true); mUI->mActionPrint->setShortcut(QKeySequence::Print); @@ -1290,6 +1295,10 @@ void MainWindow::enableCheckButtons(bool enable) } mUI->mActionAnalyzeDirectory->setEnabled(enable); + + if (isCppcheckPremium()) { + mUI->mActionComplianceReport->setEnabled(enable && mProjectFile && mProjectFile->getAddons().contains("misra")); + } } void MainWindow::enableResultsButtons() @@ -1459,6 +1468,20 @@ void MainWindow::save() } } +void MainWindow::complianceReport() +{ + if (isCppcheckPremium() && mProjectFile && mProjectFile->getAddons().contains("misra")) { + QTemporaryFile tempResults; + tempResults.open(); + tempResults.close(); + + mUI->mResults->save(tempResults.fileName(), Report::XMLV2); + + ComplianceReportDialog dlg(mProjectFile, tempResults.fileName()); + dlg.exec(); + } +} + void MainWindow::resultsAdded() {} diff --git a/gui/mainwindow.h b/gui/mainwindow.h index d28e32cc1..540bdf869 100644 --- a/gui/mainwindow.h +++ b/gui/mainwindow.h @@ -168,6 +168,9 @@ public slots: /** @brief Slot to save results */ void save(); + /** @brief Slot to generate compliance report */ + void complianceReport(); + /** @brief Slot to create new project file */ void newProjectFile(); diff --git a/gui/mainwindow.ui b/gui/mainwindow.ui index a7a641c4a..fc2e6cbe1 100644 --- a/gui/mainwindow.ui +++ b/gui/mainwindow.ui @@ -140,6 +140,7 @@ + @@ -914,6 +915,11 @@ C++23 + + + Compliance report... + + diff --git a/gui/projectfile.cpp b/gui/projectfile.cpp index 4717b2521..03e02338a 100644 --- a/gui/projectfile.cpp +++ b/gui/projectfile.cpp @@ -61,6 +61,7 @@ void ProjectFile::clear() mExcludedPaths.clear(); mLibraries.clear(); mPlatform.clear(); + mProjectName.clear(); mSuppressions.clear(); mAddons.clear(); mClangAnalyzer = mClangTidy = false; @@ -208,6 +209,8 @@ bool ProjectFile::read(const QString &filename) readStringList(mCodingStandards, xmlReader, CppcheckXml::CodingStandardElementName); if (xmlReader.name() == QString(CppcheckXml::CertIntPrecisionElementName)) mCertIntPrecision = readInt(xmlReader, 0); + if (xmlReader.name() == QString(CppcheckXml::ProjectNameElementName)) + mProjectName = readString(xmlReader); break; @@ -346,6 +349,32 @@ int ProjectFile::readInt(QXmlStreamReader &reader, int defaultValue) } while (true); } +QString ProjectFile::readString(QXmlStreamReader &reader) +{ + QString ret; + do { + const QXmlStreamReader::TokenType type = reader.readNext(); + switch (type) { + case QXmlStreamReader::Characters: + ret = reader.text().toString(); + FALLTHROUGH; + case QXmlStreamReader::EndElement: + return ret; + // Not handled + case QXmlStreamReader::StartElement: + case QXmlStreamReader::NoToken: + case QXmlStreamReader::Invalid: + case QXmlStreamReader::StartDocument: + case QXmlStreamReader::EndDocument: + case QXmlStreamReader::Comment: + case QXmlStreamReader::DTD: + case QXmlStreamReader::EntityReference: + case QXmlStreamReader::ProcessingInstruction: + break; + } + } while (true); +} + void ProjectFile::readIncludeDirs(QXmlStreamReader &reader) { QXmlStreamReader::TokenType type; @@ -959,6 +988,12 @@ bool ProjectFile::write(const QString &filename) xmlWriter.writeEndElement(); } + if (!mProjectName.isEmpty()) { + xmlWriter.writeStartElement(CppcheckXml::ProjectNameElementName); + xmlWriter.writeCharacters(mProjectName); + xmlWriter.writeEndElement(); + } + xmlWriter.writeEndDocument(); file.close(); return true; diff --git a/gui/projectfile.h b/gui/projectfile.h index d3897a365..dc17a4b00 100644 --- a/gui/projectfile.h +++ b/gui/projectfile.h @@ -165,6 +165,14 @@ public: return mPlatform; } + QString getProjectName() const { + return mProjectName; + } + + void setProjectName(QString projectName) { + mProjectName = std::move(projectName); + } + /** * @brief Get "raw" suppressions. * @return list of suppressions. @@ -420,6 +428,8 @@ protected: static int readInt(QXmlStreamReader &reader, int defaultValue); + static QString readString(QXmlStreamReader &reader); + /** * @brief Read list of include directories from XML. * @param reader XML stream reader. @@ -581,6 +591,9 @@ private: */ QStringList mCodingStandards; + /** @brief Project name, used when generating compliance report */ + QString mProjectName; + int mCertIntPrecision; /** @brief Execute clang analyzer? */ diff --git a/lib/importproject.h b/lib/importproject.h index f5a0b87d1..213d8c0aa 100644 --- a/lib/importproject.h +++ b/lib/importproject.h @@ -184,6 +184,7 @@ namespace CppcheckXml { const char CodingStandardsElementName[] = "coding-standards"; const char CodingStandardElementName[] = "coding-standard"; const char CertIntPrecisionElementName[] = "cert-c-int-precision"; + const char ProjectNameElementName[] = "project-name"; } /// @}