diff options
Diffstat (limited to 'src/plugins/clangstaticanalyzer/clangstaticanalyzerruncontrol.cpp')
-rw-r--r-- | src/plugins/clangstaticanalyzer/clangstaticanalyzerruncontrol.cpp | 511 |
1 files changed, 511 insertions, 0 deletions
diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerruncontrol.cpp b/src/plugins/clangstaticanalyzer/clangstaticanalyzerruncontrol.cpp new file mode 100644 index 0000000000..35652728c4 --- /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 |