forked from qt-creator/qt-creator
ClangTools: make parsing diagnostics cancelable
Change-Id: Ia5b4bd6f5fbb9a81888b1eaf11b956617e4b740c Reviewed-by: Jarek Kobus <jaroslaw.kobus@qt.io>
This commit is contained in:
@@ -677,16 +677,6 @@ void ClangTool::startTool(ClangTool::FileSelection fileSelection,
|
||||
ProjectExplorerPlugin::startRunControl(m_runControl);
|
||||
}
|
||||
|
||||
Diagnostics ClangTool::read(const FilePath &logFilePath,
|
||||
const QSet<FilePath> &projectFiles,
|
||||
QString *errorMessage) const
|
||||
{
|
||||
const auto acceptFromFilePath = [projectFiles](const FilePath &filePath) {
|
||||
return projectFiles.contains(filePath);
|
||||
};
|
||||
return readExportedDiagnostics(logFilePath, acceptFromFilePath, errorMessage);
|
||||
}
|
||||
|
||||
FileInfos ClangTool::collectFileInfos(Project *project, FileSelection fileSelection)
|
||||
{
|
||||
FileSelectionType *selectionType = std::get_if<FileSelectionType>(&fileSelection);
|
||||
@@ -763,21 +753,17 @@ void ClangTool::loadDiagnosticsFromFiles()
|
||||
|
||||
// Load files
|
||||
Diagnostics diagnostics;
|
||||
QString errors;
|
||||
QStringList errors;
|
||||
for (const FilePath &filePath : filePaths) {
|
||||
QString currentError;
|
||||
diagnostics << readExportedDiagnostics(filePath, {}, ¤tError);
|
||||
|
||||
if (!currentError.isEmpty()) {
|
||||
if (!errors.isEmpty())
|
||||
errors.append("\n");
|
||||
errors.append(currentError);
|
||||
}
|
||||
if (expected_str<Diagnostics> expectedDiagnostics = readExportedDiagnostics(filePath))
|
||||
diagnostics << *expectedDiagnostics;
|
||||
else
|
||||
errors.append(expectedDiagnostics.error());
|
||||
}
|
||||
|
||||
// Show errors
|
||||
if (!errors.isEmpty()) {
|
||||
AsynchronousMessageBox::critical(Tr::tr("Error Loading Diagnostics"), errors);
|
||||
AsynchronousMessageBox::critical(Tr::tr("Error Loading Diagnostics"), errors.join('\n'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -65,10 +65,6 @@ public:
|
||||
const RunSettings &runSettings,
|
||||
const CppEditor::ClangDiagnosticConfig &diagnosticConfig);
|
||||
|
||||
Diagnostics read(const Utils::FilePath &logFilePath,
|
||||
const QSet<Utils::FilePath> &projectFiles,
|
||||
QString *errorMessage) const;
|
||||
|
||||
FileInfos collectFileInfos(ProjectExplorer::Project *project,
|
||||
FileSelection fileSelection);
|
||||
|
||||
|
@@ -114,7 +114,6 @@ GroupItem clangToolTask(const AnalyzeInputData &input,
|
||||
QString name;
|
||||
FilePath executable;
|
||||
FilePath outputFilePath;
|
||||
QString errorMessage;
|
||||
};
|
||||
const TreeStorage<ClangToolStorage> storage;
|
||||
|
||||
@@ -186,24 +185,36 @@ GroupItem clangToolTask(const AnalyzeInputData &input,
|
||||
{false, input.unit.file, data.outputFilePath, {}, input.tool, message, details});
|
||||
};
|
||||
|
||||
const auto onReadSetup = [=](Async<Diagnostics> &data) {
|
||||
data.setConcurrentCallData(&readExportedDiagnostics,
|
||||
const auto onReadSetup = [=](Async<expected_str<Diagnostics>> &data) {
|
||||
data.setConcurrentCallData(&parseDiagnostics,
|
||||
storage->outputFilePath,
|
||||
input.diagnosticsFilter,
|
||||
&storage->errorMessage);
|
||||
input.diagnosticsFilter);
|
||||
data.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
|
||||
};
|
||||
const auto onReadDone = [=](const Async<Diagnostics> &data) {
|
||||
const auto onReadDone = [=](const Async<expected_str<Diagnostics>> &data) {
|
||||
if (!outputHandler)
|
||||
return;
|
||||
outputHandler(
|
||||
{true, input.unit.file, storage->outputFilePath, data.result(), input.tool});
|
||||
const expected_str<Diagnostics> result = data.result();
|
||||
const bool success = result.has_value();
|
||||
Diagnostics diagnostics;
|
||||
QString error;
|
||||
if (success)
|
||||
diagnostics = *result;
|
||||
else
|
||||
error = result.error();
|
||||
outputHandler({success,
|
||||
input.unit.file,
|
||||
storage->outputFilePath,
|
||||
diagnostics,
|
||||
input.tool,
|
||||
error});
|
||||
};
|
||||
const auto onReadError = [=](const Async<Diagnostics> &) {
|
||||
const auto onReadError = [=](const Async<expected_str<Diagnostics>> &data) {
|
||||
if (!outputHandler)
|
||||
return;
|
||||
const expected_str<Diagnostics> result = data.result();
|
||||
outputHandler(
|
||||
{false, input.unit.file, storage->outputFilePath, {}, input.tool, storage->errorMessage});
|
||||
{false, input.unit.file, storage->outputFilePath, {}, input.tool, result.error()});
|
||||
};
|
||||
|
||||
const Group group {
|
||||
@@ -213,7 +224,7 @@ GroupItem clangToolTask(const AnalyzeInputData &input,
|
||||
sequential,
|
||||
finishAllAndDone,
|
||||
ProcessTask(onProcessSetup, onProcessDone, onProcessError),
|
||||
AsyncTask<Diagnostics>(onReadSetup, onReadDone, onReadError)
|
||||
AsyncTask<expected_str<Diagnostics>>(onReadSetup, onReadDone, onReadError)
|
||||
}
|
||||
};
|
||||
return group;
|
||||
|
@@ -11,26 +11,13 @@
|
||||
#include <utils/fileutils.h>
|
||||
#include <utils/textutils.h>
|
||||
|
||||
#include <QFuture>
|
||||
|
||||
#include <yaml-cpp/yaml.h>
|
||||
|
||||
namespace ClangTools {
|
||||
namespace Internal {
|
||||
|
||||
static bool checkFilePath(const Utils::FilePath &filePath, QString *errorMessage)
|
||||
{
|
||||
QFileInfo fi(filePath.toFileInfo());
|
||||
if (!fi.exists() || !fi.isReadable()) {
|
||||
if (errorMessage) {
|
||||
*errorMessage
|
||||
= QString(QT_TRANSLATE_NOOP("QtC::ClangTools",
|
||||
"File \"%1\" does not exist or is not readable."))
|
||||
.arg(filePath.toUserOutput());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<LineColumnInfo> byteOffsetInUtf8TextToLineColumn(const char *text,
|
||||
int offset,
|
||||
int startLine)
|
||||
@@ -190,19 +177,25 @@ private:
|
||||
|
||||
} // namespace
|
||||
|
||||
Diagnostics readExportedDiagnostics(const Utils::FilePath &logFilePath,
|
||||
const AcceptDiagsFromFilePath &acceptFromFilePath,
|
||||
QString *errorMessage)
|
||||
void parseDiagnostics(QPromise<Utils::expected_str<Diagnostics>> &promise,
|
||||
const Utils::FilePath &logFilePath,
|
||||
const AcceptDiagsFromFilePath &acceptFromFilePath)
|
||||
{
|
||||
if (!checkFilePath(logFilePath, errorMessage))
|
||||
return {};
|
||||
const Utils::expected_str<QByteArray> localFileContents = logFilePath.fileContents();
|
||||
if (!localFileContents.has_value()) {
|
||||
promise.addResult(Utils::make_unexpected(localFileContents.error()));
|
||||
promise.future().cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
FileCache fileCache;
|
||||
Diagnostics diagnostics;
|
||||
|
||||
try {
|
||||
YAML::Node document = YAML::LoadFile(logFilePath.toString().toStdString());
|
||||
YAML::Node document = YAML::Load(*localFileContents);
|
||||
for (const auto &diagNode : document["Diagnostics"]) {
|
||||
if (promise.isCanceled())
|
||||
return;
|
||||
// Since llvm/clang 9.0 the diagnostic items are wrapped in a "DiagnosticMessage" node.
|
||||
const auto msgNode = diagNode["DiagnosticMessage"];
|
||||
const YAML::Node &node = msgNode ? msgNode : diagNode;
|
||||
@@ -252,16 +245,24 @@ Diagnostics readExportedDiagnostics(const Utils::FilePath &logFilePath,
|
||||
|
||||
diagnostics.append(diag);
|
||||
}
|
||||
promise.addResult(diagnostics);
|
||||
} catch (std::exception &e) {
|
||||
if (errorMessage) {
|
||||
*errorMessage = QString(
|
||||
QT_TRANSLATE_NOOP("QtC::ClangTools",
|
||||
const QString errorMessage
|
||||
= QString(QT_TRANSLATE_NOOP("QtC::ClangTools",
|
||||
"Error: Failed to parse YAML file \"%1\": %2."))
|
||||
.arg(logFilePath.toUserOutput(), QString::fromUtf8(e.what()));
|
||||
promise.addResult(Utils::make_unexpected(errorMessage));
|
||||
promise.future().cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return diagnostics;
|
||||
Utils::expected_str<Diagnostics> readExportedDiagnostics(
|
||||
const Utils::FilePath &logFilePath, const AcceptDiagsFromFilePath &acceptFromFilePath)
|
||||
{
|
||||
QPromise<Utils::expected_str<Diagnostics>> promise;
|
||||
promise.start();
|
||||
parseDiagnostics(promise, logFilePath, acceptFromFilePath);
|
||||
return promise.future().result();
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
|
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "clangtoolsdiagnostic.h"
|
||||
|
||||
#include <QPromise>
|
||||
#include <optional>
|
||||
|
||||
namespace Utils { class FilePath; }
|
||||
@@ -16,9 +17,13 @@ namespace Internal {
|
||||
using AcceptDiagsFromFilePath = std::function<bool(const Utils::FilePath &)>;
|
||||
|
||||
// Reads diagnostics generated by "clang-tidy/clazy-standalone -export-fixes=path/to/file"
|
||||
Diagnostics readExportedDiagnostics(const Utils::FilePath &logFilePath,
|
||||
const AcceptDiagsFromFilePath &acceptFromFilePath,
|
||||
QString *errorMessage = nullptr);
|
||||
void parseDiagnostics(QPromise<Utils::expected_str<Diagnostics>> &promise,
|
||||
const Utils::FilePath &logFilePath,
|
||||
const AcceptDiagsFromFilePath &acceptFromFilePath = {});
|
||||
|
||||
Utils::expected_str<Diagnostics> readExportedDiagnostics(
|
||||
const Utils::FilePath &logFilePath,
|
||||
const AcceptDiagsFromFilePath &acceptFromFilePath = {});
|
||||
|
||||
// Exposed for tests
|
||||
struct LineColumnInfo {
|
||||
|
@@ -31,41 +31,40 @@ ReadExportedDiagnosticsTest::ReadExportedDiagnosticsTest()
|
||||
ReadExportedDiagnosticsTest::~ReadExportedDiagnosticsTest() { delete m_baseDir; }
|
||||
|
||||
void ReadExportedDiagnosticsTest::initTestCase() { QVERIFY(m_baseDir->isValid()); }
|
||||
void ReadExportedDiagnosticsTest::init() { m_message.clear(); }
|
||||
void ReadExportedDiagnosticsTest::init() { }
|
||||
|
||||
void ReadExportedDiagnosticsTest::testNonExistingFile()
|
||||
{
|
||||
const Diagnostics diags = readExportedDiagnostics("nonExistingFile.yaml", {}, &m_message);
|
||||
QVERIFY(diags.isEmpty());
|
||||
QVERIFY(!m_message.isEmpty());
|
||||
const expected_str<Diagnostics> diags = readExportedDiagnostics("nonExistingFile.yaml");
|
||||
QVERIFY(!diags.has_value());
|
||||
QVERIFY(!diags.error().isEmpty());
|
||||
}
|
||||
|
||||
void ReadExportedDiagnosticsTest::testEmptyFile()
|
||||
{
|
||||
const Diagnostics diags = readExportedDiagnostics(filePath("empty.yaml"), {}, &m_message);
|
||||
QVERIFY(diags.isEmpty());
|
||||
QVERIFY2(m_message.isEmpty(), qPrintable(m_message));
|
||||
const expected_str<Diagnostics> diags = readExportedDiagnostics(filePath("empty.yaml"));
|
||||
QVERIFY(diags.has_value());
|
||||
QVERIFY(diags->isEmpty());
|
||||
}
|
||||
|
||||
void ReadExportedDiagnosticsTest::testUnexpectedFileContents()
|
||||
{
|
||||
const Diagnostics diags = readExportedDiagnostics(filePath("tidy.modernize-use-nullptr.cpp"),
|
||||
{}, &m_message);
|
||||
QVERIFY(!m_message.isEmpty());
|
||||
QVERIFY(diags.isEmpty());
|
||||
const expected_str<Diagnostics> diags = readExportedDiagnostics(
|
||||
filePath("tidy.modernize-use-nullptr.cpp"));
|
||||
QVERIFY(!diags.has_value());
|
||||
QVERIFY(!diags.error().isEmpty());
|
||||
}
|
||||
|
||||
static QString appendYamlSuffix(const char *filePathFragment)
|
||||
{
|
||||
const QString yamlSuffix = QLatin1String(Utils::HostOsInfo::isWindowsHost()
|
||||
? "_win.yaml" : ".yaml");
|
||||
const QString yamlSuffix = QLatin1String(HostOsInfo::isWindowsHost() ? "_win.yaml" : ".yaml");
|
||||
return filePathFragment + yamlSuffix;
|
||||
}
|
||||
|
||||
void ReadExportedDiagnosticsTest::testTidy()
|
||||
{
|
||||
const FilePath sourceFile = filePath("tidy.modernize-use-nullptr.cpp");
|
||||
const QString exportedFile = createFile(
|
||||
const FilePath exportedFile = createFile(
|
||||
filePath(appendYamlSuffix("tidy.modernize-use-nullptr")).toString(),
|
||||
sourceFile.toString());
|
||||
Diagnostic expectedDiag;
|
||||
@@ -79,31 +78,30 @@ void ReadExportedDiagnosticsTest::testTidy()
|
||||
expectedDiag.location,
|
||||
{expectedDiag.location, {sourceFile, 2, 26}},
|
||||
true}};
|
||||
const Diagnostics diags = readExportedDiagnostics(Utils::FilePath::fromString(exportedFile),
|
||||
{}, &m_message);
|
||||
const expected_str<Diagnostics> diags = readExportedDiagnostics(exportedFile);
|
||||
|
||||
QVERIFY2(m_message.isEmpty(), qPrintable(m_message));
|
||||
QCOMPARE(diags, {expectedDiag});
|
||||
QVERIFY(diags.has_value());
|
||||
QCOMPARE(*diags, {expectedDiag});
|
||||
}
|
||||
|
||||
void ReadExportedDiagnosticsTest::testAcceptDiagsFromFilePaths_None()
|
||||
{
|
||||
const QString sourceFile = filePath("tidy.modernize-use-nullptr.cpp").toString();
|
||||
const QString exportedFile = createFile(filePath("tidy.modernize-use-nullptr.yaml").toString(),
|
||||
const FilePath exportedFile = createFile(filePath("tidy.modernize-use-nullptr.yaml").toString(),
|
||||
sourceFile);
|
||||
const auto acceptNone = [](const Utils::FilePath &) { return false; };
|
||||
const Diagnostics diags = readExportedDiagnostics(FilePath::fromString(exportedFile),
|
||||
acceptNone, &m_message);
|
||||
QVERIFY2(m_message.isEmpty(), qPrintable(m_message));
|
||||
QVERIFY(diags.isEmpty());
|
||||
const auto acceptNone = [](const FilePath &) { return false; };
|
||||
const expected_str<Diagnostics> diags
|
||||
= readExportedDiagnostics(exportedFile, acceptNone);
|
||||
QVERIFY(diags.has_value());
|
||||
QVERIFY(diags->isEmpty());
|
||||
}
|
||||
|
||||
// Diagnostics from clang (static) analyzer passed through via clang-tidy
|
||||
void ReadExportedDiagnosticsTest::testTidy_ClangAnalyzer()
|
||||
{
|
||||
const FilePath sourceFile = filePath("clang-analyzer.dividezero.cpp");
|
||||
const QString exportedFile = createFile(
|
||||
filePath(appendYamlSuffix("clang-analyzer.dividezero")).toString(),
|
||||
const FilePath exportedFile
|
||||
= createFile(filePath(appendYamlSuffix("clang-analyzer.dividezero")).toString(),
|
||||
sourceFile.toString());
|
||||
Diagnostic expectedDiag;
|
||||
expectedDiag.name = "clang-analyzer-core.DivideZero";
|
||||
@@ -128,16 +126,15 @@ void ReadExportedDiagnosticsTest::testTidy_ClangAnalyzer()
|
||||
false,
|
||||
},
|
||||
};
|
||||
const Diagnostics diags = readExportedDiagnostics(Utils::FilePath::fromString(exportedFile),
|
||||
{}, &m_message);
|
||||
QVERIFY2(m_message.isEmpty(), qPrintable(m_message));
|
||||
QCOMPARE(diags, {expectedDiag});
|
||||
const expected_str<Diagnostics> diags = readExportedDiagnostics(exportedFile);
|
||||
QVERIFY(diags.has_value());
|
||||
QCOMPARE(*diags, {expectedDiag});
|
||||
}
|
||||
|
||||
void ReadExportedDiagnosticsTest::testClazy()
|
||||
{
|
||||
const FilePath sourceFile = filePath("clazy.qgetenv.cpp");
|
||||
const QString exportedFile = createFile(filePath(appendYamlSuffix("clazy.qgetenv")).toString(),
|
||||
const FilePath exportedFile = createFile(filePath(appendYamlSuffix("clazy.qgetenv")).toString(),
|
||||
sourceFile.toString());
|
||||
Diagnostic expectedDiag;
|
||||
expectedDiag.name = "clazy-qgetenv";
|
||||
@@ -156,10 +153,9 @@ void ReadExportedDiagnosticsTest::testClazy()
|
||||
{{sourceFile, 7, 18}, {sourceFile, 7, 29}},
|
||||
true},
|
||||
};
|
||||
const Diagnostics diags = readExportedDiagnostics(Utils::FilePath::fromString(exportedFile),
|
||||
{}, &m_message);
|
||||
QVERIFY2(m_message.isEmpty(), qPrintable(m_message));
|
||||
QCOMPARE(diags, {expectedDiag});
|
||||
const expected_str<Diagnostics> diags = readExportedDiagnostics(exportedFile);
|
||||
QVERIFY(diags.has_value());
|
||||
QCOMPARE(*diags, {expectedDiag});
|
||||
}
|
||||
|
||||
void ReadExportedDiagnosticsTest::testOffsetInvalidText()
|
||||
@@ -263,25 +259,24 @@ void ReadExportedDiagnosticsTest::testOffsetMultiByteCodePoint2()
|
||||
}
|
||||
|
||||
// Replace FILE_PATH with a real absolute file path in the *.yaml files.
|
||||
QString ReadExportedDiagnosticsTest::createFile(const QString &yamlFilePath,
|
||||
FilePath ReadExportedDiagnosticsTest::createFile(const QString &yamlFilePath,
|
||||
const QString &filePathToInject) const
|
||||
{
|
||||
QTC_ASSERT(QDir::isAbsolutePath(filePathToInject), return QString());
|
||||
const Utils::FilePath newFileName = m_baseDir->absolutePath(
|
||||
QFileInfo(yamlFilePath).fileName());
|
||||
QTC_ASSERT(QDir::isAbsolutePath(filePathToInject), return {});
|
||||
const FilePath newFileName = m_baseDir->absolutePath(QFileInfo(yamlFilePath).fileName());
|
||||
|
||||
Utils::FileReader reader;
|
||||
if (QTC_GUARD(reader.fetch(Utils::FilePath::fromString(yamlFilePath),
|
||||
FileReader reader;
|
||||
if (QTC_GUARD(reader.fetch(FilePath::fromString(yamlFilePath),
|
||||
QIODevice::ReadOnly | QIODevice::Text))) {
|
||||
QByteArray contents = reader.data();
|
||||
contents.replace("FILE_PATH", filePathToInject.toLocal8Bit());
|
||||
|
||||
Utils::FileSaver fileSaver(newFileName, QIODevice::WriteOnly | QIODevice::Text);
|
||||
FileSaver fileSaver(newFileName, QIODevice::WriteOnly | QIODevice::Text);
|
||||
QTC_CHECK(fileSaver.write(contents));
|
||||
QTC_CHECK(fileSaver.finalize());
|
||||
}
|
||||
|
||||
return newFileName.toString();
|
||||
return newFileName;
|
||||
}
|
||||
|
||||
FilePath ReadExportedDiagnosticsTest::filePath(const QString &fileName) const
|
||||
|
@@ -44,11 +44,10 @@ private slots:
|
||||
void testOffsetMultiByteCodePoint2();
|
||||
|
||||
private:
|
||||
QString createFile(const QString &yamlFilePath, const QString &filePathToInject) const;
|
||||
Utils::FilePath createFile(const QString &yamlFilePath, const QString &filePathToInject) const;
|
||||
Utils::FilePath filePath(const QString &fileName) const;
|
||||
|
||||
CppEditor::Tests::TemporaryCopiedDir * const m_baseDir;
|
||||
QString m_message;
|
||||
};
|
||||
|
||||
} // namespace ClangTools::Internal
|
||||
|
Reference in New Issue
Block a user