diff --git a/addons/misra.py b/addons/misra.py index 03583bced..2d8014c03 100644 --- a/addons/misra.py +++ b/addons/misra.py @@ -4,7 +4,8 @@ # # Example usage of this addon (scan a sourcefile main.cpp) # cppcheck --dump main.cpp -# python misra.py main.cpp.dump +# python misra.py --rule-texts= main.cpp.dump +# python misra.py --misra-pdf= main.cpp.dump # # Limitations: This addon is released as open source. Rule texts can't be freely # distributed. https://www.misra.org.uk/forum/viewtopic.php?f=56&t=1189 @@ -14,6 +15,9 @@ import cppcheckdata import sys import re +import os +import tempfile +import subprocess ruleTexts = {} @@ -31,8 +35,8 @@ def reportError(location, num1, num2): if num in ruleTexts: errmsg = ruleTexts[num] + ' [' + id + ']' else: - errmsg = 'misra violation (use --rule-texts= to get proper output) [' + id + ']' - sys.stderr.write('[' + location.file + ':' + str(location.linenr) + '] ' + errmsg + '\n') + errmsg = 'misra violation (use --misra-pdf= or --rule-texts= to get proper output) [' + id + ']' + sys.stderr.write('[' + location.file + ':' + str(location.linenr) + '] (style): ' + errmsg + '\n') def simpleMatch(token, pattern): @@ -1024,8 +1028,15 @@ def misra_21_11(data): def loadRuleTexts(filename): num1 = 0 num2 = 0 + appendixA = False for line in open(filename, 'rt'): line = line.replace('\r', '').replace('\n', '') + if not appendixA: + if line.find('Appendix A') >= 0 and line.find('Summary of guidelines') >= 10: + appendixA = True + continue + if line.find('Appendix B') >= 0: + break res = re.match(r'^Rule ([0-9]+).([0-9]+)', line) if res: num1 = int(res.group(1)) @@ -1041,12 +1052,57 @@ def loadRuleTexts(filename): num2 = num2 + 1 continue +def loadRuleTextsFromPdf(filename): + f = tempfile.NamedTemporaryFile(delete=False) + f.close() + print('tempfile:' + f.name) + subprocess.call(['pdftotext', filename, f.name]) + loadRuleTexts(f.name) + try: + os.remove(f.name) + except OSError as err: + print('Failed to remove temporary file ' + f.name) + +if len(sys.argv) == 1: + print(""" +Syntax: misra.py [OPTIONS] + +OPTIONS: + +--rule-texts= Load rule texts from plain text file. + + You can more or less copy/paste the chapter: + Appendix A Summary of guidelines + from the MISRA pdf. + + Format: + + <..arbitrary text..> + Appendix A Summary of guidelines + Dir 1.1 + Rule text for 1.1 + Dir 1.2 + Rule text for 1.2 + <...> + +--misra-pdf= Misra PDF file that rule texts will be extracted from. + + The tool 'pdftotext' from xpdf is used and must be installed. + Debian: sudo apt-get install xpdf + Windows: http://gnuwin32.sourceforge.net/packages/xpdf.htm + + If you don't have 'pdftotext' and don't want to install it then + you can use --rule-texts=. +""") + sys.exit(1) for arg in sys.argv[1:]: if arg == '-verify': VERIFY = True elif arg.startswith('--rule-texts='): loadRuleTexts(arg[13:]) + elif arg.startswith('--misra-pdf='): + loadRuleTextsFromPdf(arg[12:]) for arg in sys.argv[1:]: if not arg.endswith('.dump'): diff --git a/gui/checkthread.cpp b/gui/checkthread.cpp index 6d2a490e3..8fb911b94 100644 --- a/gui/checkthread.cpp +++ b/gui/checkthread.cpp @@ -287,6 +287,12 @@ void CheckThread::runAddonsAndTools(const ImportProject::FileSettings *fileSetti QStringList args; args << addonFilePath << dumpFile; + if (addon == "misra" && !mMisraFile.isEmpty() && QFileInfo(mMisraFile).exists()) { + if (mMisraFile.endsWith('.pdf', Qt::CaseInsensitive)) + args << "--misra-pdf=" + mMisraFile; + else + args << "--rule-texts=" + mMisraFile; + } qDebug() << python << args; QProcess process; diff --git a/gui/checkthread.h b/gui/checkthread.h index f354717f2..0726514fe 100644 --- a/gui/checkthread.h +++ b/gui/checkthread.h @@ -56,6 +56,10 @@ public: mAddonsAndTools = addonsAndTools; } + void setMisraFile(const QString &misraFile) { + mMisraFile = misraFile; + } + void setDataDir(const QString &dataDir) { mDataDir = dataDir; } @@ -148,6 +152,7 @@ private: QString mDataDir; QStringList mClangIncludePaths; QStringList mSuppressions; + QString mMisraFile; }; /// @} #endif // CHECKTHREAD_H diff --git a/gui/common.h b/gui/common.h index 74e77073f..5502fbe7f 100644 --- a/gui/common.h +++ b/gui/common.h @@ -84,6 +84,7 @@ #define SETTINGS_LANGUAGE "Application language" #define SETTINGS_GLOBAL_INCLUDE_PATHS "Global include paths" #define SETTINGS_PYTHON_PATH "Python path" +#define SETTINGS_MISRA_FILE "MISRA C 2012 file" #define SETTINGS_CLANG_PATH "Clang path" #define SETTINGS_VS_INCLUDE_PATHS "VS include paths" #define SETTINGS_INLINE_SUPPRESSIONS "Inline suppressions" diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index e948b74ce..aa91bcc55 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -456,7 +456,7 @@ void MainWindow::doAnalyzeProject(ImportProject p) //mThread->SetanalyzeProject(true); if (mProjectFile) { - mThread->setAddonsAndTools(mProjectFile->getAddonsAndTools()); + mThread->setAddonsAndTools(mProjectFile->getAddonsAndTools(), mSettings->value(SETTINGS_MISRA_FILE).toString()); QString clangHeaders = mSettings->value(SETTINGS_VS_INCLUDE_PATHS).toString(); mThread->setClangIncludePaths(clangHeaders.split(";")); mThread->setSuppressions(mProjectFile->getSuppressions()); @@ -498,6 +498,8 @@ void MainWindow::doAnalyzeFiles(const QStringList &files) mUI.mResults->checkingStarted(fileNames.count()); mThread->setFiles(fileNames); + if (mProjectFile) + mThread->setAddonsAndTools(mProjectFile->getAddonsAndTools(), mSettings->value(SETTINGS_MISRA_FILE).toString()); QDir inf(mCurrentDirectory); const QString checkPath = inf.canonicalPath(); setPath(SETTINGS_LAST_CHECK_PATH, checkPath); @@ -1428,7 +1430,7 @@ void MainWindow::analyzeProject(const ProjectFile *projectFile) QFileInfo inf(projectFile->getFilename()); const QString rootpath = projectFile->getRootPath(); - mThread->setAddonsAndTools(projectFile->getAddonsAndTools()); + mThread->setAddonsAndTools(projectFile->getAddonsAndTools(), mSettings->value(SETTINGS_MISRA_FILE).toString()); mUI.mResults->setTags(projectFile->getTags()); // If the root path is not given or is not "current dir", use project diff --git a/gui/projectfiledialog.cpp b/gui/projectfiledialog.cpp index 26c7a0d6a..bfc9ad9fb 100644 --- a/gui/projectfiledialog.cpp +++ b/gui/projectfiledialog.cpp @@ -163,9 +163,11 @@ void ProjectFileDialog::loadFromProjectFile(const ProjectFile *projectFile) updateAddonCheckBox(mUI.mAddonThreadSafety, projectFile, dataDir, "threadsafety"); updateAddonCheckBox(mUI.mAddonY2038, projectFile, dataDir, "y2038"); updateAddonCheckBox(mUI.mAddonCert, projectFile, dataDir, "cert"); + updateAddonCheckBox(mUI.mAddonMisra, projectFile, dataDir, "misra"); mUI.mAddonY2038->setChecked(projectFile->getAddons().contains("y2038")); mUI.mAddonCert->setChecked(projectFile->getAddons().contains("cert")); + mUI.mAddonMisra->setChecked(projectFile->getAddons().contains("misra")); mUI.mToolClangAnalyzer->setChecked(projectFile->getClangAnalyzer()); mUI.mToolClangTidy->setChecked(projectFile->getClangTidy()); if (CheckThread::clangTidyCmd().isEmpty()) { @@ -202,6 +204,8 @@ void ProjectFileDialog::saveToProjectFile(ProjectFile *projectFile) const list << "y2038"; if (mUI.mAddonCert->isChecked()) list << "cert"; + if (mUI.mAddonMisra->isChecked()) + list << "misra"; projectFile->setAddons(list); projectFile->setClangAnalyzer(mUI.mToolClangAnalyzer->isChecked()); projectFile->setClangTidy(mUI.mToolClangTidy->isChecked()); diff --git a/gui/projectfiledialog.ui b/gui/projectfiledialog.ui index 3e253e8f8..aa4fb531e 100644 --- a/gui/projectfiledialog.ui +++ b/gui/projectfiledialog.ui @@ -493,6 +493,13 @@ + + + + MISRA C 2012 + + + diff --git a/gui/settings.ui b/gui/settings.ui index fa4d342ee..87fe9cf2d 100644 --- a/gui/settings.ui +++ b/gui/settings.ui @@ -306,6 +306,46 @@ + + + + Misra addon: rule texts + + + + + + <html><head/><body><p>The Misra addon can read the rule texts from a file; either a Misra PDF or a plaintext file.</p><p>PDF: To extract text from the MISRA PDF, the misra addon uses the tool 'pdftotext' available in xpdf (crossplatform open source tool). That tool must be installed.</p><p>Text: Copy/paste the text in the chapter &quot;Appendix A Summary of guidelines&quot; from the MISRA pdf into a text file.</p></body></html> + + + true + + + + + + + + + Misra PDF/Text file + + + + + + + + + + ... + + + + + + + + diff --git a/gui/settingsdialog.cpp b/gui/settingsdialog.cpp index 100a45cd4..818ac2bff 100644 --- a/gui/settingsdialog.cpp +++ b/gui/settingsdialog.cpp @@ -54,6 +54,7 @@ SettingsDialog::SettingsDialog(ApplicationList *list, mUI.mShowStatistics->setCheckState(boolToCheckState(settings.value(SETTINGS_SHOW_STATISTICS, false).toBool())); mUI.mShowErrorId->setCheckState(boolToCheckState(settings.value(SETTINGS_SHOW_ERROR_ID, false).toBool())); mUI.mEditPythonPath->setText(settings.value(SETTINGS_PYTHON_PATH, QString()).toString()); + mUI.mEditMisraFile->setText(settings.value(SETTINGS_MISRA_FILE, QString()).toString()); #ifdef Q_OS_WIN //mUI.mTabClang->setVisible(true); @@ -77,6 +78,7 @@ SettingsDialog::SettingsDialog(ApplicationList *list, this, SLOT(editApplication())); connect(mUI.mBtnBrowsePythonPath, &QPushButton::clicked, this, &SettingsDialog::browsePythonPath); + connect(mUI.mBtnBrowseMisraFile, &QPushButton::clicked, this, &SettingsDialog::browseMisraFile); mUI.mListWidget->setSortingEnabled(false); populateApplicationList(); @@ -161,6 +163,7 @@ void SettingsDialog::saveSettingValues() const saveCheckboxValue(&settings, mUI.mShowStatistics, SETTINGS_SHOW_STATISTICS); saveCheckboxValue(&settings, mUI.mShowErrorId, SETTINGS_SHOW_ERROR_ID); settings.setValue(SETTINGS_PYTHON_PATH, mUI.mEditPythonPath->text()); + settings.setValue(SETTINGS_MISRA_FILE, mUI.mEditMisraFile->text()); #ifdef Q_OS_WIN settings.setValue(SETTINGS_CLANG_PATH, mUI.mEditClangPath->text()); @@ -306,6 +309,13 @@ void SettingsDialog::browsePythonPath() mUI.mEditPythonPath->setText(fileName); } +void SettingsDialog::browseMisraFile() +{ + const QString fileName = QFileDialog::getOpenFileName(this, tr("Select MISRA File"), QDir::homePath(), "Misra File (*.pdf *.txt)"); + if (!fileName.isEmpty()) + mUI.mEditMisraFile->setText(fileName); +} + void SettingsDialog::browseClangPath() { QString selectedDir = QFileDialog::getExistingDirectory(this, diff --git a/gui/settingsdialog.h b/gui/settingsdialog.h index 20c4be1dc..304086779 100644 --- a/gui/settingsdialog.h +++ b/gui/settingsdialog.h @@ -129,6 +129,11 @@ protected slots: /** @brief Slot for browsing for the clang binary */ void browseClangPath(); + + /** + * @brief Browse for MISRA file + */ + void browseMisraFile(); protected: /** * @brief Clear all applications from the list and re insert them from mTempApplications diff --git a/gui/threadhandler.cpp b/gui/threadhandler.cpp index 3b3a096c1..045fa0af4 100644 --- a/gui/threadhandler.cpp +++ b/gui/threadhandler.cpp @@ -94,6 +94,7 @@ void ThreadHandler::check(const Settings &settings) for (int i = 0; i < mRunningThreadCount; i++) { mThreads[i]->setAddonsAndTools(mAddonsAndTools); + mThreads[i]->setMisraFile(mMisraFile); mThreads[i]->setSuppressions(mSuppressions); mThreads[i]->setClangIncludePaths(mClangIncludePaths); mThreads[i]->setDataDir(mDataDir); diff --git a/gui/threadhandler.h b/gui/threadhandler.h index 21f421ce1..43130be93 100644 --- a/gui/threadhandler.h +++ b/gui/threadhandler.h @@ -71,8 +71,9 @@ public: */ void saveSettings(QSettings &settings) const; - void setAddonsAndTools(const QStringList &addonsAndTools) { + void setAddonsAndTools(const QStringList &addonsAndTools, const QString misraFile) { mAddonsAndTools = addonsAndTools; + mMisraFile = misraFile; } void setSuppressions(const QStringList &s) { @@ -258,6 +259,7 @@ protected: QStringList mClangIncludePaths; QString mDataDir; + QString mMisraFile; private: /**