Misra: Integration of MISRA in the GUI. The misra addon can now extract the rule texts from the PDF.

This commit is contained in:
Daniel Marjamäki 2018-01-20 14:13:09 +01:00
parent 0a70b8794c
commit 6f2d4361df
12 changed files with 145 additions and 6 deletions

View File

@ -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=<path-to-rule-texts> main.cpp.dump
# python misra.py --misra-pdf=<path-to-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=<file> to get proper output) [' + id + ']'
sys.stderr.write('[' + location.file + ':' + str(location.linenr) + '] ' + errmsg + '\n')
errmsg = 'misra violation (use --misra-pdf=<file> or --rule-texts=<file> 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] <dumpfiles>
OPTIONS:
--rule-texts=<file> 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=<file> 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=<file>.
""")
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'):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -493,6 +493,13 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="mAddonMisra">
<property name="text">
<string>MISRA C 2012</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_5">
<property name="orientation">

View File

@ -306,6 +306,46 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>Misra addon: rule texts</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The Misra addon can read the rule texts from a file; either a Misra PDF or a plaintext file.&lt;/p&gt;&lt;p&gt;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.&lt;/p&gt;&lt;p&gt;Text: Copy/paste the text in the chapter &amp;quot;Appendix A Summary of guidelines&amp;quot; from the MISRA pdf into a text file.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Misra PDF/Text file</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="mEditMisraFile"/>
</item>
<item>
<widget class="QPushButton" name="mBtnBrowseMisraFile">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">

View File

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

View File

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

View File

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

View File

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