diff --git a/src/plugins/clangstaticanalyzer/ClangStaticAnalyzer.json.in b/src/plugins/clangstaticanalyzer/ClangStaticAnalyzer.json.in new file mode 100644 index 0000000000000000000000000000000000000000..8be50b27da55580f34e5002b9bc575bed42dbd8b --- /dev/null +++ b/src/plugins/clangstaticanalyzer/ClangStaticAnalyzer.json.in @@ -0,0 +1,19 @@ +{ + \"Name\" : \"ClangStaticAnalyzer\", + \"Version\" : \"$$QTCREATOR_VERSION\", + \"CompatVersion\" : \"$$QTCREATOR_COMPAT_VERSION\", + \"Vendor\" : \"The Qt Company Ltd\", + \"Copyright\" : \"(C) 2016 The Qt Company Ltd\", + \"License\" : [ \"Commercial Usage\", + \"\", + \"Licensees holding valid commercial Qt licenses may use this file in accordance with the commercial license agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and The Qt Company. For licensing terms and conditions see https://www.qt.io/terms-conditions. For further information use the contact form at https://www.qt.io/contact-us.\", + \"\", + \"GNU General Public License Usage\", + \"\", + \"Alternatively, this file may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this file. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html.\" + ], + \"Category\" : \"Code Analyzer\", + \"Description\" : \"ClangStaticAnalyzer Plugin.\", + \"Url\" : \"http://www.qt.io\", + $$dependencyList +} diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzer.pro b/src/plugins/clangstaticanalyzer/clangstaticanalyzer.pro new file mode 100644 index 0000000000000000000000000000000000000000..cdffb1f1d1ccc1a4f4779387be4ba7d01e37676c --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzer.pro @@ -0,0 +1,54 @@ +TARGET = ClangStaticAnalyzer +TEMPLATE = lib + +include(../../qtcreatorplugin.pri) +include(clangstaticanalyzer_dependencies.pri) + +SOURCES += \ + clangstaticanalyzerconfigwidget.cpp \ + clangstaticanalyzerdiagnostic.cpp \ + clangstaticanalyzerdiagnosticmodel.cpp \ + clangstaticanalyzerdiagnosticview.cpp \ + clangstaticanalyzerlogfilereader.cpp \ + clangstaticanalyzerplugin.cpp \ + clangstaticanalyzerprojectsettings.cpp \ + clangstaticanalyzerprojectsettingsmanager.cpp \ + clangstaticanalyzerprojectsettingswidget.cpp \ + clangstaticanalyzerruncontrol.cpp \ + clangstaticanalyzerruncontrolfactory.cpp \ + clangstaticanalyzerrunner.cpp \ + clangstaticanalyzersettings.cpp \ + clangstaticanalyzertool.cpp \ + clangstaticanalyzerutils.cpp + +HEADERS += \ + clangstaticanalyzerconfigwidget.h \ + clangstaticanalyzerconstants.h \ + clangstaticanalyzerdiagnostic.h \ + clangstaticanalyzerdiagnosticmodel.h \ + clangstaticanalyzerdiagnosticview.h \ + clangstaticanalyzer_global.h \ + clangstaticanalyzerlogfilereader.h \ + clangstaticanalyzerplugin.h \ + clangstaticanalyzerprojectsettings.h \ + clangstaticanalyzerprojectsettingsmanager.h \ + clangstaticanalyzerprojectsettingswidget.h \ + clangstaticanalyzerruncontrolfactory.h \ + clangstaticanalyzerruncontrol.h \ + clangstaticanalyzerrunner.h \ + clangstaticanalyzersettings.h \ + clangstaticanalyzertool.h \ + clangstaticanalyzerutils.h + +FORMS += \ + clangstaticanalyzerconfigwidget.ui \ + clangstaticanalyzerprojectsettingswidget.ui + +equals(TEST, 1) { + HEADERS += clangstaticanalyzerunittests.h + SOURCES += clangstaticanalyzerunittests.cpp + RESOURCES += clangstaticanalyzerunittests.qrc +} + +DISTFILES += \ + tests/tests.pri diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzer.qbs b/src/plugins/clangstaticanalyzer/clangstaticanalyzer.qbs new file mode 100644 index 0000000000000000000000000000000000000000..2562b46b3c1f6b500373e3e55200a4327703d84e --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzer.qbs @@ -0,0 +1,74 @@ +import qbs + +QtcPlugin { + name: "ClangStaticAnalyzer" + + Depends { name: "AnalyzerBase" } + Depends { name: "Core" } + Depends { name: "CppTools" } + Depends { name: "ExtensionSystem" } + Depends { name: "ProjectExplorer" } + Depends { name: "QtcSsh" } + Depends { name: "Utils" } + + Depends { name: "Qt.widgets" } + + pluginTestDepends: [ + "QbsProjectManager", + "QmakeProjectManager", + ] + + files: [ + "clangstaticanalyzerconfigwidget.cpp", + "clangstaticanalyzerconfigwidget.h", + "clangstaticanalyzerconfigwidget.ui", + "clangstaticanalyzerconstants.h", + "clangstaticanalyzerdiagnostic.cpp", + "clangstaticanalyzerdiagnostic.h", + "clangstaticanalyzerdiagnosticmodel.cpp", + "clangstaticanalyzerdiagnosticmodel.h", + "clangstaticanalyzerdiagnosticview.cpp", + "clangstaticanalyzerdiagnosticview.h", + "clangstaticanalyzerlogfilereader.cpp", + "clangstaticanalyzerlogfilereader.h", + "clangstaticanalyzerplugin.cpp", + "clangstaticanalyzerplugin.h", + "clangstaticanalyzerprojectsettings.cpp", + "clangstaticanalyzerprojectsettings.h", + "clangstaticanalyzerprojectsettingsmanager.cpp", + "clangstaticanalyzerprojectsettingsmanager.h", + "clangstaticanalyzerprojectsettingswidget.cpp", + "clangstaticanalyzerprojectsettingswidget.h", + "clangstaticanalyzerprojectsettingswidget.ui", + "clangstaticanalyzerruncontrol.cpp", + "clangstaticanalyzerruncontrol.h", + "clangstaticanalyzerruncontrolfactory.cpp", + "clangstaticanalyzerruncontrolfactory.h", + "clangstaticanalyzerrunner.cpp", + "clangstaticanalyzerrunner.h", + "clangstaticanalyzersettings.cpp", + "clangstaticanalyzersettings.h", + "clangstaticanalyzertool.cpp", + "clangstaticanalyzertool.h", + "clangstaticanalyzerutils.cpp", + "clangstaticanalyzerutils.h", + "clangstaticanalyzer_global.h", + ] + + Group { + name: "Unit tests" + condition: project.testsEnabled + files: [ + "clangstaticanalyzerunittests.cpp", + "clangstaticanalyzerunittests.h", + "clangstaticanalyzerunittests.qrc", + ] + } + + Group { + name: "Unit test resources" + prefix: "unit-tests/" + fileTags: [] + files: ["**/*"] + } +} diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzer_dependencies.pri b/src/plugins/clangstaticanalyzer/clangstaticanalyzer_dependencies.pri new file mode 100644 index 0000000000000000000000000000000000000000..b0993923732bba98a493380a4e594128552a0e1d --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzer_dependencies.pri @@ -0,0 +1,10 @@ +QTC_PLUGIN_NAME = ClangStaticAnalyzer +QTC_LIB_DEPENDS += \ + extensionsystem \ + utils +QTC_PLUGIN_DEPENDS += \ + analyzerbase \ + cpptools +QTC_TEST_DEPENDS += \ + qbsprojectmanager \ + qmakeprojectmanager diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzer_global.h b/src/plugins/clangstaticanalyzer/clangstaticanalyzer_global.h new file mode 100644 index 0000000000000000000000000000000000000000..39fb52397659c6d9a12fbcac4afed448c680774b --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzer_global.h @@ -0,0 +1,38 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#ifndef CLANGSTATICANALYZER_GLOBAL_H +#define CLANGSTATICANALYZER_GLOBAL_H + +#include <QtGlobal> + +#if defined(CLANGSTATICANALYZER_LIBRARY) +# define CLANGSTATICANALYZER_EXPORT Q_DECL_EXPORT +#else +# define CLANGSTATICANALYZER_EXPORT Q_DECL_IMPORT +#endif + +#endif // CLANGSTATICANALYZER_GLOBAL_H + diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerconfigwidget.cpp b/src/plugins/clangstaticanalyzer/clangstaticanalyzerconfigwidget.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0ffe9ca8c275bbbfce3e17c5b9ca1887f3888acb --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerconfigwidget.cpp @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "clangstaticanalyzerconfigwidget.h" +#include "ui_clangstaticanalyzerconfigwidget.h" + +#include "clangstaticanalyzerutils.h" + +#include <QThread> + +namespace ClangStaticAnalyzer { +namespace Internal { + +ClangStaticAnalyzerConfigWidget::ClangStaticAnalyzerConfigWidget( + ClangStaticAnalyzerSettings *settings, + QWidget *parent) + : QWidget(parent) + , m_ui(new Ui::ClangStaticAnalyzerConfigWidget) + , m_settings(settings) +{ + m_ui->setupUi(this); + + Utils::PathChooser * const chooser = m_ui->clangExecutableChooser; + chooser->setExpectedKind(Utils::PathChooser::ExistingCommand); + chooser->setHistoryCompleter(QLatin1String("ClangStaticAnalyzer.ClangCommand.History")); + chooser->setPromptDialogTitle(tr("Clang Command")); + const auto validator = [chooser](Utils::FancyLineEdit *edit, QString *errorMessage) { + const QString currentFilePath = chooser->fileName().toString(); + Utils::PathChooser pc; + Utils::PathChooser *helperPathChooser; + if (currentFilePath.isEmpty()) { + pc.setExpectedKind(chooser->expectedKind()); + pc.setPath(edit->placeholderText()); + helperPathChooser = &pc; + } else { + helperPathChooser = chooser; + } + return chooser->defaultValidationFunction()(helperPathChooser->lineEdit(), errorMessage) + && isClangExecutableUsable(helperPathChooser->fileName().toString(), errorMessage); + }; + chooser->setValidationFunction(validator); + bool clangExeIsSet; + const QString clangExe = settings->clangExecutable(&clangExeIsSet); + chooser->lineEdit()->setPlaceholderText(settings->defaultClangExecutable()); + if (clangExeIsSet) { + chooser->setPath(clangExe); + } else { + // Setting an empty string does not trigger the validator, as that is the initial value + // in the line edit. + chooser->setPath(QLatin1String(" ")); + chooser->lineEdit()->clear(); + } + connect(m_ui->clangExecutableChooser, &Utils::PathChooser::rawPathChanged, + [settings](const QString &path) { settings->setClangExecutable(path); }); + + m_ui->simultaneousProccessesSpinBox->setValue(settings->simultaneousProcesses()); + m_ui->simultaneousProccessesSpinBox->setMinimum(1); + m_ui->simultaneousProccessesSpinBox->setMaximum(QThread::idealThreadCount()); + connect(m_ui->simultaneousProccessesSpinBox, + static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), + [settings](int count) { settings->setSimultaneousProcesses(count); }); +} + +ClangStaticAnalyzerConfigWidget::~ClangStaticAnalyzerConfigWidget() +{ + delete m_ui; +} + +} // namespace Internal +} // namespace ClangStaticAnalyzer diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerconfigwidget.h b/src/plugins/clangstaticanalyzer/clangstaticanalyzerconfigwidget.h new file mode 100644 index 0000000000000000000000000000000000000000..4655a07eec81e4779d15dce6b0f079cd646d8029 --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerconfigwidget.h @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#ifndef CLANGSTATICANALYZERCONFIGWIDGET_H +#define CLANGSTATICANALYZERCONFIGWIDGET_H + +#include "clangstaticanalyzersettings.h" + +#include <QWidget> + +namespace ClangStaticAnalyzer { +namespace Internal { + +namespace Ui { class ClangStaticAnalyzerConfigWidget; } + +class ClangStaticAnalyzerConfigWidget : public QWidget +{ + Q_OBJECT + +public: + explicit ClangStaticAnalyzerConfigWidget(ClangStaticAnalyzerSettings *settings, + QWidget *parent = 0); + ~ClangStaticAnalyzerConfigWidget(); + +private: + Ui::ClangStaticAnalyzerConfigWidget *m_ui; + ClangStaticAnalyzerSettings *m_settings; +}; + +} // namespace Internal +} // namespace ClangStaticAnalyzer + +#endif // CLANGSTATICANALYZERCONFIGWIDGET_H diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerconfigwidget.ui b/src/plugins/clangstaticanalyzer/clangstaticanalyzerconfigwidget.ui new file mode 100644 index 0000000000000000000000000000000000000000..800733a33179b30abf80fbcf16effc30b4004048 --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerconfigwidget.ui @@ -0,0 +1,99 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ClangStaticAnalyzer::Internal::ClangStaticAnalyzerConfigWidget</class> + <widget class="QWidget" name="ClangStaticAnalyzer::Internal::ClangStaticAnalyzerConfigWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>300</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>General</string> + </property> + <layout class="QFormLayout" name="formLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Clang executable:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="Utils::PathChooser" name="clangExecutableChooser" native="true"/> + </item> + </layout> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Simultaneous processes:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QSpinBox" name="simultaneousProccessesSpinBox"> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>32</number> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>183</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>Utils::PathChooser</class> + <extends>QWidget</extends> + <header location="global">utils/pathchooser.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerconstants.h b/src/plugins/clangstaticanalyzer/clangstaticanalyzerconstants.h new file mode 100644 index 0000000000000000000000000000000000000000..66b1f149fe873e064db3c41b71bb563401675030 --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerconstants.h @@ -0,0 +1,38 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#ifndef CLANGSTATICANALYZERCONSTANTS_H +#define CLANGSTATICANALYZERCONSTANTS_H + +namespace ClangStaticAnalyzer { +namespace Constants { + +const char SETTINGS_ID[] = "ClangStaticAnalyzer"; +const char CLANGSTATICANALYZER_RUN_MODE[] = "ClangStaticAnalyzer.RunMode"; + +} // Constants +} // ClangStaticAnalyzer + +#endif // CLANGSTATICANALYZERCONSTANTS_H diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerdiagnostic.cpp b/src/plugins/clangstaticanalyzer/clangstaticanalyzerdiagnostic.cpp new file mode 100644 index 0000000000000000000000000000000000000000..173e5ee5964adcfb5b21806cdcef365b8e1342f4 --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerdiagnostic.cpp @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "clangstaticanalyzerdiagnostic.h" + +namespace ClangStaticAnalyzer { +namespace Internal { + +ExplainingStep::ExplainingStep() + : depth(0) +{ +} + +bool ExplainingStep::isValid() const +{ + return location.isValid() && !ranges.isEmpty() && !message.isEmpty(); +} + +bool Diagnostic::isValid() const +{ + return !description.isEmpty(); +} + +} // namespace Internal +} // namespace ClangStaticAnalyzer diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerdiagnostic.h b/src/plugins/clangstaticanalyzer/clangstaticanalyzerdiagnostic.h new file mode 100644 index 0000000000000000000000000000000000000000..6da02dfaa974d4d18b7ff65b8c817077ed52e8fe --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerdiagnostic.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#ifndef CLANGSTATICANALZYERDIAGNOSTIC_H +#define CLANGSTATICANALZYERDIAGNOSTIC_H + +#include <analyzerbase/diagnosticlocation.h> + +#include <QList> +#include <QMetaType> +#include <QString> + +namespace ClangStaticAnalyzer { +namespace Internal { + +class ExplainingStep +{ +public: + ExplainingStep(); + + bool isValid() const; + + QString message; + QString extendedMessage; + Analyzer::DiagnosticLocation location; + QList<Analyzer::DiagnosticLocation> ranges; + int depth; +}; + +class Diagnostic +{ +public: + bool isValid() const; + + QString description; + QString category; + QString type; + QString issueContextKind; + QString issueContext; + Analyzer::DiagnosticLocation location; + QList<ExplainingStep> explainingSteps; +}; + +} // namespace Internal +} // namespace ClangStaticAnalyzer + +Q_DECLARE_METATYPE(ClangStaticAnalyzer::Internal::Diagnostic) + +#endif // CLANGSTATICANALZYERDIAGNOSTIC_H diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerdiagnosticmodel.cpp b/src/plugins/clangstaticanalyzer/clangstaticanalyzerdiagnosticmodel.cpp new file mode 100644 index 0000000000000000000000000000000000000000..fa0625891a3197b46fe5b5e69cc10cc5cc188fdb --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerdiagnosticmodel.cpp @@ -0,0 +1,357 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "clangstaticanalyzerdiagnosticmodel.h" + +#include "clangstaticanalyzerdiagnosticview.h" +#include "clangstaticanalyzerprojectsettingsmanager.h" +#include "clangstaticanalyzerutils.h" + +#include <projectexplorer/project.h> +#include <projectexplorer/session.h> +#include <utils/qtcassert.h> + +#include <QCoreApplication> +#include <QFileInfo> + +#include <cmath> + +namespace ClangStaticAnalyzer { +namespace Internal { + +class DiagnosticItem : public Utils::TreeItem +{ +public: + DiagnosticItem(const Diagnostic &diag); + + Diagnostic diagnostic() const { return m_diagnostic; } + +private: + QVariant data(int column, int role) const override; + + const Diagnostic m_diagnostic; +}; + +class ExplainingStepItem : public Utils::TreeItem +{ +public: + ExplainingStepItem(const ExplainingStep &step); + +private: + QVariant data(int column, int role) const override; + + const ExplainingStep m_step; +}; + +ClangStaticAnalyzerDiagnosticModel::ClangStaticAnalyzerDiagnosticModel(QObject *parent) + : Utils::TreeModel(parent) +{ + setHeader(QStringList() << tr("Issue") << tr("Location")); +} + +void ClangStaticAnalyzerDiagnosticModel::addDiagnostics(const QList<Diagnostic> &diagnostics) +{ + foreach (const Diagnostic &d, diagnostics) + rootItem()->appendChild(new DiagnosticItem(d)); +} + +QList<Diagnostic> ClangStaticAnalyzerDiagnosticModel::diagnostics() const +{ + QList<Diagnostic> diags; + foreach (const Utils::TreeItem * const item, rootItem()->children()) + diags << static_cast<const DiagnosticItem *>(item)->diagnostic(); + return diags; +} + +static QString createDiagnosticToolTipString(const Diagnostic &diagnostic) +{ + typedef QPair<QString, QString> StringPair; + QList<StringPair> lines; + + if (!diagnostic.category.isEmpty()) { + lines << qMakePair( + QCoreApplication::translate("ClangStaticAnalyzer::Diagnostic", "Category:"), + diagnostic.category.toHtmlEscaped()); + } + + if (!diagnostic.type.isEmpty()) { + lines << qMakePair( + QCoreApplication::translate("ClangStaticAnalyzer::Diagnostic", "Type:"), + diagnostic.type.toHtmlEscaped()); + } + + if (!diagnostic.issueContext.isEmpty() && !diagnostic.issueContextKind.isEmpty()) { + lines << qMakePair( + QCoreApplication::translate("ClangStaticAnalyzer::Diagnostic", "Context:"), + diagnostic.issueContextKind.toHtmlEscaped() + QLatin1Char(' ') + + diagnostic.issueContext.toHtmlEscaped()); + } + + lines << qMakePair( + QCoreApplication::translate("ClangStaticAnalyzer::Diagnostic", "Location:"), + createFullLocationString(diagnostic.location)); + + QString html = QLatin1String("<html>" + "<head>" + "<style>dt { font-weight:bold; } dd { font-family: monospace; }</style>\n" + "<body><dl>"); + + foreach (const StringPair &pair, lines) { + html += QLatin1String("<dt>"); + html += pair.first; + html += QLatin1String("</dt><dd>"); + html += pair.second; + html += QLatin1String("</dd>\n"); + } + html += QLatin1String("</dl></body></html>"); + return html; +} + +static QString createExplainingStepToolTipString(const ExplainingStep &step) +{ + if (step.message == step.extendedMessage) + return createFullLocationString(step.location); + + typedef QPair<QString, QString> StringPair; + QList<StringPair> lines; + + if (!step.message.isEmpty()) { + lines << qMakePair( + QCoreApplication::translate("ClangStaticAnalyzer::ExplainingStep", "Message:"), + step.message.toHtmlEscaped()); + } + if (!step.extendedMessage.isEmpty()) { + lines << qMakePair( + QCoreApplication::translate("ClangStaticAnalyzer::ExplainingStep", "Extended Message:"), + step.extendedMessage.toHtmlEscaped()); + } + + lines << qMakePair( + QCoreApplication::translate("ClangStaticAnalyzer::ExplainingStep", "Location:"), + createFullLocationString(step.location)); + + QString html = QLatin1String("<html>" + "<head>" + "<style>dt { font-weight:bold; } dd { font-family: monospace; }</style>\n" + "<body><dl>"); + + foreach (const StringPair &pair, lines) { + html += QLatin1String("<dt>"); + html += pair.first; + html += QLatin1String("</dt><dd>"); + html += pair.second; + html += QLatin1String("</dd>\n"); + } + html += QLatin1String("</dl></body></html>"); + return html; +} + +static QString createLocationString(const Analyzer::DiagnosticLocation &location) +{ + const QString filePath = location.filePath; + const QString lineNumber = QString::number(location.line); + const QString fileAndLine = filePath + QLatin1Char(':') + lineNumber; + return QLatin1String("in ") + fileAndLine; +} + +static QString createExplainingStepNumberString(int number) +{ + const int fieldWidth = 2; + return QString::fromLatin1("%1:").arg(number, fieldWidth); +} + +static QString createExplainingStepString(const ExplainingStep &explainingStep, int number) +{ + return createExplainingStepNumberString(number) + + QLatin1Char(' ') + + explainingStep.extendedMessage + + QLatin1Char(' ') + + createLocationString(explainingStep.location); +} + +static QString fullText(const Diagnostic &diagnostic) +{ + // Summary. + QString text = diagnostic.category + QLatin1String(": ") + diagnostic.type; + if (diagnostic.type != diagnostic.description) + text += QLatin1String(": ") + diagnostic.description; + text += QLatin1Char('\n'); + + // Explaining steps. + int explainingStepNumber = 1; + foreach (const ExplainingStep &explainingStep, diagnostic.explainingSteps) { + text += createExplainingStepString(explainingStep, explainingStepNumber++) + + QLatin1Char('\n'); + } + + text.chop(1); // Trailing newline. + return text; +} + + +DiagnosticItem::DiagnosticItem(const Diagnostic &diag) : m_diagnostic(diag) +{ + // Don't show explaining steps if they add no information. + if (diag.explainingSteps.count() == 1) { + const ExplainingStep &step = diag.explainingSteps.first(); + if (step.message == diag.description && step.location == diag.location) + return; + } + + foreach (const ExplainingStep &s, diag.explainingSteps) + appendChild(new ExplainingStepItem(s)); +} + +QVariant locationData(int role, const Analyzer::DiagnosticLocation &location) +{ + switch (role) { + case Analyzer::DetailedErrorView::LocationRole: + return QVariant::fromValue(location); + case Qt::ToolTipRole: + return location.filePath.isEmpty() ? QVariant() : QVariant(location.filePath); + default: + return QVariant(); + } +} + +QVariant DiagnosticItem::data(int column, int role) const +{ + if (column == Analyzer::DetailedErrorView::LocationColumn) + return locationData(role, m_diagnostic.location); + + // DiagnosticColumn + switch (role) { + case Analyzer::DetailedErrorView::FullTextRole: + return fullText(m_diagnostic); + case ClangStaticAnalyzerDiagnosticModel::DiagnosticRole: + return QVariant::fromValue(m_diagnostic); + case Qt::DisplayRole: + return m_diagnostic.description; + case Qt::ToolTipRole: + return createDiagnosticToolTipString(m_diagnostic); + default: + return QVariant(); + } +} + +ExplainingStepItem::ExplainingStepItem(const ExplainingStep &step) : m_step(step) +{ +} + +QVariant ExplainingStepItem::data(int column, int role) const +{ + if (column == Analyzer::DetailedErrorView::LocationColumn) + return locationData(role, m_step.location); + + // DiagnosticColumn + switch (role) { + case Analyzer::DetailedErrorView::FullTextRole: + return fullText(static_cast<DiagnosticItem *>(parent())->diagnostic()); + case ClangStaticAnalyzerDiagnosticModel::DiagnosticRole: + return QVariant::fromValue(static_cast<DiagnosticItem *>(parent())->diagnostic()); + case Qt::DisplayRole: { + const int row = parent()->children().indexOf(const_cast<ExplainingStepItem *>(this)) + 1; + const int padding = static_cast<int>(std::log10(parent()->rowCount())) + - static_cast<int>(std::log10(row)); + return QString::fromLatin1("%1%2: %3") + .arg(QString(padding, QLatin1Char(' '))) + .arg(row) + .arg(m_step.message); + } + case Qt::ToolTipRole: + return createExplainingStepToolTipString(m_step); + default: + return QVariant(); + } +} + + +ClangStaticAnalyzerDiagnosticFilterModel::ClangStaticAnalyzerDiagnosticFilterModel(QObject *parent) + : QSortFilterProxyModel(parent) +{ + // So that when a user closes and re-opens a project and *then* clicks "Suppress", + // we enter that information into the project settings. + connect(ProjectExplorer::SessionManager::instance(), + &ProjectExplorer::SessionManager::projectAdded, this, + [this](ProjectExplorer::Project *project) { + if (!m_project && project->projectDirectory() == m_lastProjectDirectory) + setProject(project); + }); +} + +void ClangStaticAnalyzerDiagnosticFilterModel::setProject(ProjectExplorer::Project *project) +{ + QTC_ASSERT(project, return); + if (m_project) { + disconnect(ProjectSettingsManager::getSettings(m_project), + &ProjectSettings::suppressedDiagnosticsChanged, this, + &ClangStaticAnalyzerDiagnosticFilterModel::handleSuppressedDiagnosticsChanged); + } + m_project = project; + m_lastProjectDirectory = m_project->projectDirectory(); + connect(ProjectSettingsManager::getSettings(m_project), + &ProjectSettings::suppressedDiagnosticsChanged, + this, &ClangStaticAnalyzerDiagnosticFilterModel::handleSuppressedDiagnosticsChanged); + handleSuppressedDiagnosticsChanged(); +} + +void ClangStaticAnalyzerDiagnosticFilterModel::addSuppressedDiagnostic( + const SuppressedDiagnostic &diag) +{ + QTC_ASSERT(!m_project, return); + m_suppressedDiagnostics << diag; + invalidate(); +} + +bool ClangStaticAnalyzerDiagnosticFilterModel::filterAcceptsRow(int sourceRow, + const QModelIndex &sourceParent) const +{ + if (sourceParent.isValid()) + return true; + const Diagnostic diag = static_cast<ClangStaticAnalyzerDiagnosticModel *>(sourceModel()) + ->diagnostics().at(sourceRow); + foreach (const SuppressedDiagnostic &d, m_suppressedDiagnostics) { + if (d.description != diag.description) + continue; + QString filePath = d.filePath.toString(); + QFileInfo fi(filePath); + if (fi.isRelative()) + filePath = m_lastProjectDirectory.toString() + QLatin1Char('/') + filePath; + if (filePath == diag.location.filePath) + return false; + } + return true; +} + +void ClangStaticAnalyzerDiagnosticFilterModel::handleSuppressedDiagnosticsChanged() +{ + QTC_ASSERT(m_project, return); + m_suppressedDiagnostics + = ProjectSettingsManager::getSettings(m_project)->suppressedDiagnostics(); + invalidate(); +} + +} // namespace Internal +} // namespace ClangStaticAnalyzer diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerdiagnosticmodel.h b/src/plugins/clangstaticanalyzer/clangstaticanalyzerdiagnosticmodel.h new file mode 100644 index 0000000000000000000000000000000000000000..9c93fb9f7791905ea5f64fe31d309bc2bf1962a1 --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerdiagnosticmodel.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#ifndef CLANGSTATICANALYZERDIAGNOSTICMODEL_H +#define CLANGSTATICANALYZERDIAGNOSTICMODEL_H + +#include "clangstaticanalyzerdiagnostic.h" +#include "clangstaticanalyzerprojectsettings.h" + +#include <analyzerbase/detailederrorview.h> +#include <utils/fileutils.h> +#include <utils/treemodel.h> + +#include <QPointer> +#include <QSortFilterProxyModel> + +namespace ProjectExplorer { class Project; } + +namespace ClangStaticAnalyzer { +namespace Internal { + +class ClangStaticAnalyzerDiagnosticModel : public Utils::TreeModel +{ + Q_OBJECT + +public: + ClangStaticAnalyzerDiagnosticModel(QObject *parent = 0); + + void addDiagnostics(const QList<Diagnostic> &diagnostics); + QList<Diagnostic> diagnostics() const; + + enum ItemRole { + DiagnosticRole = Analyzer::DetailedErrorView::FullTextRole + 1 + }; +}; + +class ClangStaticAnalyzerDiagnosticFilterModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + ClangStaticAnalyzerDiagnosticFilterModel(QObject *parent = 0); + + void setProject(ProjectExplorer::Project *project); + void addSuppressedDiagnostic(const SuppressedDiagnostic &diag); + ProjectExplorer::Project *project() const { return m_project; } + +private: + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; + void handleSuppressedDiagnosticsChanged(); + + QPointer<ProjectExplorer::Project> m_project; + Utils::FileName m_lastProjectDirectory; + SuppressedDiagnosticsList m_suppressedDiagnostics; +}; + +} // namespace Internal +} // namespace ClangStaticAnalyzer + +#endif // CLANGSTATICANALYZERDIAGNOSTICMODEL_H diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerdiagnosticview.cpp b/src/plugins/clangstaticanalyzer/clangstaticanalyzerdiagnosticview.cpp new file mode 100644 index 0000000000000000000000000000000000000000..69fb87a3bb5f2f68b8653da3b56d336fabfe8cbe --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerdiagnosticview.cpp @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "clangstaticanalyzerdiagnosticview.h" + +#include "clangstaticanalyzerdiagnosticmodel.h" +#include "clangstaticanalyzerprojectsettings.h" +#include "clangstaticanalyzerprojectsettingsmanager.h" +#include "clangstaticanalyzerutils.h" + +#include <utils/fileutils.h> +#include <utils/qtcassert.h> + +#include <QAction> +#include <QDebug> + +using namespace Analyzer; + +namespace ClangStaticAnalyzer { +namespace Internal { + +ClangStaticAnalyzerDiagnosticView::ClangStaticAnalyzerDiagnosticView(QWidget *parent) + : Analyzer::DetailedErrorView(parent) +{ + m_suppressAction = new QAction(tr("Suppress this diagnostic"), this); + connect(m_suppressAction, &QAction::triggered, [this](bool) { suppressCurrentDiagnostic(); }); +} + +void ClangStaticAnalyzerDiagnosticView::suppressCurrentDiagnostic() +{ + const QModelIndexList indexes = selectionModel()->selectedRows(); + QTC_ASSERT(indexes.count() == 1, return); + const Diagnostic diag = model()->data(indexes.first(), + ClangStaticAnalyzerDiagnosticModel::DiagnosticRole) + .value<Diagnostic>(); + QTC_ASSERT(diag.isValid(), return); + + // If the original project was closed, we work directly on the filter model, otherwise + // we go via the project settings. + auto * const filterModel = static_cast<ClangStaticAnalyzerDiagnosticFilterModel *>(model()); + ProjectExplorer::Project * const project = filterModel->project(); + if (project) { + Utils::FileName filePath = Utils::FileName::fromString(diag.location.filePath); + const Utils::FileName relativeFilePath + = filePath.relativeChildPath(project->projectDirectory()); + if (!relativeFilePath.isEmpty()) + filePath = relativeFilePath; + const SuppressedDiagnostic supDiag(filePath, diag.description, diag.issueContextKind, + diag.issueContext, diag.explainingSteps.count()); + ProjectSettingsManager::getSettings(project)->addSuppressedDiagnostic(supDiag); + } else { + filterModel->addSuppressedDiagnostic(SuppressedDiagnostic(diag)); + } +} + +QList<QAction *> ClangStaticAnalyzerDiagnosticView::customActions() const +{ + return QList<QAction *>() << m_suppressAction; +} + +} // namespace Internal +} // namespace ClangStaticAnalyzer diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerdiagnosticview.h b/src/plugins/clangstaticanalyzer/clangstaticanalyzerdiagnosticview.h new file mode 100644 index 0000000000000000000000000000000000000000..392294fd86135db33fd34d3211e1c96c50d5a1d2 --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerdiagnosticview.h @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#ifndef CLANGSTATICANALYZERDIAGNOSTICVIEW_H +#define CLANGSTATICANALYZERDIAGNOSTICVIEW_H + +#include <analyzerbase/detailederrorview.h> + +namespace ClangStaticAnalyzer { +namespace Internal { + +class ClangStaticAnalyzerDiagnosticView : public Analyzer::DetailedErrorView +{ + Q_OBJECT + +public: + ClangStaticAnalyzerDiagnosticView(QWidget *parent = 0); + +private: + void suppressCurrentDiagnostic(); + + QList<QAction *> customActions() const; + + QAction *m_suppressAction; +}; + +} // namespace Internal +} // namespace ClangStaticAnalyzer + +#endif // CLANGSTATICANALYZERDIAGNOSTICVIEW_H diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerlogfilereader.cpp b/src/plugins/clangstaticanalyzer/clangstaticanalyzerlogfilereader.cpp new file mode 100644 index 0000000000000000000000000000000000000000..dd0727864fc0a3814cf334e9898c334f080aafd5 --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerlogfilereader.cpp @@ -0,0 +1,381 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "clangstaticanalyzerlogfilereader.h" + +#include <QDebug> +#include <QObject> +#include <QFile> +#include <QFileInfo> +#include <QXmlStreamReader> + +#include <utils/qtcassert.h> + +namespace ClangStaticAnalyzer { +namespace Internal { + +class ClangStaticAnalyzerLogFileReader +{ +public: + ClangStaticAnalyzerLogFileReader(const QString &filePath); + + QXmlStreamReader::Error read(); + + // Output + QString clangVersion() const; + QStringList files() const; + QList<Diagnostic> diagnostics() const; + +private: + void readPlist(); + void readTopLevelDict(); + void readDiagnosticsArray(); + void readDiagnosticsDict(); + QList<ExplainingStep> readPathArray(); + ExplainingStep readPathDict(); + Analyzer::DiagnosticLocation readLocationDict(bool elementIsRead = false); + QList<Analyzer::DiagnosticLocation> readRangesArray(); + + QString readString(); + QStringList readStringArray(); + int readInteger(bool *convertedSuccessfully); + +private: + QString m_filePath; + QXmlStreamReader m_xml; + + QString m_clangVersion; + QStringList m_referencedFiles; + QList<Diagnostic> m_diagnostics; +}; + +QList<Diagnostic> LogFileReader::read(const QString &filePath, QString *errorMessage) +{ + const QList<Diagnostic> emptyList; + + // Check file path + QFileInfo fi(filePath); + if (!fi.exists() || !fi.isReadable()) { + if (errorMessage) { + *errorMessage = QObject::tr("File \"%1\" does not exist or is not readable.") + .arg(filePath); + } + return emptyList; + } + + // Read + ClangStaticAnalyzerLogFileReader reader(filePath); + const QXmlStreamReader::Error error = reader.read(); + + // Return diagnostics + switch (error) { + case QXmlStreamReader::NoError: + return reader.diagnostics(); + + // Handle errors + case QXmlStreamReader::UnexpectedElementError: + if (errorMessage) { + *errorMessage = QObject::tr("Could not read file \"%1\": UnexpectedElementError.") + .arg(filePath); + } // fall-through + case QXmlStreamReader::CustomError: + if (errorMessage) { + *errorMessage = QObject::tr("Could not read file \"%1\": CustomError.") + .arg(filePath); + } // fall-through + case QXmlStreamReader::NotWellFormedError: + if (errorMessage) { + *errorMessage = QObject::tr("Could not read file \"%1\": NotWellFormedError.") + .arg(filePath); + } // fall-through + case QXmlStreamReader::PrematureEndOfDocumentError: + if (errorMessage) { + *errorMessage = QObject::tr("Could not read file \"%1\": PrematureEndOfDocumentError.") + .arg(filePath); + } // fall-through + default: + return emptyList; + } +} + +ClangStaticAnalyzerLogFileReader::ClangStaticAnalyzerLogFileReader(const QString &filePath) + : m_filePath(filePath) +{ +} + +QXmlStreamReader::Error ClangStaticAnalyzerLogFileReader::read() +{ + QTC_ASSERT(!m_filePath.isEmpty(), return QXmlStreamReader::CustomError); + QFile file(m_filePath); + QTC_ASSERT(file.open(QIODevice::ReadOnly | QIODevice::Text), + return QXmlStreamReader::CustomError); + + m_xml.setDevice(&file); + readPlist(); + + // If file is empty, m_xml.error() == QXmlStreamReader::PrematureEndOfDocumentError + return m_xml.error(); +} + +QString ClangStaticAnalyzerLogFileReader::clangVersion() const +{ + return m_clangVersion; +} + +QStringList ClangStaticAnalyzerLogFileReader::files() const +{ + return m_referencedFiles; +} + +QList<Diagnostic> ClangStaticAnalyzerLogFileReader::diagnostics() const +{ + return m_diagnostics; +} + +void ClangStaticAnalyzerLogFileReader::readPlist() +{ + if (m_xml.readNextStartElement()) { + if (m_xml.name() == QLatin1String("plist")) { + if (m_xml.attributes().value(QLatin1String("version")) == QLatin1String("1.0")) + readTopLevelDict(); + } else { + m_xml.raiseError(QObject::tr("File is not a plist version 1.0 file.")); + } + } +} + +void ClangStaticAnalyzerLogFileReader::readTopLevelDict() +{ + QTC_ASSERT(m_xml.isStartElement() && m_xml.name() == QLatin1String("plist"), return); + QTC_ASSERT(m_xml.readNextStartElement() && m_xml.name() == QLatin1String("dict"), return); + + while (m_xml.readNextStartElement()) { + if (m_xml.name() == QLatin1String("key")) { + const QString key = m_xml.readElementText(); + if (key == QLatin1String("clang_version")) + m_clangVersion = readString(); + else if (key == QLatin1String("files")) + m_referencedFiles = readStringArray(); + else if (key == QLatin1String("diagnostics")) + readDiagnosticsArray(); + } else { + m_xml.skipCurrentElement(); + } + } +} + +void ClangStaticAnalyzerLogFileReader::readDiagnosticsArray() +{ + if (m_xml.readNextStartElement() && m_xml.name() == QLatin1String("array")) { + while (m_xml.readNextStartElement() && m_xml.name() == QLatin1String("dict")) + readDiagnosticsDict(); + } +} + +void ClangStaticAnalyzerLogFileReader::readDiagnosticsDict() +{ + QTC_ASSERT(m_xml.isStartElement() && m_xml.name() == QLatin1String("dict"), return); + + Diagnostic diagnostic; + + while (m_xml.readNextStartElement()) { + if (m_xml.name() == QLatin1String("key")) { + const QString key = m_xml.readElementText(); + if (key == QLatin1String("path")) + diagnostic.explainingSteps = readPathArray(); + else if (key == QLatin1String("description")) + diagnostic.description = readString(); + else if (key == QLatin1String("category")) + diagnostic.category = readString(); + else if (key == QLatin1String("type")) + diagnostic.type = readString(); + else if (key == QLatin1String("issue_context_kind")) + diagnostic.issueContextKind = readString(); + else if (key == QLatin1String("issue_context")) + diagnostic.issueContext = readString(); + else if (key == QLatin1String("location")) + diagnostic.location = readLocationDict(); + } else { + m_xml.skipCurrentElement(); + } + } + + m_diagnostics << diagnostic; +} + +QList<ExplainingStep> ClangStaticAnalyzerLogFileReader::readPathArray() +{ + QList<ExplainingStep> result; + + if (m_xml.readNextStartElement() && m_xml.name() == QLatin1String("array")) { + while (m_xml.readNextStartElement() && m_xml.name() == QLatin1String("dict")) { + const ExplainingStep step = readPathDict(); + if (step.isValid()) + result << step; + } + } + + return result; +} + +ExplainingStep ClangStaticAnalyzerLogFileReader::readPathDict() +{ + ExplainingStep explainingStep; + + QTC_ASSERT(m_xml.isStartElement() && m_xml.name() == QLatin1String("dict"), + return explainingStep); + + // We are interested only in dict entries an kind=event type + if (m_xml.readNextStartElement()) { + if (m_xml.name() == QLatin1String("key")) { + const QString key = m_xml.readElementText(); + QTC_ASSERT(key == QLatin1String("kind"), return explainingStep); + const QString kind = readString(); + if (kind != QLatin1String("event")) { + m_xml.skipCurrentElement(); + return explainingStep; + } + } + } + + bool depthOk = false; + + while (m_xml.readNextStartElement()) { + if (m_xml.name() == QLatin1String("key")) { + const QString key = m_xml.readElementText(); + if (key == QLatin1String("location")) + explainingStep.location = readLocationDict(); + else if (key == QLatin1String("ranges")) + explainingStep.ranges = readRangesArray(); + else if (key == QLatin1String("depth")) + explainingStep.depth = readInteger(&depthOk); + else if (key == QLatin1String("message")) + explainingStep.message = readString(); + else if (key == QLatin1String("extended_message")) + explainingStep.extendedMessage = readString(); + } else { + m_xml.skipCurrentElement(); + } + } + + QTC_CHECK(depthOk); + return explainingStep; +} + +Analyzer::DiagnosticLocation ClangStaticAnalyzerLogFileReader::readLocationDict(bool elementIsRead) +{ + Analyzer::DiagnosticLocation location; + if (elementIsRead) { + QTC_ASSERT(m_xml.isStartElement() && m_xml.name() == QLatin1String("dict"), + return location); + } else { + QTC_ASSERT(m_xml.readNextStartElement() && m_xml.name() == QLatin1String("dict"), + return location); + } + + int line = 0; + int column = 0; + int fileIndex = 0; + bool lineOk = false, columnOk = false, fileIndexOk = false; + + // Collect values + while (m_xml.readNextStartElement()) { + if (m_xml.name() == QLatin1String("key")) { + const QString keyName = m_xml.readElementText(); + if (keyName == QLatin1String("line")) + line = readInteger(&lineOk); + else if (keyName == QLatin1String("col")) + column = readInteger(&columnOk); + else if (keyName == QLatin1String("file")) + fileIndex = readInteger(&fileIndexOk); + } else { + m_xml.skipCurrentElement(); + } + } + + if (lineOk && columnOk && fileIndexOk) { + QTC_ASSERT(fileIndex < m_referencedFiles.size(), return location); + location = Analyzer::DiagnosticLocation(m_referencedFiles.at(fileIndex), line, column); + } + return location; +} + +QList<Analyzer::DiagnosticLocation> ClangStaticAnalyzerLogFileReader::readRangesArray() +{ + QList<Analyzer::DiagnosticLocation> result; + + // It's an array of arrays... + QTC_ASSERT(m_xml.readNextStartElement() && m_xml.name() == QLatin1String("array"), + return result); + QTC_ASSERT(m_xml.readNextStartElement() && m_xml.name() == QLatin1String("array"), + return result); + + while (m_xml.readNextStartElement() && m_xml.name() == QLatin1String("dict")) + result << readLocationDict(true); + + m_xml.skipCurrentElement(); // Laeve outer array + return result; +} + +QString ClangStaticAnalyzerLogFileReader::readString() +{ + if (m_xml.readNextStartElement() && m_xml.name() == QLatin1String("string")) + return m_xml.readElementText(); + + m_xml.raiseError(QObject::tr("Expected a string element.")); + return QString(); +} + +QStringList ClangStaticAnalyzerLogFileReader::readStringArray() +{ + if (m_xml.readNextStartElement() && m_xml.name() == QLatin1String("array")) { + QStringList result; + while (m_xml.readNextStartElement() && m_xml.name() == QLatin1String("string")) { + const QString string = m_xml.readElementText(); + if (!string.isEmpty()) + result << string; + } + return result; + } + + m_xml.raiseError(QObject::tr("Expected an array element.")); + return QStringList(); +} + +int ClangStaticAnalyzerLogFileReader::readInteger(bool *convertedSuccessfully) +{ + if (m_xml.readNextStartElement() && m_xml.name() == QLatin1String("integer")) { + const QString contents = m_xml.readElementText(); + return contents.toInt(convertedSuccessfully); + } + + m_xml.raiseError(QObject::tr("Expected an integer element.")); + if (convertedSuccessfully) + *convertedSuccessfully = false; + return -1; +} + +} // namespace Internal +} // namespace ClangStaticAnalyzer diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerlogfilereader.h b/src/plugins/clangstaticanalyzer/clangstaticanalyzerlogfilereader.h new file mode 100644 index 0000000000000000000000000000000000000000..8b05ada7aa34a0c0e89516811bd24e150a05ebd1 --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerlogfilereader.h @@ -0,0 +1,45 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#ifndef CLANGSTATICANALYZERLOGFILEREADER_H +#define CLANGSTATICANALYZERLOGFILEREADER_H + +#include "clangstaticanalyzerdiagnostic.h" + +#include <QList> + +namespace ClangStaticAnalyzer { +namespace Internal { + +class LogFileReader +{ +public: + static QList<Diagnostic> read(const QString &filePath, QString *errorMessage); +}; + +} // namespace Internal +} // namespace ClangStaticAnalyzer + +#endif // CLANGSTATICANALYZERLOGFILEREADER_H diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerplugin.cpp b/src/plugins/clangstaticanalyzer/clangstaticanalyzerplugin.cpp new file mode 100644 index 0000000000000000000000000000000000000000..eb9683e3d42c1ca63746ddbaa736e2c403ef067a --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerplugin.cpp @@ -0,0 +1,184 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "clangstaticanalyzerplugin.h" + +#include "clangstaticanalyzerconfigwidget.h" +#include "clangstaticanalyzerconstants.h" +#include "clangstaticanalyzerprojectsettingswidget.h" +#include "clangstaticanalyzerruncontrolfactory.h" +#include "clangstaticanalyzertool.h" + +#ifdef WITH_TESTS +#include "clangstaticanalyzerunittests.h" +#endif + +#include <analyzerbase/analyzermanager.h> +#include <coreplugin/icore.h> +#include <coreplugin/icontext.h> +#include <coreplugin/actionmanager/actionmanager.h> +#include <coreplugin/actionmanager/command.h> +#include <coreplugin/actionmanager/actioncontainer.h> +#include <coreplugin/coreconstants.h> +#include <coreplugin/dialogs/ioptionspage.h> +#include <projectexplorer/projectpanelfactory.h> + +#include <QAction> +#include <QDebug> +#include <QMainWindow> +#include <QMessageBox> +#include <QMenu> + +#include <QtPlugin> + +using namespace Analyzer; + +namespace ClangStaticAnalyzer { +namespace Internal { + +class ClangStaticAnalyzerOptionsPage : public Core::IOptionsPage +{ +public: + explicit ClangStaticAnalyzerOptionsPage() + { + setId("Analyzer.ClangStaticAnalyzer.Settings"); // TODO: Get it from "clangstaticanalyzersettings.h" + setDisplayName(QCoreApplication::translate( + "ClangStaticAnalyzer::Internal::ClangStaticAnalyzerOptionsPage", + "Clang Static Analyzer")); + setCategory("T.Analyzer"); + setDisplayCategory(QCoreApplication::translate("Analyzer", "Analyzer")); + setCategoryIcon(QLatin1String(":/images/analyzer_category.png")); + } + + QWidget *widget() + { + if (!m_widget) + m_widget = new ClangStaticAnalyzerConfigWidget(ClangStaticAnalyzerSettings::instance()); + return m_widget; + } + + void apply() + { + ClangStaticAnalyzerSettings::instance()->writeSettings(); + } + + void finish() + { + delete m_widget; + } + +private: + QPointer<QWidget> m_widget; +}; + +ClangStaticAnalyzerPlugin::ClangStaticAnalyzerPlugin() +{ + // Create your members +} + +ClangStaticAnalyzerPlugin::~ClangStaticAnalyzerPlugin() +{ + // Unregister objects from the plugin manager's object pool + // Delete members +} + +bool ClangStaticAnalyzerPlugin::initialize(const QStringList &arguments, QString *errorString) +{ + // Register objects in the plugin manager's object pool + // Load settings + // Add actions to menus + // Connect to other plugins' signals + // In the initialize method, a plugin can be sure that the plugins it + // depends on have initialized their members. + + auto panelFactory = new ProjectExplorer::ProjectPanelFactory(); + panelFactory->setPriority(100); + panelFactory->setDisplayName(tr("Clang Static Analyzer Settings")); + panelFactory->setSimpleCreateWidgetFunction<ProjectSettingsWidget>(QIcon()); + ProjectExplorer::ProjectPanelFactory::registerFactory(panelFactory); + return initializeEnterpriseFeatures(arguments, errorString); +} + +bool ClangStaticAnalyzerPlugin::initializeEnterpriseFeatures(const QStringList &arguments, + QString *errorString) +{ + Q_UNUSED(arguments); + Q_UNUSED(errorString); + + auto tool = m_analyzerTool = new ClangStaticAnalyzerTool(this); + addAutoReleasedObject(new ClangStaticAnalyzerRunControlFactory(m_analyzerTool)); + addAutoReleasedObject(new ClangStaticAnalyzerOptionsPage); + + auto widgetCreator = [tool] { return tool->createWidgets(); }; + auto runControlCreator = [tool](const AnalyzerStartParameters &, + ProjectExplorer::RunConfiguration *runConfiguration, Core::Id runMode) { + return tool->createRunControl(runConfiguration, runMode); + }; + + const QString toolTip = tr("Clang Static Analyzer uses the analyzer from the clang project " + "to find bugs."); + + auto action = new AnalyzerAction(this); + action->setRunMode(Constants::CLANGSTATICANALYZER_RUN_MODE); + action->setToolId(ClangStaticAnalyzerToolId); + action->setActionId("ClangStaticAnalyzer"); + action->setWidgetCreator(widgetCreator); + action->setRunControlCreator(runControlCreator); + action->setCustomToolStarter([tool] { tool->startTool(); }); + action->setText(tr("Clang Static Analyzer")); + action->setToolTip(toolTip); + action->setMenuGroup(Analyzer::Constants::G_ANALYZER_TOOLS); + action->setEnabled(false); + AnalyzerManager::addAction(action); + + return true; +} + +void ClangStaticAnalyzerPlugin::extensionsInitialized() +{ + // Retrieve objects from the plugin manager's object pool + // In the extensionsInitialized method, a plugin can be sure that all + // plugins that depend on it are completely initialized. +} + +ExtensionSystem::IPlugin::ShutdownFlag ClangStaticAnalyzerPlugin::aboutToShutdown() +{ + // Save settings + // Disconnect from signals that are not needed during shutdown + // Hide UI (if you add UI that is not in the main window directly) + return SynchronousShutdown; +} + +QList<QObject *> ClangStaticAnalyzerPlugin::createTestObjects() const +{ + QList<QObject *> tests; +#ifdef WITH_TESTS + tests << new ClangStaticAnalyzerUnitTests(m_analyzerTool); +#endif + return tests; +} + +} // namespace Internal +} // namespace ClangStaticAnalyzerPlugin diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerplugin.h b/src/plugins/clangstaticanalyzer/clangstaticanalyzerplugin.h new file mode 100644 index 0000000000000000000000000000000000000000..b663a16487758d41e07dceb0634de367330d3c26 --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerplugin.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#ifndef CLANGSTATICANALYZERPLUGIN_H +#define CLANGSTATICANALYZERPLUGIN_H + +#include <extensionsystem/iplugin.h> + +namespace ClangStaticAnalyzer { +namespace Internal { + +class ClangStaticAnalyzerTool; +class ClangStaticAnalyzerSettings; + +class ClangStaticAnalyzerPlugin : public ExtensionSystem::IPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "ClangStaticAnalyzer.json") + +public: + ClangStaticAnalyzerPlugin(); + ~ClangStaticAnalyzerPlugin(); + + bool initialize(const QStringList &arguments, QString *errorString); + bool initializeEnterpriseFeatures(const QStringList &arguments, QString *errorString); + void extensionsInitialized(); + ShutdownFlag aboutToShutdown(); + +private: + QList<QObject *> createTestObjects() const; + + ClangStaticAnalyzerTool *m_analyzerTool; +}; + +} // namespace Internal +} // namespace ClangStaticAnalyzerPlugin + +#endif // CLANGSTATICANALYZERPLUGIN_H + diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerprojectsettings.cpp b/src/plugins/clangstaticanalyzer/clangstaticanalyzerprojectsettings.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4d37154c8585e72450bf1371ebd55382e7dc2d46 --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerprojectsettings.cpp @@ -0,0 +1,145 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "clangstaticanalyzerprojectsettings.h" + +#include "clangstaticanalyzerdiagnostic.h" + +#include <utils/qtcassert.h> + +namespace ClangStaticAnalyzer { +namespace Internal { + +static QString suppressedDiagnosticsKey() +{ + return QLatin1String("ClangStaticAnalyzer.SuppressedDiagnostics"); +} + +static QString suppressedDiagnosticFilePathKey() +{ + return QLatin1String("ClangStaticAnalyzer.SuppressedDiagnosticFilePath"); +} + +static QString suppressedDiagnosticMessageKey() +{ + return QLatin1String("ClangStaticAnalyzer.SuppressedDiagnosticMessage"); +} + +static QString suppressedDiagnosticContextKindKey() +{ + return QLatin1String("ClangStaticAnalyzer.SuppressedDiagnosticContextKind"); +} + +static QString suppressedDiagnosticContextKey() +{ + return QLatin1String("ClangStaticAnalyzer.SuppressedDiagnosticContext"); +} + +static QString suppressedDiagnosticUniquifierKey() +{ + return QLatin1String("ClangStaticAnalyzer.SuppressedDiagnosticUniquifier"); +} + +ProjectSettings::ProjectSettings(ProjectExplorer::Project *project) : m_project(project) +{ + load(); + connect(project, &ProjectExplorer::Project::aboutToSaveSettings, this, + &ProjectSettings::store); +} + +void ProjectSettings::addSuppressedDiagnostic(const SuppressedDiagnostic &diag) +{ + QTC_ASSERT(!m_suppressedDiagnostics.contains(diag), return); + m_suppressedDiagnostics << diag; + emit suppressedDiagnosticsChanged(); +} + +void ProjectSettings::removeSuppressedDiagnostic(const SuppressedDiagnostic &diag) +{ + const bool wasPresent = m_suppressedDiagnostics.removeOne(diag); + QTC_ASSERT(wasPresent, return); + emit suppressedDiagnosticsChanged(); +} + +void ProjectSettings::removeAllSuppressedDiagnostics() +{ + m_suppressedDiagnostics.clear(); + emit suppressedDiagnosticsChanged(); +} + +void ProjectSettings::load() +{ + const QVariantList list = m_project->namedSettings(suppressedDiagnosticsKey()).toList(); + foreach (const QVariant &v, list) { + const QVariantMap diag = v.toMap(); + const QString fp = diag.value(suppressedDiagnosticFilePathKey()).toString(); + if (fp.isEmpty()) + continue; + const QString message = diag.value(suppressedDiagnosticMessageKey()).toString(); + if (message.isEmpty()) + continue; + Utils::FileName fullPath = Utils::FileName::fromString(fp); + if (fullPath.toFileInfo().isRelative()) { + fullPath = m_project->projectDirectory(); + fullPath.appendPath(fp); + } + if (!fullPath.exists()) + continue; + const QString contextKind = diag.value(suppressedDiagnosticContextKindKey()).toString(); + const QString context = diag.value(suppressedDiagnosticContextKey()).toString(); + const int uniquifier = diag.value(suppressedDiagnosticUniquifierKey()).toInt(); + m_suppressedDiagnostics << SuppressedDiagnostic(Utils::FileName::fromString(fp), message, + contextKind, context, uniquifier); + } + emit suppressedDiagnosticsChanged(); +} + +void ProjectSettings::store() +{ + QVariantList list; + foreach (const SuppressedDiagnostic &diag, m_suppressedDiagnostics) { + QVariantMap diagMap; + diagMap.insert(suppressedDiagnosticFilePathKey(), diag.filePath.toString()); + diagMap.insert(suppressedDiagnosticMessageKey(), diag.description); + diagMap.insert(suppressedDiagnosticContextKindKey(), diag.contextKind); + diagMap.insert(suppressedDiagnosticContextKey(), diag.context); + diagMap.insert(suppressedDiagnosticUniquifierKey(), diag.uniquifier); + list << diagMap; + } + m_project->setNamedSettings(suppressedDiagnosticsKey(), list); +} + + +SuppressedDiagnostic::SuppressedDiagnostic(const Diagnostic &diag) + : filePath(Utils::FileName::fromString(diag.location.filePath)) + , description(diag.description) + , contextKind(diag.issueContextKind) + , context(diag.issueContext) + , uniquifier(diag.explainingSteps.count()) +{ +} + +} // namespace Internal +} // namespace ClangStaticAnalyzer diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerprojectsettings.h b/src/plugins/clangstaticanalyzer/clangstaticanalyzerprojectsettings.h new file mode 100644 index 0000000000000000000000000000000000000000..f30298b69f67859ffcbdfab898e398543551607b --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerprojectsettings.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#ifndef CLANGSTATICANALYZERPROJECTSETTINGS_H +#define CLANGSTATICANALYZERPROJECTSETTINGS_H + +#include <projectexplorer/project.h> +#include <utils/fileutils.h> + +#include <QList> +#include <QObject> + +namespace ClangStaticAnalyzer { +namespace Internal { +class Diagnostic; + +class SuppressedDiagnostic +{ +public: + SuppressedDiagnostic(const Utils::FileName &filePath, const QString &description, + const QString &contextKind, const QString &context, int uniquifier) + : filePath(filePath) + , description(description) + , contextKind(contextKind) + , context(context) + , uniquifier(uniquifier) + { + } + + SuppressedDiagnostic(const Diagnostic &diag); + + Utils::FileName filePath; // Relative for files in project, absolute otherwise. + QString description; + QString contextKind; + QString context; + int uniquifier; +}; + +inline bool operator==(const SuppressedDiagnostic &d1, const SuppressedDiagnostic &d2) +{ + return d1.filePath == d2.filePath && d1.description == d2.description + && d1.contextKind == d2.contextKind && d1.context == d2.context + && d1.uniquifier == d2.uniquifier; +} + +typedef QList<SuppressedDiagnostic> SuppressedDiagnosticsList; + +class ProjectSettings : public QObject +{ + Q_OBJECT +public: + ProjectSettings(ProjectExplorer::Project *project); + + SuppressedDiagnosticsList suppressedDiagnostics() const { return m_suppressedDiagnostics; } + void addSuppressedDiagnostic(const SuppressedDiagnostic &diag); + void removeSuppressedDiagnostic(const SuppressedDiagnostic &diag); + void removeAllSuppressedDiagnostics(); + +signals: + void suppressedDiagnosticsChanged(); + +private: + void load(); + void store(); + + ProjectExplorer::Project * const m_project; + SuppressedDiagnosticsList m_suppressedDiagnostics; +}; + +} // namespace Internal +} // namespace ClangStaticAnalyzer + +#endif // Include guard. diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerprojectsettingsmanager.cpp b/src/plugins/clangstaticanalyzer/clangstaticanalyzerprojectsettingsmanager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c0b456345b6b1aad34cbae498be3ad7112f9ff25 --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerprojectsettingsmanager.cpp @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "clangstaticanalyzerprojectsettingsmanager.h" + +#include "clangstaticanalyzerprojectsettings.h" + +#include <projectexplorer/session.h> + +namespace ClangStaticAnalyzer { +namespace Internal { + +ProjectSettingsManager::ProjectSettingsManager() +{ + QObject::connect(ProjectExplorer::SessionManager::instance(), + &ProjectExplorer::SessionManager::aboutToRemoveProject, + &ProjectSettingsManager::handleProjectToBeRemoved); +} + +ProjectSettings *ProjectSettingsManager::getSettings(ProjectExplorer::Project *project) +{ + auto &settings = m_settings[project]; + if (!settings) + settings.reset(new ProjectSettings(project)); + return settings.data(); +} + +void ProjectSettingsManager::handleProjectToBeRemoved(ProjectExplorer::Project *project) +{ + m_settings.remove(project); +} + +ProjectSettingsManager::SettingsMap ProjectSettingsManager::m_settings; + +} // namespace Internal +} // namespace ClangStaticAnalyzer diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerprojectsettingsmanager.h b/src/plugins/clangstaticanalyzer/clangstaticanalyzerprojectsettingsmanager.h new file mode 100644 index 0000000000000000000000000000000000000000..a588a7824d785556f673b0cbe2a1c3624614fe65 --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerprojectsettingsmanager.h @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#ifndef CLANGSTATICANALYZERPROJECTSETTINGSMANAGER_H +#define CLANGSTATICANALYZERPROJECTSETTINGSMANAGER_H + +namespace ProjectExplorer { class Project; } + +#include <QHash> +#include <QSharedPointer> + +namespace ClangStaticAnalyzer { +namespace Internal { +class ProjectSettings; + +class ProjectSettingsManager +{ +public: + ProjectSettingsManager(); + + static ProjectSettings *getSettings(ProjectExplorer::Project *project); + +private: + static void handleProjectToBeRemoved(ProjectExplorer::Project *project); + + typedef QHash<ProjectExplorer::Project *, QSharedPointer<ProjectSettings>> SettingsMap; + static SettingsMap m_settings; +}; + +} // namespace Internal +} // namespace ClangStaticAnalyzer + +#endif // Include guard. diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerprojectsettingswidget.cpp b/src/plugins/clangstaticanalyzer/clangstaticanalyzerprojectsettingswidget.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3ec14f7d2913ff8a35cc675ca0db6f14abf35d74 --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerprojectsettingswidget.cpp @@ -0,0 +1,168 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "clangstaticanalyzerprojectsettingswidget.h" +#include "ui_clangstaticanalyzerprojectsettingswidget.h" + +#include "clangstaticanalyzerprojectsettings.h" +#include "clangstaticanalyzerprojectsettingsmanager.h" + +#include <utils/qtcassert.h> + +#include <QAbstractTableModel> + +namespace ClangStaticAnalyzer { +namespace Internal { + +class SuppressedDiagnosticsModel : public QAbstractTableModel +{ + Q_OBJECT +public: + SuppressedDiagnosticsModel(QObject *parent = 0) : QAbstractTableModel(parent) { } + + void setDiagnostics(const SuppressedDiagnosticsList &diagnostics); + SuppressedDiagnostic diagnosticAt(int i) const; + +private: + enum Columns { ColumnFile, ColumnContext, ColumnDescription, ColumnLast = ColumnDescription }; + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex & = QModelIndex()) const { return ColumnLast + 1; } + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + + SuppressedDiagnosticsList m_diagnostics; +}; + +ProjectSettingsWidget::ProjectSettingsWidget(ProjectExplorer::Project *project, QWidget *parent) : + QWidget(parent), + m_ui(new Ui::ProjectSettingsWidget) + , m_projectSettings(ProjectSettingsManager::getSettings(project)) +{ + m_ui->setupUi(this); + auto * const model = new SuppressedDiagnosticsModel(this); + model->setDiagnostics(m_projectSettings->suppressedDiagnostics()); + connect(m_projectSettings, &ProjectSettings::suppressedDiagnosticsChanged, + [model, this] { + model->setDiagnostics(m_projectSettings->suppressedDiagnostics()); + updateButtonStates(); + }); + m_ui->diagnosticsView->setModel(model); + updateButtonStates(); + connect(m_ui->diagnosticsView->selectionModel(), &QItemSelectionModel::selectionChanged, + [this](const QItemSelection &, const QItemSelection &) { + updateButtonStateRemoveSelected(); + }); + connect(m_ui->removeSelectedButton, &QAbstractButton::clicked, + [this](bool) { removeSelected(); }); + connect(m_ui->removeAllButton, &QAbstractButton::clicked, + [this](bool) { m_projectSettings->removeAllSuppressedDiagnostics();}); +} + +ProjectSettingsWidget::~ProjectSettingsWidget() +{ + delete m_ui; +} + +void ProjectSettingsWidget::updateButtonStates() +{ + updateButtonStateRemoveSelected(); + updateButtonStateRemoveAll(); +} + +void ProjectSettingsWidget::updateButtonStateRemoveSelected() +{ + const auto selectedRows = m_ui->diagnosticsView->selectionModel()->selectedRows(); + QTC_ASSERT(selectedRows.count() <= 1, return); + m_ui->removeSelectedButton->setEnabled(!selectedRows.isEmpty()); +} + +void ProjectSettingsWidget::updateButtonStateRemoveAll() +{ + m_ui->removeAllButton->setEnabled(m_ui->diagnosticsView->model()->rowCount() > 0); +} + +void ProjectSettingsWidget::removeSelected() +{ + const auto selectedRows = m_ui->diagnosticsView->selectionModel()->selectedRows(); + QTC_ASSERT(selectedRows.count() == 1, return); + const auto * const model + = static_cast<SuppressedDiagnosticsModel *>(m_ui->diagnosticsView->model()); + m_projectSettings->removeSuppressedDiagnostic(model->diagnosticAt(selectedRows.first().row())); +} + + +void SuppressedDiagnosticsModel::setDiagnostics(const SuppressedDiagnosticsList &diagnostics) +{ + beginResetModel(); + m_diagnostics = diagnostics; + endResetModel(); +} + +SuppressedDiagnostic SuppressedDiagnosticsModel::diagnosticAt(int i) const +{ + return m_diagnostics.at(i); +} + +int SuppressedDiagnosticsModel::rowCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : m_diagnostics.count(); +} + +QVariant SuppressedDiagnosticsModel::headerData(int section, Qt::Orientation orientation, + int role) const +{ + if (role == Qt::DisplayRole && orientation == Qt::Horizontal) { + if (section == ColumnFile) + return tr("File"); + if (section == ColumnContext) + return tr("Context"); + if (section == ColumnDescription) + return tr("Diagnostic"); + } + return QVariant(); +} + +QVariant SuppressedDiagnosticsModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || role != Qt::DisplayRole || index.row() >= rowCount()) + return QVariant(); + const SuppressedDiagnostic &diag = m_diagnostics.at(index.row()); + if (index.column() == ColumnFile) + return diag.filePath.toUserOutput(); + if (index.column() == ColumnContext) { + if (diag.contextKind == QLatin1String("function") && !diag.context.isEmpty()) + return tr("Function \"%1\"").arg(diag.context); + return QString(); + } + if (index.column() == ColumnDescription) + return diag.description; + return QVariant(); +} + +} // namespace Internal +} // namespace ClangStaticAnalyzer + +#include "clangstaticanalyzerprojectsettingswidget.moc" diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerprojectsettingswidget.h b/src/plugins/clangstaticanalyzer/clangstaticanalyzerprojectsettingswidget.h new file mode 100644 index 0000000000000000000000000000000000000000..b68003b25c52b5b799179835fe2c2a7bca0c6dae --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerprojectsettingswidget.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#ifndef CLANGSTATICANALYZERPROJECTSETTINGSWIDGET_H +#define CLANGSTATICANALYZERPROJECTSETTINGSWIDGET_H + +#include <QWidget> + +namespace ProjectExplorer { class Project; } + +namespace ClangStaticAnalyzer { +namespace Internal { +class ProjectSettings; + +namespace Ui { class ProjectSettingsWidget; } + +class ProjectSettingsWidget : public QWidget +{ + Q_OBJECT + +public: + explicit ProjectSettingsWidget(ProjectExplorer::Project *project, QWidget *parent = 0); + ~ProjectSettingsWidget(); + +private: + void updateButtonStates(); + void updateButtonStateRemoveSelected(); + void updateButtonStateRemoveAll(); + void removeSelected(); + + Ui::ProjectSettingsWidget * const m_ui; + ProjectSettings * const m_projectSettings; +}; + +} // namespace Internal +} // namespace ClangStaticAnalyzer + +#endif // Include guard. diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerprojectsettingswidget.ui b/src/plugins/clangstaticanalyzer/clangstaticanalyzerprojectsettingswidget.ui new file mode 100644 index 0000000000000000000000000000000000000000..c131bbe0c22f9a53538851204da347dba87e47d4 --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerprojectsettingswidget.ui @@ -0,0 +1,87 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ClangStaticAnalyzer::Internal::ProjectSettingsWidget</class> + <widget class="QWidget" name="ClangStaticAnalyzer::Internal::ProjectSettingsWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>300</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Suppressed Diagnostics:</string> + </property> + </widget> + </item> + <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> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QTreeView" name="diagnosticsView"> + <property name="selectionMode"> + <enum>QAbstractItemView::SingleSelection</enum> + </property> + </widget> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QPushButton" name="removeSelectedButton"> + <property name="text"> + <string>Remove Selected</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="removeAllButton"> + <property name="text"> + <string>Remove All</string> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerruncontrol.cpp b/src/plugins/clangstaticanalyzer/clangstaticanalyzerruncontrol.cpp new file mode 100644 index 0000000000000000000000000000000000000000..35652728c47f3c35e30a41124cbc5cef2977a66b --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerruncontrol.cpp @@ -0,0 +1,511 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "clangstaticanalyzerruncontrol.h" + +#include "clangstaticanalyzerlogfilereader.h" +#include "clangstaticanalyzerrunner.h" +#include "clangstaticanalyzersettings.h" +#include "clangstaticanalyzerutils.h" + +#include <analyzerbase/analyzermanager.h> +#include <analyzerbase/analyzerutils.h> + +#include <clangcodemodel/clangutils.h> + +#include <coreplugin/progressmanager/futureprogress.h> +#include <coreplugin/progressmanager/progressmanager.h> + +#include <cpptools/compileroptionsbuilder.h> +#include <cpptools/cppmodelmanager.h> +#include <cpptools/cppprojectfile.h> +#include <cpptools/projectinfo.h> + +#include <projectexplorer/abi.h> +#include <projectexplorer/buildconfiguration.h> +#include <projectexplorer/kitinformation.h> +#include <projectexplorer/project.h> +#include <projectexplorer/runconfiguration.h> +#include <projectexplorer/target.h> +#include <projectexplorer/taskhub.h> +#include <projectexplorer/toolchain.h> + +#include <utils/algorithm.h> + +#include <QLoggingCategory> +#include <QTemporaryDir> + +using namespace CppTools; +using namespace ProjectExplorer; + +static Q_LOGGING_CATEGORY(LOG, "qtc.clangstaticanalyzer.runcontrol") + +namespace ClangStaticAnalyzer { +namespace Internal { + +ClangStaticAnalyzerRunControl::ClangStaticAnalyzerRunControl( + RunConfiguration *runConfiguration, + Core::Id runMode, + const ProjectInfo &projectInfo) + : AnalyzerRunControl(runConfiguration, runMode) + , m_projectInfo(projectInfo) + , m_wordWidth(runConfiguration->abi().wordWidth()) + , m_initialFilesToProcessSize(0) + , m_filesAnalyzed(0) + , m_filesNotAnalyzed(0) +{ + Target *target = runConfiguration->target(); + BuildConfiguration *buildConfiguration = target->activeBuildConfiguration(); + QTC_ASSERT(buildConfiguration, return); + m_environment = buildConfiguration->environment(); +} + +static void prependWordWidthArgumentIfNotIncluded(QStringList *arguments, unsigned char wordWidth) +{ + QTC_ASSERT(arguments, return); + + const QString m64Argument = QLatin1String("-m64"); + const QString m32Argument = QLatin1String("-m32"); + + const QString argument = wordWidth == 64 ? m64Argument : m32Argument; + if (!arguments->contains(argument)) + arguments->prepend(argument); + + QTC_CHECK(!arguments->contains(m32Argument) || !arguments->contains(m64Argument)); +} + +// Removes (1) filePath (2) -o <somePath>. +// Adds -m64/-m32 argument if not already included. +static QStringList tweakedArguments(const QString &filePath, + const QStringList &arguments, + unsigned char wordWidth) +{ + QStringList newArguments; + + bool skip = false; + foreach (const QString &argument, arguments) { + if (skip) { + skip = false; + continue; + } else if (argument == QLatin1String("-o")) { + skip = true; + continue; + } else if (QDir::fromNativeSeparators(argument) == filePath) { + continue; // TODO: Let it in? + } + + newArguments << argument; + } + QTC_CHECK(skip == false); + + prependWordWidthArgumentIfNotIncluded(&newArguments, wordWidth); + + return newArguments; +} + +static QString createLanguageOptionMsvc(ProjectFile::Kind fileKind) +{ + switch (fileKind) { + case ProjectFile::CHeader: + case ProjectFile::CSource: + return QLatin1String("/TC"); + break; + case ProjectFile::CXXHeader: + case ProjectFile::CXXSource: + return QLatin1String("/TP"); + break; + default: + break; + } + return QString(); +} + +class ClangStaticAnalyzerOptionsBuilder : public CompilerOptionsBuilder +{ +public: + static QStringList build(const CppTools::ProjectPart &projectPart, + CppTools::ProjectFile::Kind fileKind, + unsigned char wordWidth) + { + ClangStaticAnalyzerOptionsBuilder optionsBuilder(projectPart); + optionsBuilder.addLanguageOption(fileKind); + optionsBuilder.addOptionsForLanguage(false); + + // In gcc headers, lots of built-ins are referenced that clang does not understand. + // Therefore, prevent the inclusion of the header that references them. Of course, this + // will break if code actually requires stuff from there, but that should be the less common + // case. + const Core::Id type = projectPart.toolchainType; + if (type == ProjectExplorer::Constants::MINGW_TOOLCHAIN_TYPEID + || type == ProjectExplorer::Constants::GCC_TOOLCHAIN_TYPEID) + optionsBuilder.addDefine("#define _X86INTRIN_H_INCLUDED\n"); + + optionsBuilder.addToolchainAndProjectDefines(); + optionsBuilder.addHeaderPathOptions(); + + if (type == ProjectExplorer::Constants::MSVC_TOOLCHAIN_TYPEID) + optionsBuilder.add(QLatin1String("/EHsc")); // clang-cl does not understand exceptions + else + optionsBuilder.add(QLatin1String("-fPIC")); // TODO: Remove? + + QStringList options = optionsBuilder.options(); + prependWordWidthArgumentIfNotIncluded(&options, wordWidth); + return options; + } + +private: + ClangStaticAnalyzerOptionsBuilder(const CppTools::ProjectPart &projectPart) + : CompilerOptionsBuilder(projectPart) + , m_isMsvcToolchain(m_projectPart.toolchainType == ProjectExplorer::Constants::MSVC_TOOLCHAIN_TYPEID) + { + } + + void addLanguageOption(ProjectFile::Kind fileKind) override + { + if (m_isMsvcToolchain) + add(createLanguageOptionMsvc(fileKind)); + else + CompilerOptionsBuilder::addLanguageOption(fileKind); + } + + void addOptionsForLanguage(bool checkForBorlandExtensions) override + { + if (m_isMsvcToolchain) + return; + CompilerOptionsBuilder::addOptionsForLanguage(checkForBorlandExtensions); + } + + QString includeOption() const override + { + if (m_isMsvcToolchain) + return QLatin1String("/I"); + return CompilerOptionsBuilder::includeOption(); + } + + QString defineOption() const override + { + if (m_isMsvcToolchain) + return QLatin1String("/D"); + return CompilerOptionsBuilder::defineOption(); + } + +private: + bool m_isMsvcToolchain; +}; + +static AnalyzeUnits unitsToAnalyzeFromCompilerCallData( + const ProjectInfo::CompilerCallData &compilerCallData, + unsigned char wordWidth) +{ + qCDebug(LOG) << "Taking arguments for analyzing from CompilerCallData."; + + AnalyzeUnits unitsToAnalyze; + + QHashIterator<QString, QList<QStringList> > it(compilerCallData); + while (it.hasNext()) { + it.next(); + const QString file = it.key(); + const QList<QStringList> compilerCalls = it.value(); + foreach (const QStringList &options, compilerCalls) { + const QStringList arguments = tweakedArguments(file, options, wordWidth); + unitsToAnalyze << AnalyzeUnit(file, arguments); + } + } + + return unitsToAnalyze; +} + +static AnalyzeUnits unitsToAnalyzeFromProjectParts(const QList<ProjectPart::Ptr> projectParts, + unsigned char wordWidth) +{ + qCDebug(LOG) << "Taking arguments for analyzing from ProjectParts."; + + AnalyzeUnits unitsToAnalyze; + + foreach (const ProjectPart::Ptr &projectPart, projectParts) { + if (!projectPart->selectedForBuilding) + continue; + + foreach (const ProjectFile &file, projectPart->files) { + if (file.path == CppModelManager::configurationFileName()) + continue; + QTC_CHECK(file.kind != ProjectFile::Unclassified); + if (ProjectFile::isSource(file.kind)) { + const QStringList arguments + = ClangStaticAnalyzerOptionsBuilder::build(*projectPart.data(), + file.kind, + wordWidth); + unitsToAnalyze << AnalyzeUnit(file.path, arguments); + } + } + } + + return unitsToAnalyze; +} + +AnalyzeUnits ClangStaticAnalyzerRunControl::sortedUnitsToAnalyze() +{ + QTC_ASSERT(m_projectInfo.isValid(), return AnalyzeUnits()); + + AnalyzeUnits units; + const ProjectInfo::CompilerCallData compilerCallData = m_projectInfo.compilerCallData(); + if (compilerCallData.isEmpty()) { + units = unitsToAnalyzeFromProjectParts(m_projectInfo.projectParts(), + m_wordWidth); + } else { + units = unitsToAnalyzeFromCompilerCallData(compilerCallData, m_wordWidth); + } + + Utils::sort(units, [](const AnalyzeUnit &a1, const AnalyzeUnit &a2) -> bool { + return a1.file < a2.file; + }); + return units; +} + +static QDebug operator<<(QDebug debug, const Utils::Environment &environment) +{ + foreach (const QString &entry, environment.toStringList()) + debug << "\n " << entry; + return debug; +} + +static QDebug operator<<(QDebug debug, const AnalyzeUnits &analyzeUnits) +{ + foreach (const AnalyzeUnit &unit, analyzeUnits) + debug << "\n " << unit.file; + return debug; +} + +static Core::Id toolchainType(ProjectExplorer::RunConfiguration *runConfiguration) +{ + QTC_ASSERT(runConfiguration, return Core::Id()); + return ToolChainKitInformation::toolChain(runConfiguration->target()->kit())->typeId(); +} + +bool ClangStaticAnalyzerRunControl::startEngine() +{ + m_success = false; + emit starting(this); + + QTC_ASSERT(m_projectInfo.isValid(), emit finished(); return false); + const Utils::FileName projectFile = m_projectInfo.project()->projectFilePath(); + appendMessage(tr("Running Clang Static Analyzer on %1").arg(projectFile.toUserOutput()) + + QLatin1Char('\n'), Utils::NormalMessageFormat); + + // Check clang executable + bool isValidClangExecutable; + const QString executable = clangExecutableFromSettings(toolchainType(runConfiguration()), + &isValidClangExecutable); + if (!isValidClangExecutable) { + const QString errorMessage = tr("Clang Static Analyzer: Invalid executable \"%1\", stop.") + .arg(executable); + appendMessage(errorMessage + QLatin1Char('\n'), Utils::ErrorMessageFormat); + AnalyzerUtils::logToIssuesPane(Task::Error, errorMessage); + emit finished(); + return false; + } + m_clangExecutable = executable; + + // Create log dir + QTemporaryDir temporaryDir(QDir::tempPath() + QLatin1String("/qtc-clangstaticanalyzer-XXXXXX")); + temporaryDir.setAutoRemove(false); + if (!temporaryDir.isValid()) { + const QString errorMessage + = tr("Clang Static Analyzer: Failed to create temporary dir, stop."); + appendMessage(errorMessage + QLatin1Char('\n'), Utils::ErrorMessageFormat); + AnalyzerUtils::logToIssuesPane(Task::Error, errorMessage); + emit finished(); + return false; + } + m_clangLogFileDir = temporaryDir.path(); + + // Collect files + const AnalyzeUnits unitsToProcess = sortedUnitsToAnalyze(); + qCDebug(LOG) << "Files to process:" << unitsToProcess; + m_unitsToProcess = unitsToProcess; + m_initialFilesToProcessSize = m_unitsToProcess.count(); + m_filesAnalyzed = 0; + m_filesNotAnalyzed = 0; + + // Set up progress information + using namespace Core; + m_progress = QFutureInterface<void>(); + FutureProgress *futureProgress + = ProgressManager::addTask(m_progress.future(), tr("Analyzing"), "ClangStaticAnalyzer"); + futureProgress->setKeepOnFinish(FutureProgress::HideOnFinish); + connect(futureProgress, &FutureProgress::canceled, + this, &ClangStaticAnalyzerRunControl::onProgressCanceled); + m_progress.setProgressRange(0, m_initialFilesToProcessSize); + m_progress.reportStarted(); + + // Start process(es) + qCDebug(LOG) << "Environment:" << m_environment; + m_runners.clear(); + const int parallelRuns = ClangStaticAnalyzerSettings::instance()->simultaneousProcesses(); + QTC_ASSERT(parallelRuns >= 1, emit finished(); return false); + m_success = true; + + if (m_unitsToProcess.isEmpty()) { + finalize(); + return false; + } + while (m_runners.size() < parallelRuns && !m_unitsToProcess.isEmpty()) + analyzeNextFile(); + return true; +} + +void ClangStaticAnalyzerRunControl::stopEngine() +{ + QSetIterator<ClangStaticAnalyzerRunner *> i(m_runners); + while (i.hasNext()) { + ClangStaticAnalyzerRunner *runner = i.next(); + QObject::disconnect(runner, 0, this, 0); + delete runner; + } + m_runners.clear(); + m_unitsToProcess.clear(); + appendMessage(tr("Clang Static Analyzer stopped by user.") + QLatin1Char('\n'), + Utils::NormalMessageFormat); + m_progress.reportFinished(); + emit finished(); +} + +void ClangStaticAnalyzerRunControl::analyzeNextFile() +{ + if (m_progress.isFinished()) + return; // The previous call already reported that we are finished. + + if (m_unitsToProcess.isEmpty()) { + if (m_runners.isEmpty()) + finalize(); + return; + } + + const AnalyzeUnit unit = m_unitsToProcess.takeFirst(); + qCDebug(LOG) << "analyzeNextFile:" << unit.file; + + ClangStaticAnalyzerRunner *runner = createRunner(); + m_runners.insert(runner); + QTC_ASSERT(runner->run(unit.file, unit.arguments), return); + + appendMessage(tr("Analyzing \"%1\".").arg( + Utils::FileName::fromString(unit.file).toUserOutput()) + QLatin1Char('\n'), + Utils::StdOutFormat); +} + +ClangStaticAnalyzerRunner *ClangStaticAnalyzerRunControl::createRunner() +{ + QTC_ASSERT(!m_clangExecutable.isEmpty(), return 0); + QTC_ASSERT(!m_clangLogFileDir.isEmpty(), return 0); + + auto runner = new ClangStaticAnalyzerRunner(m_clangExecutable, + m_clangLogFileDir, + m_environment, + this); + connect(runner, &ClangStaticAnalyzerRunner::finishedWithSuccess, + this, &ClangStaticAnalyzerRunControl::onRunnerFinishedWithSuccess); + connect(runner, &ClangStaticAnalyzerRunner::finishedWithFailure, + this, &ClangStaticAnalyzerRunControl::onRunnerFinishedWithFailure); + return runner; +} + +void ClangStaticAnalyzerRunControl::onRunnerFinishedWithSuccess(const QString &logFilePath) +{ + qCDebug(LOG) << "onRunnerFinishedWithSuccess:" << logFilePath; + + QString errorMessage; + const QList<Diagnostic> diagnostics = LogFileReader::read(logFilePath, &errorMessage); + if (!errorMessage.isEmpty()) { + qCDebug(LOG) << "onRunnerFinishedWithSuccess: Error reading log file:" << errorMessage; + const QString filePath = qobject_cast<ClangStaticAnalyzerRunner *>(sender())->filePath(); + appendMessage(tr("Failed to analyze \"%1\": %2").arg(filePath, errorMessage) + + QLatin1Char('\n') + , Utils::StdErrFormat); + } else { + ++m_filesAnalyzed; + if (!diagnostics.isEmpty()) + emit newDiagnosticsAvailable(diagnostics); + } + + handleFinished(); +} + +void ClangStaticAnalyzerRunControl::onRunnerFinishedWithFailure(const QString &errorMessage, + const QString &errorDetails) +{ + qCDebug(LOG) << "onRunnerFinishedWithFailure:" << errorMessage << errorDetails; + + ++m_filesNotAnalyzed; + m_success = false; + const QString filePath = qobject_cast<ClangStaticAnalyzerRunner *>(sender())->filePath(); + appendMessage(tr("Failed to analyze \"%1\": %2").arg(filePath, errorMessage) + + QLatin1Char('\n') + , Utils::StdErrFormat); + appendMessage(errorDetails, Utils::StdErrFormat); + AnalyzerUtils::logToIssuesPane(Task::Warning, errorMessage); + AnalyzerUtils::logToIssuesPane(Task::Warning, errorDetails); + handleFinished(); +} + +void ClangStaticAnalyzerRunControl::handleFinished() +{ + m_runners.remove(qobject_cast<ClangStaticAnalyzerRunner *>(sender())); + updateProgressValue(); + sender()->deleteLater(); + analyzeNextFile(); +} + +void ClangStaticAnalyzerRunControl::onProgressCanceled() +{ + Analyzer::AnalyzerManager::stopTool(); + m_progress.reportCanceled(); + m_progress.reportFinished(); +} + +void ClangStaticAnalyzerRunControl::updateProgressValue() +{ + m_progress.setProgressValue(m_initialFilesToProcessSize - m_unitsToProcess.size()); +} + +void ClangStaticAnalyzerRunControl::finalize() +{ + appendMessage(tr("Clang Static Analyzer finished: " + "Processed %1 files successfully, %2 failed.") + .arg(m_filesAnalyzed) + .arg(m_filesNotAnalyzed) + + QLatin1Char('\n'), + Utils::NormalMessageFormat); + + if (m_filesAnalyzed == 0 && m_filesNotAnalyzed != 0) { + AnalyzerUtils::logToIssuesPane(Task::Error, + tr("Clang Static Analyzer: Failed to analyze any files.")); + } + + m_progress.reportFinished(); + emit finished(); +} + +} // namespace Internal +} // namespace ClangStaticAnalyzer diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerruncontrol.h b/src/plugins/clangstaticanalyzer/clangstaticanalyzerruncontrol.h new file mode 100644 index 0000000000000000000000000000000000000000..06cbc7ed9329ce674781db1f981ef254135d561e --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerruncontrol.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#ifndef CLANGSTATICANALYZERRUNCONTROL_H +#define CLANGSTATICANALYZERRUNCONTROL_H + +#include <analyzerbase/analyzerruncontrol.h> +#include <cpptools/projectinfo.h> +#include <utils/environment.h> + +#include <QFutureInterface> +#include <QStringList> + +namespace ClangStaticAnalyzer { +namespace Internal { + +class ClangStaticAnalyzerRunner; +class Diagnostic; + +struct AnalyzeUnit { + AnalyzeUnit(const QString &file, const QStringList &options) + : file(file), arguments(options) {} + + QString file; + QStringList arguments; // without file itself and "-o somePath" +}; +typedef QList<AnalyzeUnit> AnalyzeUnits; + +class ClangStaticAnalyzerRunControl : public Analyzer::AnalyzerRunControl +{ + Q_OBJECT + +public: + ClangStaticAnalyzerRunControl(ProjectExplorer::RunConfiguration *runConfiguration, + Core::Id runMode, + const CppTools::ProjectInfo &projectInfo); + + bool startEngine(); + void stopEngine(); + + bool success() const { return m_success; } // For testing. + +signals: + void newDiagnosticsAvailable(const QList<Diagnostic> &diagnostics); + +private: + AnalyzeUnits sortedUnitsToAnalyze(); + void analyzeNextFile(); + ClangStaticAnalyzerRunner *createRunner(); + + void onRunnerFinishedWithSuccess(const QString &logFilePath); + void onRunnerFinishedWithFailure(const QString &errorMessage, const QString &errorDetails); + void handleFinished(); + + void onProgressCanceled(); + void updateProgressValue(); + + void finalize(); + +private: + const CppTools::ProjectInfo m_projectInfo; + const unsigned char m_wordWidth; + + Utils::Environment m_environment; + QString m_clangExecutable; + QString m_clangLogFileDir; + QFutureInterface<void> m_progress; + AnalyzeUnits m_unitsToProcess; + QSet<ClangStaticAnalyzerRunner *> m_runners; + int m_initialFilesToProcessSize; + int m_filesAnalyzed; + int m_filesNotAnalyzed; + bool m_success; +}; + +} // namespace Internal +} // namespace ClangStaticAnalyzer + +#endif // CLANGSTATICANALYZERRUNCONTROL_H diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerruncontrolfactory.cpp b/src/plugins/clangstaticanalyzer/clangstaticanalyzerruncontrolfactory.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b89ea1cee55c826a8d7d828d4d972a4c8d94c1c9 --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerruncontrolfactory.cpp @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "clangstaticanalyzerruncontrolfactory.h" + +#include "clangstaticanalyzerconstants.h" + +#include <analyzerbase/analyzermanager.h> +#include <analyzerbase/analyzerruncontrol.h> +#include <analyzerbase/analyzerstartparameters.h> + +#include <coreplugin/icontext.h> + +#include <cpptools/cppmodelmanager.h> + +#include <projectexplorer/buildconfiguration.h> +#include <projectexplorer/gcctoolchain.h> +#include <projectexplorer/kit.h> +#include <projectexplorer/kitinformation.h> +#include <projectexplorer/project.h> +#include <projectexplorer/projectexplorerconstants.h> +#include <projectexplorer/session.h> +#include <projectexplorer/target.h> +#include <projectexplorer/toolchain.h> + +#include <utils/qtcassert.h> + +using namespace Analyzer; +using namespace ProjectExplorer; + +namespace ClangStaticAnalyzer { +namespace Internal { + +ClangStaticAnalyzerRunControlFactory::ClangStaticAnalyzerRunControlFactory( + ClangStaticAnalyzerTool *tool, + QObject *parent) + : IRunControlFactory(parent) + , m_tool(tool) +{ + QTC_CHECK(m_tool); +} + +bool ClangStaticAnalyzerRunControlFactory::canRun(RunConfiguration *runConfiguration, + Core::Id runMode) const +{ + if (runMode != Constants::CLANGSTATICANALYZER_RUN_MODE) + return false; + + Project *project = runConfiguration->target()->project(); + QTC_ASSERT(project, return false); + const Core::Context context = project->projectLanguages(); + if (!context.contains(ProjectExplorer::Constants::LANG_CXX)) + return false; + + Target *target = runConfiguration->target(); + QTC_ASSERT(target, return false); + Kit *kit = target->kit(); + QTC_ASSERT(kit, return false); + ToolChain *toolChain = ToolChainKitInformation::toolChain(kit); + return toolChain && (toolChain->typeId() == ProjectExplorer::Constants::CLANG_TOOLCHAIN_TYPEID + || toolChain->typeId() == ProjectExplorer::Constants::GCC_TOOLCHAIN_TYPEID + || toolChain->typeId() == ProjectExplorer::Constants::MINGW_TOOLCHAIN_TYPEID + || toolChain->typeId() == ProjectExplorer::Constants::MSVC_TOOLCHAIN_TYPEID); +} + +RunControl *ClangStaticAnalyzerRunControlFactory::create(RunConfiguration *runConfiguration, + Core::Id runMode, + QString *errorMessage) +{ + using namespace CppTools; + const ProjectInfo projectInfoBeforeBuild = m_tool->projectInfoBeforeBuild(); + QTC_ASSERT(projectInfoBeforeBuild.isValid(), return 0); + + QTC_ASSERT(runConfiguration, return 0); + Target * const target = runConfiguration->target(); + QTC_ASSERT(target, return 0); + Project * const project = target->project(); + QTC_ASSERT(project, return 0); + + const ProjectInfo projectInfoAfterBuild = CppModelManager::instance()->projectInfo(project); + + if (projectInfoAfterBuild.configurationOrFilesChanged(projectInfoBeforeBuild)) { + // If it's more than a release/debug build configuration change, e.g. + // a version control checkout, files might be not valid C++ anymore + // or even gone, so better stop here. + + m_tool->resetCursorAndProjectInfoBeforeBuild(); + if (errorMessage) { + *errorMessage = tr( + "The project configuration changed since the start of the Clang Static Analyzer. " + "Please re-run with current configuration."); + } + return 0; + } + + return AnalyzerManager::createRunControl(runConfiguration, runMode); +} + +} // namespace Internal +} // namespace ClangStaticAnalyzer diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerruncontrolfactory.h b/src/plugins/clangstaticanalyzer/clangstaticanalyzerruncontrolfactory.h new file mode 100644 index 0000000000000000000000000000000000000000..6f9e5fc81cd7c7204ded8c0703eb0c5e75336658 --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerruncontrolfactory.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#ifndef CLANGSTATICANALYZERRUNCONTROLFACTORY_H +#define CLANGSTATICANALYZERRUNCONTROLFACTORY_H + +#include "clangstaticanalyzertool.h" + +#include <projectexplorer/runconfiguration.h> + +namespace ClangStaticAnalyzer { +namespace Internal { + +class ClangStaticAnalyzerRunControlFactory : public ProjectExplorer::IRunControlFactory +{ + Q_OBJECT + +public: + explicit ClangStaticAnalyzerRunControlFactory(ClangStaticAnalyzerTool *tool, + QObject *parent = 0); + + bool canRun(ProjectExplorer::RunConfiguration *runConfiguration, + Core::Id runMode) const; + + ProjectExplorer::RunControl *create(ProjectExplorer::RunConfiguration *runConfiguration, + Core::Id runMode, + QString *errorMessage); + +private: + ClangStaticAnalyzerTool *m_tool; +}; + +} // namespace Internal +} // namespace ClangStaticAnalyzer + +#endif // CLANGSTATICANALYZERRUNCONTROLFACTORY_H diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerrunner.cpp b/src/plugins/clangstaticanalyzer/clangstaticanalyzerrunner.cpp new file mode 100644 index 0000000000000000000000000000000000000000..00e0672d3b487f893d2038dbc4292741f8d17b00 --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerrunner.cpp @@ -0,0 +1,193 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "clangstaticanalyzerrunner.h" + +#include "clangstaticanalyzerconstants.h" + +#include <utils/synchronousprocess.h> + +#include <QDebug> +#include <QDir> +#include <QFileInfo> +#include <QLoggingCategory> +#include <QTemporaryFile> + +static Q_LOGGING_CATEGORY(LOG, "qtc.clangstaticanalyzer.runner") + +static QString generalProcessError() +{ + return QObject::tr("An error occurred with the clang static analyzer process."); +} + +static QString finishedDueToCrash() +{ + return QObject::tr("Clang static analyzer crashed."); +} + +static QStringList constructCommandLineArguments(const QString &filePath, + const QString &logFile, + const QStringList &options) +{ + QStringList arguments = QStringList() + << QLatin1String("--analyze") + << QLatin1String("-o") + << logFile + ; + arguments += options; + arguments << QDir::toNativeSeparators(filePath); + return arguments; +} + +namespace ClangStaticAnalyzer { +namespace Internal { + +QString finishedWithBadExitCode(int exitCode) +{ + return QObject::tr("Clang static analyzer finished with exit code: %1.").arg(exitCode); +} + +ClangStaticAnalyzerRunner::ClangStaticAnalyzerRunner(const QString &clangExecutable, + const QString &clangLogFileDir, + const Utils::Environment &environment, + QObject *parent) + : QObject(parent) + , m_clangExecutable(clangExecutable) + , m_clangLogFileDir(clangLogFileDir) +{ + QTC_CHECK(!m_clangExecutable.isEmpty()); + QTC_CHECK(!m_clangLogFileDir.isEmpty()); + + m_process.setProcessChannelMode(QProcess::MergedChannels); + m_process.setProcessEnvironment(environment.toProcessEnvironment()); + m_process.setWorkingDirectory(m_clangLogFileDir); // Current clang-cl puts log file into working dir. + connect(&m_process, &QProcess::started, + this, &ClangStaticAnalyzerRunner::onProcessStarted); + connect(&m_process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), + this, &ClangStaticAnalyzerRunner::onProcessFinished); + connect(&m_process, static_cast<void (QProcess::*)(QProcess::ProcessError)>(&QProcess::error), + this, &ClangStaticAnalyzerRunner::onProcessError); + connect(&m_process, &QProcess::readyRead, + this, &ClangStaticAnalyzerRunner::onProcessOutput); +} + +ClangStaticAnalyzerRunner::~ClangStaticAnalyzerRunner() +{ + Utils::SynchronousProcess::stopProcess(m_process); +} + +bool ClangStaticAnalyzerRunner::run(const QString &filePath, const QStringList &compilerOptions) +{ + QTC_ASSERT(!m_clangExecutable.isEmpty(), return false); + QTC_CHECK(!compilerOptions.contains(QLatin1String("-o"))); + QTC_CHECK(!compilerOptions.contains(filePath)); + + m_filePath = filePath; + m_processOutput.clear(); + + m_logFile = createLogFile(filePath); + QTC_ASSERT(!m_logFile.isEmpty(), return false); + const QStringList arguments = constructCommandLineArguments(filePath, m_logFile, + compilerOptions); + m_commandLine = (QStringList(m_clangExecutable) + arguments).join(QLatin1String("\" \"")); + + qCDebug(LOG) << "Starting" << m_commandLine; + m_process.start(m_clangExecutable, arguments); + return true; +} + +QString ClangStaticAnalyzerRunner::filePath() const +{ + return m_filePath; +} + +void ClangStaticAnalyzerRunner::onProcessStarted() +{ + emit started(); +} + +void ClangStaticAnalyzerRunner::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + if (exitStatus == QProcess::NormalExit) { + if (exitCode == 0) + emit finishedWithSuccess(actualLogFile()); + else + emit finishedWithFailure(finishedWithBadExitCode(exitCode), processCommandlineAndOutput()); + } else { // == QProcess::CrashExit + emit finishedWithFailure(finishedDueToCrash(), processCommandlineAndOutput()); + } +} + +void ClangStaticAnalyzerRunner::onProcessError(QProcess::ProcessError error) +{ + if (error == QProcess::Crashed) + return; // handled by slot of finished() + + emit finishedWithFailure(generalProcessError(), processCommandlineAndOutput()); +} + +void ClangStaticAnalyzerRunner::onProcessOutput() +{ + m_processOutput.append(m_process.readAll()); +} + +QString ClangStaticAnalyzerRunner::createLogFile(const QString &filePath) const +{ + const QString fileName = QFileInfo(filePath).fileName(); + const QString fileTemplate = m_clangLogFileDir + + QLatin1String("/report-") + fileName + QLatin1String("-XXXXXX.plist"); + + QTemporaryFile temporaryFile; + temporaryFile.setAutoRemove(false); + temporaryFile.setFileTemplate(fileTemplate); + if (temporaryFile.open()) { + temporaryFile.close(); + return temporaryFile.fileName(); + } + return QString(); +} + +QString ClangStaticAnalyzerRunner::processCommandlineAndOutput() const +{ + return QObject::tr("Command line: \"%1\"\n" + "Process Error: %2\n" + "Output:\n%3") + .arg(m_commandLine, + QString::number(m_process.error()), + QString::fromLocal8Bit(m_processOutput)); +} + +QString ClangStaticAnalyzerRunner::actualLogFile() const +{ + if (QFileInfo(m_logFile).size() == 0) { + // Current clang-cl ignores -o, always putting the log file into the working directory. + return m_clangLogFileDir + QLatin1Char('/') + QFileInfo(m_filePath).completeBaseName() + + QLatin1String(".plist"); + } + return m_logFile; +} + +} // namespace Internal +} // namespace ClangStaticAnalyzer diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerrunner.h b/src/plugins/clangstaticanalyzer/clangstaticanalyzerrunner.h new file mode 100644 index 0000000000000000000000000000000000000000..92852d34c2c46d7551d8fe18c26dbee3370013cc --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerrunner.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#ifndef CLANGSTATICANALYZERRUNNER_H +#define CLANGSTATICANALYZERRUNNER_H + +#include <QString> +#include <QProcess> + +#include <utils/environment.h> +#include <utils/qtcassert.h> + +namespace ClangStaticAnalyzer { +namespace Internal { + +QString finishedWithBadExitCode(int exitCode); // exposed for tests + +class ClangStaticAnalyzerRunner : public QObject +{ + Q_OBJECT + +public: + ClangStaticAnalyzerRunner(const QString &clangExecutable, + const QString &clangLogFileDir, + const Utils::Environment &environment, + QObject *parent = 0); + ~ClangStaticAnalyzerRunner(); + + // compilerOptions is expected to contain everything except: + // (1) filePath, that is the file to analyze + // (2) -o output-file + bool run(const QString &filePath, const QStringList &compilerOptions = QStringList()); + + QString filePath() const; + +signals: + void started(); + void finishedWithSuccess(const QString &logFilePath); + void finishedWithFailure(const QString &errorMessage, const QString &errorDetails); + +private: + void onProcessStarted(); + void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); + void onProcessError(QProcess::ProcessError error); + void onProcessOutput(); + + QString createLogFile(const QString &filePath) const; + QString processCommandlineAndOutput() const; + QString actualLogFile() const; + +private: + QString m_clangExecutable; + QString m_clangLogFileDir; + QString m_filePath; + QString m_logFile; + QString m_commandLine; + QProcess m_process; + QByteArray m_processOutput; +}; + +} // namespace Internal +} // namespace ClangStaticAnalyzer + +#endif // CLANGSTATICANALYZERRUNNER_H diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzersettings.cpp b/src/plugins/clangstaticanalyzer/clangstaticanalyzersettings.cpp new file mode 100644 index 0000000000000000000000000000000000000000..803cbb02f09bf4fe0991af1b14c22d4f75348e1d --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzersettings.cpp @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "clangstaticanalyzersettings.h" + +#include "clangstaticanalyzerconstants.h" + +#include <coreplugin/icore.h> + +#include <utils/hostosinfo.h> +#include <utils/qtcassert.h> + +#include <QFileInfo> +#include <QThread> + +static const char clangExecutableKey[] = "clangExecutable"; +static const char simultaneousProcessesKey[] = "simultaneousProcesses"; + +namespace ClangStaticAnalyzer { +namespace Internal { + +ClangStaticAnalyzerSettings::ClangStaticAnalyzerSettings() + : m_simultaneousProcesses(-1) +{ + readSettings(); +} + +ClangStaticAnalyzerSettings *ClangStaticAnalyzerSettings::instance() +{ + static ClangStaticAnalyzerSettings instance; + return &instance; +} + +static QString clangExecutableFileName() +{ + return QLatin1String(Utils::HostOsInfo::isWindowsHost() ? "clang-cl.exe" : "clang"); +} + +QString ClangStaticAnalyzerSettings::defaultClangExecutable() const +{ + const QString shippedBinary = Core::ICore::libexecPath() + QLatin1Char('/') + + clangExecutableFileName(); + if (QFileInfo(shippedBinary).isExecutable()) + return shippedBinary; + return clangExecutableFileName(); +} + +QString ClangStaticAnalyzerSettings::clangExecutable(bool *isSet) const +{ + if (m_clangExecutable.isEmpty()) { + if (isSet) + *isSet = false; + return defaultClangExecutable(); + } + if (isSet) + *isSet = true; + return m_clangExecutable; +} + +void ClangStaticAnalyzerSettings::setClangExecutable(const QString &exectuable) +{ + m_clangExecutable = exectuable; +} + +int ClangStaticAnalyzerSettings::simultaneousProcesses() const +{ + return m_simultaneousProcesses; +} + +void ClangStaticAnalyzerSettings::setSimultaneousProcesses(int processes) +{ + QTC_ASSERT(processes >=1, return); + m_simultaneousProcesses = processes; +} + +void ClangStaticAnalyzerSettings::readSettings() +{ + QSettings *settings = Core::ICore::settings(); + settings->beginGroup(QLatin1String(Constants::SETTINGS_ID)); + + setClangExecutable(settings->value(QLatin1String(clangExecutableKey)).toString()); + + const int defaultSimultaneousProcesses = qMax(0, QThread::idealThreadCount() / 2); + setSimultaneousProcesses(settings->value(QLatin1String(simultaneousProcessesKey), + defaultSimultaneousProcesses).toInt()); + + settings->endGroup(); +} + +void ClangStaticAnalyzerSettings::writeSettings() const +{ + QSettings *settings = Core::ICore::settings(); + settings->beginGroup(QLatin1String(Constants::SETTINGS_ID)); + settings->setValue(QLatin1String(clangExecutableKey), m_clangExecutable); + settings->setValue(QLatin1String(simultaneousProcessesKey), simultaneousProcesses()); + settings->endGroup(); +} + +} // namespace Internal +} // namespace ClangStaticAnalyzer diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzersettings.h b/src/plugins/clangstaticanalyzer/clangstaticanalyzersettings.h new file mode 100644 index 0000000000000000000000000000000000000000..5f7d80a01fbed81210e0dde0563256c67d14959f --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzersettings.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#ifndef CLANGSTATICANALYZERSETTINGS_H +#define CLANGSTATICANALYZERSETTINGS_H + +#include <QString> + +namespace ClangStaticAnalyzer { +namespace Internal { + +class ClangStaticAnalyzerSettings +{ +public: + static ClangStaticAnalyzerSettings *instance(); + + void writeSettings() const; + + QString defaultClangExecutable() const; + QString clangExecutable(bool *isSet = nullptr) const; + void setClangExecutable(const QString &exectuable); + + int simultaneousProcesses() const; + void setSimultaneousProcesses(int processes); + +private: + ClangStaticAnalyzerSettings(); + void readSettings(); + + QString m_clangExecutable; + int m_simultaneousProcesses; +}; + +} // namespace Internal +} // namespace ClangStaticAnalyzer + +#endif // CLANGSTATICANALYZERSETTINGS_H diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzertool.cpp b/src/plugins/clangstaticanalyzer/clangstaticanalyzertool.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9fe109d63d6c6d54a44edc80895965fb99e394f0 --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzertool.cpp @@ -0,0 +1,332 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "clangstaticanalyzertool.h" + +#include "clangstaticanalyzerconstants.h" +#include "clangstaticanalyzerdiagnostic.h" +#include "clangstaticanalyzerdiagnosticmodel.h" +#include "clangstaticanalyzerdiagnosticview.h" +#include "clangstaticanalyzerruncontrol.h" + +#include <analyzerbase/analyzermanager.h> +#include <coreplugin/coreconstants.h> +#include <coreplugin/coreicons.h> +#include <coreplugin/icore.h> +#include <cpptools/cppmodelmanager.h> +#include <projectexplorer/buildconfiguration.h> +#include <projectexplorer/projectexplorer.h> +#include <projectexplorer/projectexplorerconstants.h> +#include <projectexplorer/session.h> +#include <projectexplorer/target.h> + +#include <utils/checkablemessagebox.h> +#include <utils/fancymainwindow.h> + +#include <QDockWidget> +#include <QHBoxLayout> +#include <QLabel> +#include <QListView> +#include <QSortFilterProxyModel> +#include <QToolButton> + +using namespace Analyzer; +using namespace ProjectExplorer; + +namespace ClangStaticAnalyzer { +namespace Internal { + +class DummyRunConfiguration : public RunConfiguration +{ + Q_OBJECT + +public: + DummyRunConfiguration(Target *parent) + : RunConfiguration(parent, "ClangStaticAnalyzer.DummyRunConfig") + { + setDefaultDisplayName(tr("Clang Static Analyzer")); + addExtraAspects(); + } + +private: + QWidget *createConfigurationWidget() override { return 0; } +}; + +ClangStaticAnalyzerTool::ClangStaticAnalyzerTool(QObject *parent) + : QObject(parent) + , m_diagnosticModel(0) + , m_diagnosticFilterModel(0) + , m_diagnosticView(0) + , m_goBack(0) + , m_goNext(0) + , m_running(false) +{ + setObjectName(QLatin1String("ClangStaticAnalyzerTool")); +} + +QWidget *ClangStaticAnalyzerTool::createWidgets() +{ + QTC_ASSERT(!m_diagnosticView, return 0); + QTC_ASSERT(!m_diagnosticModel, return 0); + QTC_ASSERT(!m_goBack, return 0); + QTC_ASSERT(!m_goNext, return 0); + + // + // Diagnostic View + // + m_diagnosticView = new ClangStaticAnalyzerDiagnosticView; + m_diagnosticView->setFrameStyle(QFrame::NoFrame); + m_diagnosticView->setAttribute(Qt::WA_MacShowFocusRect, false); + m_diagnosticModel = new ClangStaticAnalyzerDiagnosticModel(this); + m_diagnosticFilterModel = new ClangStaticAnalyzerDiagnosticFilterModel(this); + m_diagnosticFilterModel->setSourceModel(m_diagnosticModel); + m_diagnosticView->setModel(m_diagnosticFilterModel); + m_diagnosticView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + m_diagnosticView->setAutoScroll(false); + m_diagnosticView->setObjectName(QLatin1String("ClangStaticAnalyzerIssuesView")); + m_diagnosticView->setWindowTitle(tr("Clang Static Analyzer Issues")); + foreach (auto * const model, + QList<QAbstractItemModel *>() << m_diagnosticModel << m_diagnosticFilterModel) { + connect(model, &QAbstractItemModel::rowsInserted, + this, &ClangStaticAnalyzerTool::handleStateUpdate); + connect(model, &QAbstractItemModel::rowsRemoved, + this, &ClangStaticAnalyzerTool::handleStateUpdate); + connect(model, &QAbstractItemModel::modelReset, + this, &ClangStaticAnalyzerTool::handleStateUpdate); + connect(model, &QAbstractItemModel::layoutChanged, // For QSortFilterProxyModel::invalidate() + this, &ClangStaticAnalyzerTool::handleStateUpdate); + } + + QDockWidget *issuesDock = AnalyzerManager::createDockWidget(ClangStaticAnalyzerToolId, + m_diagnosticView); + issuesDock->show(); + Utils::FancyMainWindow *mw = AnalyzerManager::mainWindow(); + mw->splitDockWidget(mw->toolBarDockWidget(), issuesDock, Qt::Vertical); + + // + // Toolbar widget + // + QHBoxLayout *layout = new QHBoxLayout; + layout->setMargin(0); + layout->setSpacing(0); + + QAction *action = 0; + QToolButton *button = 0; + + // Go to previous diagnostic + action = new QAction(this); + action->setDisabled(true); + action->setIcon(Core::Icons::PREV.icon()); + action->setToolTip(tr("Go to previous bug.")); + connect(action, &QAction::triggered, m_diagnosticView, &DetailedErrorView::goBack); + button = new QToolButton; + button->setDefaultAction(action); + layout->addWidget(button); + m_goBack = action; + + // Go to next diagnostic + action = new QAction(this); + action->setDisabled(true); + action->setIcon(Core::Icons::NEXT.icon()); + action->setToolTip(tr("Go to next bug.")); + connect(action, &QAction::triggered, m_diagnosticView, &DetailedErrorView::goNext); + button = new QToolButton; + button->setDefaultAction(action); + layout->addWidget(button); + m_goNext = action; + + layout->addStretch(); + + QWidget *toolbarWidget = new QWidget; + toolbarWidget->setObjectName(QLatin1String("ClangStaticAnalyzerToolBarWidget")); + toolbarWidget->setLayout(layout); + return toolbarWidget; +} + +AnalyzerRunControl *ClangStaticAnalyzerTool::createRunControl(RunConfiguration *runConfiguration, + Core::Id runMode) +{ + QTC_ASSERT(runConfiguration, return 0); + QTC_ASSERT(m_projectInfoBeforeBuild.isValid(), return 0); + + // Some projects provides CompilerCallData once a build is finished, + // so pass on the updated Project Info unless no configuration change + // (defines/includes/files) happened. + Project *project = runConfiguration->target()->project(); + QTC_ASSERT(project, return 0); + const CppTools::ProjectInfo projectInfoAfterBuild + = CppTools::CppModelManager::instance()->projectInfo(project); + QTC_ASSERT(!projectInfoAfterBuild.configurationOrFilesChanged(m_projectInfoBeforeBuild), + return 0); + m_projectInfoBeforeBuild = CppTools::ProjectInfo(); + + auto runControl = new ClangStaticAnalyzerRunControl(runConfiguration, runMode, + projectInfoAfterBuild); + connect(runControl, &ClangStaticAnalyzerRunControl::starting, + this, &ClangStaticAnalyzerTool::onEngineIsStarting); + connect(runControl, &ClangStaticAnalyzerRunControl::newDiagnosticsAvailable, + this, &ClangStaticAnalyzerTool::onNewDiagnosticsAvailable); + connect(runControl, &ClangStaticAnalyzerRunControl::finished, + this, &ClangStaticAnalyzerTool::onEngineFinished); + return runControl; +} + +static bool dontStartAfterHintForDebugMode(Project *project) +{ + BuildConfiguration::BuildType buildType = BuildConfiguration::Unknown; + if (project) { + if (const Target *target = project->activeTarget()) { + if (const BuildConfiguration *buildConfig = target->activeBuildConfiguration()) + buildType = buildConfig->buildType(); + } + } + + if (buildType == BuildConfiguration::Release) { + const QString wrongMode = ClangStaticAnalyzerTool::tr("Release"); + const QString toolName = ClangStaticAnalyzerTool::tr("Clang Static Analyzer"); + const QString title = ClangStaticAnalyzerTool::tr("Run %1 in %2 Mode?").arg(toolName) + .arg(wrongMode); + const QString message = ClangStaticAnalyzerTool::tr( + "<html><head/><body>" + "<p>You are trying to run the tool \"%1\" on an application in %2 mode. The tool is " + "designed to be used in Debug mode since enabled assertions can reduce the number of " + "false positives.</p>" + "<p>Do you want to continue and run the tool in %2 mode?</p>" + "</body></html>") + .arg(toolName).arg(wrongMode); + if (Utils::CheckableMessageBox::doNotAskAgainQuestion(Core::ICore::mainWindow(), + title, message, Core::ICore::settings(), + QLatin1String("ClangStaticAnalyzerCorrectModeWarning")) != QDialogButtonBox::Yes) + return true; + } + + return false; +} + +void ClangStaticAnalyzerTool::startTool() +{ + AnalyzerManager::showMode(); + + Project *project = SessionManager::startupProject(); + QTC_ASSERT(project, emit finished(false); return); + + if (dontStartAfterHintForDebugMode(project)) + return; + + m_diagnosticModel->clear(); + setBusyCursor(true); + m_diagnosticFilterModel->setProject(project); + m_projectInfoBeforeBuild = CppTools::CppModelManager::instance()->projectInfo(project); + QTC_ASSERT(m_projectInfoBeforeBuild.isValid(), emit finished(false); return); + m_running = true; + handleStateUpdate(); + + Target * const target = project->activeTarget(); + QTC_ASSERT(target, return); + DummyRunConfiguration *& rc = m_runConfigs[target]; + if (!rc) { + rc = new DummyRunConfiguration(target); + connect(project, &Project::aboutToRemoveTarget, this, + [this](Target *t) { m_runConfigs.remove(t); }); + const auto onProjectRemoved = [this](Project *p) { + foreach (Target * const t, p->targets()) + m_runConfigs.remove(t); + }; + connect(SessionManager::instance(), &SessionManager::aboutToRemoveProject, this, + onProjectRemoved, Qt::UniqueConnection); + } + ProjectExplorerPlugin::runRunConfiguration(rc, Constants::CLANGSTATICANALYZER_RUN_MODE); +} + +CppTools::ProjectInfo ClangStaticAnalyzerTool::projectInfoBeforeBuild() const +{ + return m_projectInfoBeforeBuild; +} + +void ClangStaticAnalyzerTool::resetCursorAndProjectInfoBeforeBuild() +{ + setBusyCursor(false); + m_projectInfoBeforeBuild = CppTools::ProjectInfo(); +} + +QList<Diagnostic> ClangStaticAnalyzerTool::diagnostics() const +{ + return m_diagnosticModel->diagnostics(); +} + +void ClangStaticAnalyzerTool::onEngineIsStarting() +{ + QTC_ASSERT(m_diagnosticModel, return); +} + +void ClangStaticAnalyzerTool::onNewDiagnosticsAvailable(const QList<Diagnostic> &diagnostics) +{ + QTC_ASSERT(m_diagnosticModel, return); + m_diagnosticModel->addDiagnostics(diagnostics); +} + +void ClangStaticAnalyzerTool::onEngineFinished() +{ + resetCursorAndProjectInfoBeforeBuild(); + m_running = false; + handleStateUpdate(); + emit finished(static_cast<ClangStaticAnalyzerRunControl *>(sender())->success()); +} + +void ClangStaticAnalyzerTool::setBusyCursor(bool busy) +{ + QTC_ASSERT(m_diagnosticView, return); + QCursor cursor(busy ? Qt::BusyCursor : Qt::ArrowCursor); + m_diagnosticView->setCursor(cursor); +} + +void ClangStaticAnalyzerTool::handleStateUpdate() +{ + QTC_ASSERT(m_goBack, return); + QTC_ASSERT(m_goNext, return); + QTC_ASSERT(m_diagnosticModel, return); + QTC_ASSERT(m_diagnosticFilterModel, return); + + const int issuesFound = m_diagnosticModel->diagnostics().count(); + const int issuesVisible = m_diagnosticFilterModel->rowCount(); + m_goBack->setEnabled(issuesVisible > 1); + m_goNext->setEnabled(issuesVisible > 1); + + QString message = m_running ? tr("Clang Static Analyzer running.") + : tr("Clang Static Analyzer finished."); + message += QLatin1Char(' '); + if (issuesFound == 0) { + message += tr("No issues found."); + } else { + message += tr("%n issues found (%1 suppressed).", 0, issuesFound) + .arg(issuesFound - issuesVisible); + } + AnalyzerManager::showPermanentStatusMessage(ClangStaticAnalyzerToolId, message); +} + +} // namespace Internal +} // namespace ClangStaticAnalyzer + +#include "clangstaticanalyzertool.moc" diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzertool.h b/src/plugins/clangstaticanalyzer/clangstaticanalyzertool.h new file mode 100644 index 0000000000000000000000000000000000000000..e5a748d0cd129da58f3a016c64116543c22aeb82 --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzertool.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#ifndef CLANGSTATICANALYZERTOOL_H +#define CLANGSTATICANALYZERTOOL_H + +#include <analyzerbase/ianalyzertool.h> +#include <cpptools/projectinfo.h> + +#include <QHash> + +namespace Analyzer { class DetailedErrorView; } +namespace ProjectExplorer { class Target; } + +namespace ClangStaticAnalyzer { +namespace Internal { + +class ClangStaticAnalyzerDiagnosticFilterModel; +class ClangStaticAnalyzerDiagnosticModel; +class ClangStaticAnalyzerDiagnosticView; +class Diagnostic; +class DummyRunConfiguration; + +const char ClangStaticAnalyzerToolId[] = "ClangStaticAnalyzer"; + +class ClangStaticAnalyzerTool : public QObject +{ + Q_OBJECT + +public: + explicit ClangStaticAnalyzerTool(QObject *parent = 0); + CppTools::ProjectInfo projectInfoBeforeBuild() const; + void resetCursorAndProjectInfoBeforeBuild(); + + // For testing. + bool isRunning() const { return m_running; } + QList<Diagnostic> diagnostics() const; + + QWidget *createWidgets(); + Analyzer::AnalyzerRunControl *createRunControl(ProjectExplorer::RunConfiguration *runConfiguration, + Core::Id runMode); + void startTool(); + +signals: + void finished(bool success); // For testing. + +private: + void onEngineIsStarting(); + void onNewDiagnosticsAvailable(const QList<Diagnostic> &diagnostics); + void onEngineFinished(); + + void setBusyCursor(bool busy); + void handleStateUpdate(); + +private: + CppTools::ProjectInfo m_projectInfoBeforeBuild; + + ClangStaticAnalyzerDiagnosticModel *m_diagnosticModel; + ClangStaticAnalyzerDiagnosticFilterModel *m_diagnosticFilterModel; + ClangStaticAnalyzerDiagnosticView *m_diagnosticView; + + QAction *m_goBack; + QAction *m_goNext; + QHash<ProjectExplorer::Target *, DummyRunConfiguration *> m_runConfigs; + bool m_running; +}; + +} // namespace Internal +} // namespace ClangStaticAnalyzer + +#endif // CLANGSTATICANALYZERTOOL_H diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerunittests.cpp b/src/plugins/clangstaticanalyzer/clangstaticanalyzerunittests.cpp new file mode 100644 index 0000000000000000000000000000000000000000..12acb811a629bd5d2c854b1ccd168981e9e405d3 --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerunittests.cpp @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "clangstaticanalyzerunittests.h" + +#include "clangstaticanalyzerdiagnostic.h" +#include "clangstaticanalyzertool.h" +#include "clangstaticanalyzerutils.h" + +#include <analyzerbase/analyzermanager.h> +#include <cpptools/cppmodelmanager.h> +#include <cpptools/cpptoolstestcase.h> +#include <projectexplorer/kitinformation.h> +#include <projectexplorer/kitmanager.h> +#include <projectexplorer/projectexplorer.h> +#include <projectexplorer/toolchain.h> +#include <utils/fileutils.h> + +#include <QEventLoop> +#include <QSignalSpy> +#include <QTemporaryDir> +#include <QTimer> +#include <QtTest> + +using namespace Analyzer; +using namespace ProjectExplorer; +using namespace Utils; + +namespace ClangStaticAnalyzer { +namespace Internal { + +ClangStaticAnalyzerUnitTests::ClangStaticAnalyzerUnitTests(ClangStaticAnalyzerTool *analyzerTool, + QObject *parent) + : QObject(parent) + , m_analyzerTool(analyzerTool) + , m_tmpDir(0) +{ +} + +void ClangStaticAnalyzerUnitTests::initTestCase() +{ + const QList<Kit *> allKits = KitManager::kits(); + if (allKits.count() != 1) + QSKIP("This test requires exactly one kit to be present"); + const ToolChain * const toolchain = ToolChainKitInformation::toolChain(allKits.first()); + if (!toolchain) + QSKIP("This test requires that there is a kit with a toolchain."); + bool hasClangExecutable; + clangExecutableFromSettings(toolchain->typeId(), &hasClangExecutable); + if (!hasClangExecutable) + QSKIP("No clang suitable for analyzing found"); + + m_tmpDir = new CppTools::Tests::TemporaryCopiedDir(QLatin1String(":/unit-tests")); + QVERIFY(m_tmpDir->isValid()); +} + +void ClangStaticAnalyzerUnitTests::cleanupTestCase() +{ + delete m_tmpDir; +} + +void ClangStaticAnalyzerUnitTests::testProject() +{ + QFETCH(QString, projectFilePath); + QFETCH(int, expectedDiagCount); + + CppTools::Tests::ProjectOpenerAndCloser projectManager; + const CppTools::ProjectInfo projectInfo = projectManager.open(projectFilePath, true); + QVERIFY(projectInfo.isValid()); + AnalyzerManager::selectAction(ClangStaticAnalyzerToolId, /* alsoRunIt = */ true); + QSignalSpy waiter(m_analyzerTool, SIGNAL(finished(bool))); + QVERIFY(waiter.wait(30000)); + const QList<QVariant> arguments = waiter.takeFirst(); + QVERIFY(arguments.first().toBool()); + QCOMPARE(m_analyzerTool->diagnostics().count(), expectedDiagCount); +} + +void ClangStaticAnalyzerUnitTests::testProject_data() +{ + QTest::addColumn<QString>("projectFilePath"); + QTest::addColumn<int>("expectedDiagCount"); + + QTest::newRow("simple qbs project") + << QString(m_tmpDir->absolutePath("simple/simple.qbs")) << 1; + QTest::newRow("simple qmake project") + << QString(m_tmpDir->absolutePath("simple/simple.pro")) << 1; + + QTest::newRow("qt-widgets-app qbs project") + << QString(m_tmpDir->absolutePath("qt-widgets-app/qt-widgets-app.qbs")) << 0; + QTest::newRow("qt-widgets-app qmake project") + << QString(m_tmpDir->absolutePath("qt-widgets-app/qt-widgets-app.pro")) << 0; +} + +} // namespace Internal +} // namespace ClangStaticAnalyzerPlugin diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerunittests.h b/src/plugins/clangstaticanalyzer/clangstaticanalyzerunittests.h new file mode 100644 index 0000000000000000000000000000000000000000..b49ef5b1d30043c28b5097e163de22ffe299ce00 --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerunittests.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#ifndef CLANGSTATICANALYZERUNITTESTS_H +#define CLANGSTATICANALYZERUNITTESTS_H + +#include <QObject> +#include <QTemporaryDir> + +namespace CppTools { namespace Tests { class TemporaryCopiedDir; } } + +namespace ClangStaticAnalyzer { +namespace Internal { +class ClangStaticAnalyzerTool; + +class ClangStaticAnalyzerUnitTests : public QObject +{ + Q_OBJECT + +public: + ClangStaticAnalyzerUnitTests(ClangStaticAnalyzerTool *analyzerTool, QObject *parent = 0); + +private slots: + void initTestCase(); + void cleanupTestCase(); + void testProject(); + void testProject_data(); + +private: + ClangStaticAnalyzerTool * const m_analyzerTool; + CppTools::Tests::TemporaryCopiedDir *m_tmpDir; +}; + +} // namespace Internal +} // namespace ClangStaticAnalyzerPlugin + +#endif // Include guard + diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerunittests.qrc b/src/plugins/clangstaticanalyzer/clangstaticanalyzerunittests.qrc new file mode 100644 index 0000000000000000000000000000000000000000..d8a1a8674e866db31d4c1853e81566a27ab132d3 --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerunittests.qrc @@ -0,0 +1,13 @@ +<RCC> + <qresource prefix="/"> + <file>unit-tests/simple/main.cpp</file> + <file>unit-tests/simple/simple.qbs</file> + <file>unit-tests/simple/simple.pro</file> + <file>unit-tests/qt-widgets-app/main.cpp</file> + <file>unit-tests/qt-widgets-app/mainwindow.cpp</file> + <file>unit-tests/qt-widgets-app/mainwindow.h</file> + <file>unit-tests/qt-widgets-app/mainwindow.ui</file> + <file>unit-tests/qt-widgets-app/qt-widgets-app.pro</file> + <file>unit-tests/qt-widgets-app/qt-widgets-app.qbs</file> + </qresource> +</RCC> diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerutils.cpp b/src/plugins/clangstaticanalyzer/clangstaticanalyzerutils.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1c7af5176bac1a9d797aac7b46951e75cbe3ee7d --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerutils.cpp @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "clangstaticanalyzerutils.h" + +#include "clangstaticanalyzerdiagnostic.h" +#include "clangstaticanalyzersettings.h" + +#include <projectexplorer/projectexplorerconstants.h> + +#include <utils/environment.h> + +#include <QCoreApplication> +#include <QFileInfo> + +static bool isFileExecutable(const QString &executablePath) +{ + if (executablePath.isEmpty()) + return false; + + const QFileInfo fileInfo(executablePath); + return fileInfo.isFile() && fileInfo.isExecutable(); +} + +namespace ClangStaticAnalyzer { +namespace Internal { + +QString clangExecutableFromSettings(Core::Id toolchainType, bool *isValid) +{ + QString exeFromSettings = ClangStaticAnalyzerSettings::instance()->clangExecutable(); + if (toolchainType == ProjectExplorer::Constants::MSVC_TOOLCHAIN_TYPEID) + exeFromSettings.replace(QLatin1String("clang.exe"), QLatin1String("clang-cl.exe")); + return clangExecutable(exeFromSettings, isValid); +} + +QString clangExecutable(const QString &fileNameOrPath, bool *isValid) +{ + QString executable = fileNameOrPath; + if (executable.isEmpty()) { + *isValid = false; + return executable; + } + + if (!QFileInfo(executable).isAbsolute()) { + const Utils::Environment &environment = Utils::Environment::systemEnvironment(); + const QString executableFromPath = environment.searchInPath(executable).toString(); + if (executableFromPath.isEmpty()) { + *isValid = false; + return executable; + } + executable = executableFromPath; + } + + *isValid = isFileExecutable(executable) && isClangExecutableUsable(executable); + return executable; +} + +QString createFullLocationString(const Analyzer::DiagnosticLocation &location) +{ + const QString filePath = location.filePath; + const QString lineNumber = QString::number(location.line); + return filePath + QLatin1Char(':') + lineNumber; +} + +bool isClangExecutableUsable(const QString &filePath, QString *errorMessage) +{ + const QFileInfo fi(filePath); + if (fi.isSymLink() && fi.symLinkTarget().contains(QLatin1String("icecc"))) { + if (errorMessage) { + *errorMessage = QCoreApplication::translate("ClangStaticAnalyzer", + "The chosen file \"%1\" seems to point to an icecc binary not suitable " + "for analyzing.\nPlease set a real clang executable.") + .arg(filePath); + } + return false; + } + return true; +} + +} // namespace Internal +} // namespace ClangStaticAnalyzer diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerutils.h b/src/plugins/clangstaticanalyzer/clangstaticanalyzerutils.h new file mode 100644 index 0000000000000000000000000000000000000000..63f6f6517f96ae8e0c9ba752de0c3a6e5b637554 --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerutils.h @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#ifndef CLANGSTATICANALYZERUTILS_H +#define CLANGSTATICANALYZERUTILS_H + +#include <coreplugin/id.h> + +#include <QtGlobal> + +QT_BEGIN_NAMESPACE +class QString; +QT_END_NAMESPACE + +namespace Analyzer { class DiagnosticLocation; } + +namespace ClangStaticAnalyzer { +namespace Internal { + +bool isClangExecutableUsable(const QString &filePath, QString *errorMessage = 0); + +QString clangExecutable(const QString &fileNameOrPath, bool *isValid); +QString clangExecutableFromSettings(Core::Id toolchainType, bool *isValid); + +QString createFullLocationString(const Analyzer::DiagnosticLocation &location); + +} // namespace Internal +} // namespace ClangStaticAnalyzer + +#endif // CLANGSTATICANALYZERUTILS_H diff --git a/src/plugins/clangstaticanalyzer/tests/clangstaticanalyzerautotest.qbs b/src/plugins/clangstaticanalyzer/tests/clangstaticanalyzerautotest.qbs new file mode 100644 index 0000000000000000000000000000000000000000..88dfcb32a138713b31e4f2543039f5905f489fde --- /dev/null +++ b/src/plugins/clangstaticanalyzer/tests/clangstaticanalyzerautotest.qbs @@ -0,0 +1,11 @@ +import qbs + +QtcAutotest { + Depends { name: "Qt.widgets" } + Depends { name: "AnalyzerBase" } + Depends { name: "Utils" } + + property path pluginDir: "../../" + cpp.defines: base.concat('SRCDIR="' + sourceDirectory + '"') + cpp.includePaths: base.concat(pluginDir + "/..") +} diff --git a/src/plugins/clangstaticanalyzer/tests/clangstaticanalyzerlogfilereader/clangstaticanalyzerlogfilereader.pro b/src/plugins/clangstaticanalyzer/tests/clangstaticanalyzerlogfilereader/clangstaticanalyzerlogfilereader.pro new file mode 100644 index 0000000000000000000000000000000000000000..d9dda64f85d69012c909ea7e16669ad3081ae11b --- /dev/null +++ b/src/plugins/clangstaticanalyzer/tests/clangstaticanalyzerlogfilereader/clangstaticanalyzerlogfilereader.pro @@ -0,0 +1,14 @@ +include(../tests.pri) + +TARGET = tst_clangstaticanalyzerlogfilereader + +DEFINES += SRCDIR=\\\"$$PWD/\\\" + +SOURCES += \ + tst_clangstaticanalyzerlogfilereader.cpp \ + $$PLUGINDIR/clangstaticanalyzerdiagnostic.cpp \ + $$PLUGINDIR/clangstaticanalyzerlogfilereader.cpp + +HEADERS += \ + $$PLUGINDIR/clangstaticanalyzerdiagnostic.h \ + $$PLUGINDIR/clangstaticanalyzerlogfilereader.h diff --git a/src/plugins/clangstaticanalyzer/tests/clangstaticanalyzerlogfilereader/clangstaticanalyzerlogfilereader.qbs b/src/plugins/clangstaticanalyzer/tests/clangstaticanalyzerlogfilereader/clangstaticanalyzerlogfilereader.qbs new file mode 100644 index 0000000000000000000000000000000000000000..a1ac819f5f640805690be13b9938d823c975ddf2 --- /dev/null +++ b/src/plugins/clangstaticanalyzer/tests/clangstaticanalyzerlogfilereader/clangstaticanalyzerlogfilereader.qbs @@ -0,0 +1,21 @@ +import qbs +import "../clangstaticanalyzerautotest.qbs" as ClangStaticAnalyzerAutotest + +ClangStaticAnalyzerAutotest { + name: "ClangStaticAnalyzerLogFileReader Autotest" + + Group { + name: "sources from plugin" + prefix: pluginDir + '/' + files: [ + "clangstaticanalyzerdiagnostic.cpp", + "clangstaticanalyzerdiagnostic.h", + "clangstaticanalyzerlogfilereader.cpp", + "clangstaticanalyzerlogfilereader.h", + ] + } + + files: [ + "tst_clangstaticanalyzerlogfilereader.cpp" + ] +} diff --git a/src/plugins/clangstaticanalyzer/tests/clangstaticanalyzerlogfilereader/data/noDiagnostics.plist b/src/plugins/clangstaticanalyzer/tests/clangstaticanalyzerlogfilereader/data/noDiagnostics.plist new file mode 100644 index 0000000000000000000000000000000000000000..2cccbf770f9479f64213a0917abc2cfe6f7a6b8a --- /dev/null +++ b/src/plugins/clangstaticanalyzer/tests/clangstaticanalyzerlogfilereader/data/noDiagnostics.plist @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>clang_version</key> +<string>Ubuntu clang version 3.5-1ubuntu1 (trunk) (based on LLVM 3.5)</string> + <key>files</key> + <array> + </array> + <key>diagnostics</key> + <array> + </array> +</dict> +</plist> \ No newline at end of file diff --git a/src/plugins/clangstaticanalyzer/tests/clangstaticanalyzerlogfilereader/data/someDiagnostics.plist b/src/plugins/clangstaticanalyzer/tests/clangstaticanalyzerlogfilereader/data/someDiagnostics.plist new file mode 100644 index 0000000000000000000000000000000000000000..3fa5effad2d634dd1b0d2bdb53497433818c3b1d --- /dev/null +++ b/src/plugins/clangstaticanalyzer/tests/clangstaticanalyzerlogfilereader/data/someDiagnostics.plist @@ -0,0 +1,162 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>clang_version</key> +<string>Ubuntu clang version 3.5-1ubuntu1 (trunk) (based on LLVM 3.5)</string> + <key>files</key> + <array> + <string>../csatestproject/core.CallAndMessage3.cpp</string> + </array> + <key>diagnostics</key> + <array> + <dict> + <key>path</key> + <array> + <dict> + <key>kind</key><string>control</string> + <key>edges</key> + <array> + <dict> + <key>start</key> + <array> + <dict> + <key>line</key><integer>34</integer> + <key>col</key><integer>3</integer> + <key>file</key><integer>0</integer> + </dict> + <dict> + <key>line</key><integer>34</integer> + <key>col</key><integer>6</integer> + <key>file</key><integer>0</integer> + </dict> + </array> + <key>end</key> + <array> + <dict> + <key>line</key><integer>35</integer> + <key>col</key><integer>3</integer> + <key>file</key><integer>0</integer> + </dict> + <dict> + <key>line</key><integer>35</integer> + <key>col</key><integer>5</integer> + <key>file</key><integer>0</integer> + </dict> + </array> + </dict> + </array> + </dict> + <dict> + <key>kind</key><string>event</string> + <key>location</key> + <dict> + <key>line</key><integer>35</integer> + <key>col</key><integer>3</integer> + <key>file</key><integer>0</integer> + </dict> + <key>ranges</key> + <array> + <array> + <dict> + <key>line</key><integer>35</integer> + <key>col</key><integer>3</integer> + <key>file</key><integer>0</integer> + </dict> + <dict> + <key>line</key><integer>35</integer> + <key>col</key><integer>9</integer> + <key>file</key><integer>0</integer> + </dict> + </array> + </array> + <key>depth</key><integer>0</integer> + <key>extended_message</key> + <string>Null pointer value stored to 'foo'</string> + <key>message</key> + <string>Null pointer value stored to 'foo'</string> + </dict> + <dict> + <key>kind</key><string>control</string> + <key>edges</key> + <array> + <dict> + <key>start</key> + <array> + <dict> + <key>line</key><integer>35</integer> + <key>col</key><integer>3</integer> + <key>file</key><integer>0</integer> + </dict> + <dict> + <key>line</key><integer>35</integer> + <key>col</key><integer>5</integer> + <key>file</key><integer>0</integer> + </dict> + </array> + <key>end</key> + <array> + <dict> + <key>line</key><integer>36</integer> + <key>col</key><integer>3</integer> + <key>file</key><integer>0</integer> + </dict> + <dict> + <key>line</key><integer>36</integer> + <key>col</key><integer>5</integer> + <key>file</key><integer>0</integer> + </dict> + </array> + </dict> + </array> + </dict> + <dict> + <key>kind</key><string>event</string> + <key>location</key> + <dict> + <key>line</key><integer>36</integer> + <key>col</key><integer>3</integer> + <key>file</key><integer>0</integer> + </dict> + <key>ranges</key> + <array> + <array> + <dict> + <key>line</key><integer>36</integer> + <key>col</key><integer>3</integer> + <key>file</key><integer>0</integer> + </dict> + <dict> + <key>line</key><integer>36</integer> + <key>col</key><integer>5</integer> + <key>file</key><integer>0</integer> + </dict> + </array> + </array> + <key>depth</key><integer>0</integer> + <key>extended_message</key> + <string>Called function pointer is null (null dereference)</string> + <key>message</key> + <string>Called function pointer is null (null dereference)</string> + </dict> + </array> + <key>description</key><string>Called function pointer is null (null dereference)</string> + <key>category</key><string>Logic error</string> + <key>type</key><string>Called function pointer is null (null dereference)</string> + <key>issue_context_kind</key><string>function</string> + <key>issue_context</key><string>test</string> + <key>issue_hash</key><string>3</string> + <key>location</key> + <dict> + <key>line</key><integer>36</integer> + <key>col</key><integer>3</integer> + <key>file</key><integer>0</integer> + </dict> + <key>HTMLDiagnostics_files</key> + <array> + <string>report-6c3c2a.html</string> + </array> + </dict> + </array> +</dict> +</plist> \ No newline at end of file diff --git a/src/plugins/clangstaticanalyzer/tests/clangstaticanalyzerlogfilereader/tst_clangstaticanalyzerlogfilereader.cpp b/src/plugins/clangstaticanalyzer/tests/clangstaticanalyzerlogfilereader/tst_clangstaticanalyzerlogfilereader.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e8050c514ed218ae4f325869a0af761b759cf638 --- /dev/null +++ b/src/plugins/clangstaticanalyzer/tests/clangstaticanalyzerlogfilereader/tst_clangstaticanalyzerlogfilereader.cpp @@ -0,0 +1,162 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include <clangstaticanalyzer/clangstaticanalyzerlogfilereader.h> + +#include <utils/fileutils.h> + +#include <QtTest> + +enum { debug = 0 }; + +using namespace Analyzer; +using namespace ClangStaticAnalyzer::Internal; + +namespace { + +QDebug operator<<(QDebug dbg, const ExplainingStep &step) +{ + dbg << '\n' + << " ExplainingStep\n" + << " location:" << step.location << '\n' + << " ranges:\n"; + foreach (const DiagnosticLocation &location, step.ranges) + dbg << " " << location << '\n'; + dbg + << " message:" << step.message << '\n' + << " extendedMessage:" << step.extendedMessage << '\n' + << " depth:" << step.depth << '\n'; + return dbg; +} + +QDebug operator<<(QDebug dbg, const Diagnostic &diagnostic) +{ + dbg << "\nDiagnostic\n" + << " description:" << diagnostic.description << '\n' + << " category:" << diagnostic.category << '\n' + << " type:" << diagnostic.type << '\n' + << " issueContextKind:" << diagnostic.issueContextKind << '\n' + << " issueContext:" << diagnostic.issueContext << '\n' + << " location:" << diagnostic.location << '\n' + << " explaining steps:\n"; + foreach (const ExplainingStep &explaingStep, diagnostic.explainingSteps) + dbg << explaingStep; + return dbg; +} + +bool createEmptyFile(const QString &filePath) +{ + Utils::FileSaver saver(filePath); + return saver.write("") && saver.finalize(); +} + +QString testFilePath(const QString &relativePath) +{ + const QString fullPath = QString::fromLatin1(SRCDIR) + relativePath; + const QFileInfo fi(fullPath); + if (fi.exists() && fi.isReadable()) + return fullPath; + return QString(); +} + +} // anonymous namespace + +class ClangStaticAnalyzerLogFileReaderTest : public QObject +{ + Q_OBJECT + +private slots: + void readEmptyFile(); + void readFileWithNoDiagnostics(); + void readFileWithDiagnostics(); +}; + +void ClangStaticAnalyzerLogFileReaderTest::readEmptyFile() +{ + const QString filePath = QDir::tempPath() + QLatin1String("/empty.file"); + QVERIFY(createEmptyFile(filePath)); + + QString errorMessage; + const QList<Diagnostic> diagnostics = LogFileReader::read(filePath, &errorMessage); + QVERIFY(!errorMessage.isEmpty()); + if (debug) + qDebug() << errorMessage; + QVERIFY(diagnostics.isEmpty()); +} + +void ClangStaticAnalyzerLogFileReaderTest::readFileWithNoDiagnostics() +{ + const QString filePath = testFilePath(QLatin1String("/data/noDiagnostics.plist")); + + QString errorMessage; + const QList<Diagnostic> diagnostics = LogFileReader::read(filePath, &errorMessage); + QVERIFY(errorMessage.isEmpty()); + QVERIFY(diagnostics.isEmpty()); +} + +void ClangStaticAnalyzerLogFileReaderTest::readFileWithDiagnostics() +{ + const QString filePath = testFilePath(QLatin1String("/data/someDiagnostics.plist")); + + QString errorMessage; + const QList<Diagnostic> diagnostics = LogFileReader::read(filePath, &errorMessage); + QVERIFY(errorMessage.isEmpty()); + QVERIFY(!diagnostics.isEmpty()); + + const QString commonPath = QLatin1String("../csatestproject/core.CallAndMessage3.cpp"); + + const Diagnostic d1 = diagnostics.first(); + if (debug) + qDebug() << d1; + QCOMPARE(d1.description, QLatin1String("Called function pointer is null (null dereference)")); + QCOMPARE(d1.category, QLatin1String("Logic error")); + QCOMPARE(d1.type, d1.description); + QCOMPARE(d1.issueContextKind, QLatin1String("function")); + QCOMPARE(d1.issueContext, QLatin1String("test")); + QCOMPARE(d1.location, DiagnosticLocation(commonPath, 36, 3)); + + QCOMPARE(d1.explainingSteps.size(), 2); + const ExplainingStep step1 = d1.explainingSteps.at(0); + QCOMPARE(step1.location, DiagnosticLocation(commonPath, 35, 3)); + QCOMPARE(step1.ranges.size(), 2); + QCOMPARE(step1.ranges.at(0), DiagnosticLocation(commonPath, 35, 3)); + QCOMPARE(step1.ranges.at(1), DiagnosticLocation(commonPath, 35, 9)); + QCOMPARE(step1.depth, 0); + QCOMPARE(step1.message, QLatin1String("Null pointer value stored to 'foo'")); + QCOMPARE(step1.extendedMessage, step1.message); + + const ExplainingStep step2 = d1.explainingSteps.at(1); + QCOMPARE(step2.location, DiagnosticLocation(commonPath, 36, 3)); + QCOMPARE(step2.ranges.size(), 2); + QCOMPARE(step2.ranges.at(0), DiagnosticLocation(commonPath, 36, 3)); + QCOMPARE(step2.ranges.at(1), DiagnosticLocation(commonPath, 36, 5)); + QCOMPARE(step2.depth, 0); + QCOMPARE(step2.message, QLatin1String("Called function pointer is null (null dereference)")); + QCOMPARE(step2.extendedMessage, step2.message); +} + +QTEST_MAIN(ClangStaticAnalyzerLogFileReaderTest) + +#include "tst_clangstaticanalyzerlogfilereader.moc" diff --git a/src/plugins/clangstaticanalyzer/tests/clangstaticanalyzerrunner/clangstaticanalyzerrunner.pro b/src/plugins/clangstaticanalyzer/tests/clangstaticanalyzerrunner/clangstaticanalyzerrunner.pro new file mode 100644 index 0000000000000000000000000000000000000000..b19bae409288cb7c7411a46188508584f87a114b --- /dev/null +++ b/src/plugins/clangstaticanalyzer/tests/clangstaticanalyzerrunner/clangstaticanalyzerrunner.pro @@ -0,0 +1,11 @@ +include(../tests.pri) + +TARGET = tst_clangstaticanalyzerrunnertest + +DEFINES += SRCDIR=\\\"$$PWD/\\\" + +SOURCES += \ + tst_clangstaticanalyzerrunner.cpp \ + $$PLUGINDIR/clangstaticanalyzerrunner.cpp +HEADERS += \ + $$PLUGINDIR/clangstaticanalyzerrunner.h diff --git a/src/plugins/clangstaticanalyzer/tests/clangstaticanalyzerrunner/clangstaticanalyzerrunner.qbs b/src/plugins/clangstaticanalyzer/tests/clangstaticanalyzerrunner/clangstaticanalyzerrunner.qbs new file mode 100644 index 0000000000000000000000000000000000000000..6b0f9da3f37097f9bf11ec32cf5c74cd54802668 --- /dev/null +++ b/src/plugins/clangstaticanalyzer/tests/clangstaticanalyzerrunner/clangstaticanalyzerrunner.qbs @@ -0,0 +1,19 @@ +import qbs +import "../clangstaticanalyzerautotest.qbs" as ClangStaticAnalyzerAutotest + +ClangStaticAnalyzerAutotest { + name: "ClangStaticAnalyzerRunner Autotest" + + Group { + name: "sources from plugin" + prefix: pluginDir + '/' + files: [ + "clangstaticanalyzerrunner.cpp", + "clangstaticanalyzerrunner.h", + ] + } + + files: [ + "tst_clangstaticanalyzerrunner.cpp", + ] +} diff --git a/src/plugins/clangstaticanalyzer/tests/clangstaticanalyzerrunner/tst_clangstaticanalyzerrunner.cpp b/src/plugins/clangstaticanalyzer/tests/clangstaticanalyzerrunner/tst_clangstaticanalyzerrunner.cpp new file mode 100644 index 0000000000000000000000000000000000000000..712875c329168238189fd45e9653b99495371bbe --- /dev/null +++ b/src/plugins/clangstaticanalyzer/tests/clangstaticanalyzerrunner/tst_clangstaticanalyzerrunner.cpp @@ -0,0 +1,176 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include <clangstaticanalyzer/clangstaticanalyzerconstants.h> +#include <clangstaticanalyzer/clangstaticanalyzerrunner.h> + +#include <QtTest> + +using namespace ClangStaticAnalyzer::Internal; + +class ClangStaticAnalyzerRunnerTest : public QObject +{ + Q_OBJECT + +public: + ClangStaticAnalyzerRunnerTest() {} + virtual ~ClangStaticAnalyzerRunnerTest() {} + +private slots: + void runWithTestCodeGeneratedOneIssue(); + void runWithNonExistentFileToAnalyze(); +}; + +static bool writeFile(const QString &filePath, const QByteArray &source) +{ + Utils::FileSaver saver(filePath); + return saver.write(source) && saver.finalize(); +} + +class ClangStaticAnalyzerRunnerSignalTester +{ +public: + ClangStaticAnalyzerRunnerSignalTester(ClangStaticAnalyzerRunner *runner); + + bool expectStartedSignal(); + bool expectFinishWithSuccessSignal(); + bool expectFinishWithFailureSignal(const QString &expectedErrorMessage = QString()); + +private: + QSignalSpy m_spyStarted; + QSignalSpy m_spyFinishedWithFailure; + QSignalSpy m_spyFinishedWithSuccess; +}; + +ClangStaticAnalyzerRunnerSignalTester::ClangStaticAnalyzerRunnerSignalTester( + ClangStaticAnalyzerRunner *runner) + : m_spyStarted(runner, SIGNAL(started())) + , m_spyFinishedWithFailure(runner, SIGNAL(finishedWithFailure(QString,QString))) + , m_spyFinishedWithSuccess(runner, SIGNAL(finishedWithSuccess(QString))) +{ +} + +bool ClangStaticAnalyzerRunnerSignalTester::expectStartedSignal() +{ + if (m_spyStarted.wait()) { + if (m_spyStarted.size() != 1) { + qDebug() << "started() emitted more than once."; + return false; + } + } else { + qDebug() << "started() not emitted."; + return false; + } + + return true; +} + +bool ClangStaticAnalyzerRunnerSignalTester::expectFinishWithSuccessSignal() +{ + if (m_spyFinishedWithSuccess.wait()) { + if (m_spyFinishedWithSuccess.size() != 1) { + qDebug() << "finishedWithSuccess() emitted more than once."; + return false; + } + } else { + qDebug() << "finishedWithSuccess() not emitted."; + return false; + } + + if (m_spyFinishedWithFailure.size() != 0) { + qDebug() << "finishedWithFailure() emitted."; + return false; + } + + return true; +} + +bool ClangStaticAnalyzerRunnerSignalTester::expectFinishWithFailureSignal( + const QString &expectedErrorMessage) +{ + if (m_spyFinishedWithFailure.wait()) { + if (m_spyFinishedWithFailure.size() == 1) { + const QList<QVariant> args = m_spyFinishedWithFailure.at(0); + const QString errorMessage = args.at(0).toString(); + if (errorMessage == expectedErrorMessage) { + if (m_spyFinishedWithSuccess.size() != 0) { + qDebug() << "Got expected error, but finishedWithSuccess() was also emitted."; + return false; + } + return true; + } else { + qDebug() << "Actual error message:" << errorMessage; + qDebug() << "Expected error message:" << expectedErrorMessage; + return false; + } + } else { + qDebug() << "Finished with more than one failure (expected only one)."; + return false; + } + } else { + qDebug() << "Not finished with failure, but expected."; + return false; + } +} + +void ClangStaticAnalyzerRunnerTest::runWithTestCodeGeneratedOneIssue() +{ + const QString testFilePath = QDir::tempPath() + QLatin1String("/testcode.cpp"); + const QByteArray source = + "void f(int *p) {}\n" + "void f2(int *p) {\n" + " delete p;\n" + " f(p); // warn: use after free\n" + "}\n"; + QVERIFY(writeFile(testFilePath, source)); + + QTemporaryDir temporaryDir(QDir::tempPath() + QLatin1String("/qtc-clangstaticanalyzer-XXXXXX")); + QVERIFY(temporaryDir.isValid()); + ClangStaticAnalyzerRunner runner(QLatin1String("clang"), temporaryDir.path(), + Utils::Environment::systemEnvironment()); + + ClangStaticAnalyzerRunnerSignalTester st(&runner); + QVERIFY(runner.run(testFilePath)); + QVERIFY(st.expectStartedSignal()); + QVERIFY(st.expectFinishWithSuccessSignal()); +} + +void ClangStaticAnalyzerRunnerTest::runWithNonExistentFileToAnalyze() +{ + QTemporaryDir temporaryDir(QDir::tempPath() + QLatin1String("/qtc-clangstaticanalyzer-XXXXXX")); + QVERIFY(temporaryDir.isValid()); + ClangStaticAnalyzerRunner runner(QLatin1String("clang"), temporaryDir.path(), + Utils::Environment::systemEnvironment()); + + ClangStaticAnalyzerRunnerSignalTester st(&runner); + QVERIFY(runner.run(QLatin1String("not.existing.file.111"))); + + QVERIFY(st.expectStartedSignal()); + QVERIFY(st.expectFinishWithFailureSignal(finishedWithBadExitCode(1))); +} + +QTEST_MAIN(ClangStaticAnalyzerRunnerTest) + +#include "tst_clangstaticanalyzerrunner.moc" diff --git a/src/plugins/clangstaticanalyzer/tests/tests.pri b/src/plugins/clangstaticanalyzer/tests/tests.pri new file mode 100644 index 0000000000000000000000000000000000000000..8975fc8448df84e945c9c486947bef50e23e0489 --- /dev/null +++ b/src/plugins/clangstaticanalyzer/tests/tests.pri @@ -0,0 +1,22 @@ +QTC_LIB_DEPENDS += utils +QTC_PLUGIN_DEPENDS += analyzerbase + +isEmpty(IDE_SOURCE_TREE): IDE_SOURCE_TREE=$$(QTC_SOURCE) +isEmpty(IDE_BUILD_TREE): IDE_BUILD_TREE=$$(QTC_BUILD) +isEmpty(IDE_SOURCE_TREE): error(Set QTC_SOURCE environment variable) +isEmpty(IDE_BUILD_TREE): error(Set QTC_BUILD environment variable) + +PLUGINDIR = $$PWD/.. +INCLUDEPATH += $$PLUGINDIR/.. + +include($$IDE_SOURCE_TREE/qtcreator.pri) +include($$IDE_SOURCE_TREE/tests/auto/qttestrpath.pri) + +QT += testlib +QT -= gui + +CONFIG += console +CONFIG += testcase +CONFIG -= app_bundle + +TEMPLATE = app diff --git a/src/plugins/clangstaticanalyzer/tests/tests.pro b/src/plugins/clangstaticanalyzer/tests/tests.pro new file mode 100644 index 0000000000000000000000000000000000000000..72e7c586e9dc16e5c936de0d9440679ddb717c5b --- /dev/null +++ b/src/plugins/clangstaticanalyzer/tests/tests.pro @@ -0,0 +1,6 @@ +TEMPLATE = subdirs +CONFIG += ordered + +SUBDIRS = \ + clangstaticanalyzerrunner \ + clangstaticanalyzerlogfilereader diff --git a/src/plugins/clangstaticanalyzer/tests/tests.qbs b/src/plugins/clangstaticanalyzer/tests/tests.qbs new file mode 100644 index 0000000000000000000000000000000000000000..a6dc94d66cf09a4cfadbc4c9a34e5313eb398296 --- /dev/null +++ b/src/plugins/clangstaticanalyzer/tests/tests.qbs @@ -0,0 +1,8 @@ +import qbs + +Project { + references: [ + "clangstaticanalyzerlogfilereader", + "clangstaticanalyzerrunner", + ] +} diff --git a/src/plugins/clangstaticanalyzer/unit-tests/qt-widgets-app/main.cpp b/src/plugins/clangstaticanalyzer/unit-tests/qt-widgets-app/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b48f94ec827033ef073fb3c7f060837e90b935ec --- /dev/null +++ b/src/plugins/clangstaticanalyzer/unit-tests/qt-widgets-app/main.cpp @@ -0,0 +1,11 @@ +#include "mainwindow.h" +#include <QApplication> + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + + return a.exec(); +} diff --git a/src/plugins/clangstaticanalyzer/unit-tests/qt-widgets-app/mainwindow.cpp b/src/plugins/clangstaticanalyzer/unit-tests/qt-widgets-app/mainwindow.cpp new file mode 100644 index 0000000000000000000000000000000000000000..49d64fce7cedf4ed8c5e0124cfe43e90c23c6ab6 --- /dev/null +++ b/src/plugins/clangstaticanalyzer/unit-tests/qt-widgets-app/mainwindow.cpp @@ -0,0 +1,14 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow) +{ + ui->setupUi(this); +} + +MainWindow::~MainWindow() +{ + delete ui; +} diff --git a/src/plugins/clangstaticanalyzer/unit-tests/qt-widgets-app/mainwindow.h b/src/plugins/clangstaticanalyzer/unit-tests/qt-widgets-app/mainwindow.h new file mode 100644 index 0000000000000000000000000000000000000000..ce76956bad998e8efedb3b6f7ac77c4da8551018 --- /dev/null +++ b/src/plugins/clangstaticanalyzer/unit-tests/qt-widgets-app/mainwindow.h @@ -0,0 +1,24 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include <QMainWindow> + +QT_BEGIN_NAMESPACE +namespace Ui { +class MainWindow; +} +QT_END_NAMESPACE + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); + +private: + Ui::MainWindow *ui; +}; + +#endif // MAINWINDOW_H diff --git a/src/plugins/clangstaticanalyzer/unit-tests/qt-widgets-app/mainwindow.ui b/src/plugins/clangstaticanalyzer/unit-tests/qt-widgets-app/mainwindow.ui new file mode 100644 index 0000000000000000000000000000000000000000..6050363fa71ed2da04105077f9fef06150d05ee2 --- /dev/null +++ b/src/plugins/clangstaticanalyzer/unit-tests/qt-widgets-app/mainwindow.ui @@ -0,0 +1,24 @@ +<ui version="4.0"> + <class>MainWindow</class> + <widget class="QMainWindow" name="MainWindow" > + <property name="geometry" > + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>300</height> + </rect> + </property> + <property name="windowTitle" > + <string>MainWindow</string> + </property> + <widget class="QMenuBar" name="menuBar" /> + <widget class="QToolBar" name="mainToolBar" /> + <widget class="QWidget" name="centralWidget" /> + <widget class="QStatusBar" name="statusBar" /> + </widget> + <layoutDefault spacing="6" margin="11" /> + <pixmapfunction></pixmapfunction> + <resources/> + <connections/> +</ui> diff --git a/src/plugins/clangstaticanalyzer/unit-tests/qt-widgets-app/qt-widgets-app.pro b/src/plugins/clangstaticanalyzer/unit-tests/qt-widgets-app/qt-widgets-app.pro new file mode 100644 index 0000000000000000000000000000000000000000..d9e1f2590b0494c27bc561d9b8ec8a6d8a68c36d --- /dev/null +++ b/src/plugins/clangstaticanalyzer/unit-tests/qt-widgets-app/qt-widgets-app.pro @@ -0,0 +1,8 @@ +QT += core gui +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +TARGET = qt-widgets-app +TEMPLATE = app +SOURCES += main.cpp mainwindow.cpp +HEADERS += mainwindow.h +FORMS += mainwindow.ui diff --git a/src/plugins/clangstaticanalyzer/unit-tests/qt-widgets-app/qt-widgets-app.qbs b/src/plugins/clangstaticanalyzer/unit-tests/qt-widgets-app/qt-widgets-app.qbs new file mode 100644 index 0000000000000000000000000000000000000000..e641f9bd54dfb731df340ebca94795d8ba9345df --- /dev/null +++ b/src/plugins/clangstaticanalyzer/unit-tests/qt-widgets-app/qt-widgets-app.qbs @@ -0,0 +1,17 @@ +import qbs 1.0 + +QtApplication { + name : "Qt Widgets Application" + + Depends { + name: "Qt" + submodules: [ "widgets" ] + } + + files : [ + "main.cpp", + "mainwindow.cpp", + "mainwindow.h", + "mainwindow.ui" + ] +} diff --git a/src/plugins/clangstaticanalyzer/unit-tests/simple/main.cpp b/src/plugins/clangstaticanalyzer/unit-tests/simple/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..af917a3a3328bcec2869c195603a20f8b607ecca --- /dev/null +++ b/src/plugins/clangstaticanalyzer/unit-tests/simple/main.cpp @@ -0,0 +1,5 @@ +int main() +{ + int *i = 0; + *i = 42; +} diff --git a/src/plugins/clangstaticanalyzer/unit-tests/simple/simple.pro b/src/plugins/clangstaticanalyzer/unit-tests/simple/simple.pro new file mode 100644 index 0000000000000000000000000000000000000000..fb560c2af612cc4779c1aeba22a1a21ff8e99d20 --- /dev/null +++ b/src/plugins/clangstaticanalyzer/unit-tests/simple/simple.pro @@ -0,0 +1,3 @@ +CONFIG -= QT + +SOURCES = main.cpp diff --git a/src/plugins/clangstaticanalyzer/unit-tests/simple/simple.qbs b/src/plugins/clangstaticanalyzer/unit-tests/simple/simple.qbs new file mode 100644 index 0000000000000000000000000000000000000000..f6ae698a0cb6b887b931d41df7b7b12e8d4579f2 --- /dev/null +++ b/src/plugins/clangstaticanalyzer/unit-tests/simple/simple.qbs @@ -0,0 +1,5 @@ +import qbs + +CppApplication { + files: ["main.cpp"] +} diff --git a/src/plugins/plugins.pro b/src/plugins/plugins.pro index cf1904f263f0b1981e1ce0593aef85873a3dceb9..baa76e50d9045da703c83c4c37dc8afda5f1ae74 100644 --- a/src/plugins/plugins.pro +++ b/src/plugins/plugins.pro @@ -3,6 +3,7 @@ include(../../qtcreator.pri) TEMPLATE = subdirs SUBDIRS = \ + clangstaticanalyzer \ coreplugin \ texteditor \ cppeditor \ diff --git a/src/plugins/plugins.qbs b/src/plugins/plugins.qbs index 34d5510893bfff7efe0e96b9673dbd7060384965..6505ce0822ebf381a8e10d8e3ff7f315c96fac36 100644 --- a/src/plugins/plugins.qbs +++ b/src/plugins/plugins.qbs @@ -13,6 +13,7 @@ Project { "bineditor/bineditor.qbs", "bookmarks/bookmarks.qbs", "clangcodemodel/clangcodemodel.qbs", + "clangstaticanalyzer/clangstaticanalyzer.qbs", "classview/classview.qbs", "clearcase/clearcase.qbs", "cmakeprojectmanager/cmakeprojectmanager.qbs",