Commit 4c1a5182 authored by Thomas Schöps's avatar Thomas Schöps

Clang Plugin: Report some problems from included files

Differential Revision: https://phabricator.kde.org/D18224
parent 84bd62b6
......@@ -96,6 +96,28 @@ QDebug operator<<(QDebug debug, const ClangFixit& fixit)
return debug;
}
ClangProblem::ClangProblem() = default;
ClangProblem::ClangProblem(const ClangProblem& other)
: Problem(),
m_fixits(other.m_fixits)
{
setSource(other.source());
setFinalLocation(other.finalLocation());
setFinalLocationMode(other.finalLocationMode());
setDescription(other.description());
setExplanation(other.explanation());
setSeverity(other.severity());
auto diagnostics = other.diagnostics();
for (auto& diagnostic : diagnostics) {
auto* clangDiagnostic = dynamic_cast<ClangProblem*>(diagnostic.data());
Q_ASSERT(clangDiagnostic);
diagnostic = ClangProblem::Ptr(new ClangProblem(*clangDiagnostic));
}
setDiagnostics(diagnostics);
}
ClangProblem::ClangProblem(CXDiagnostic diagnostic, CXTranslationUnit unit)
{
const QString diagnosticOption = ClangString(clang_getDiagnosticOption(diagnostic, nullptr)).toString();
......
......@@ -71,6 +71,16 @@ public:
using Ptr = QExplicitlySharedDataPointer<ClangProblem>;
using ConstPtr = QExplicitlySharedDataPointer<const ClangProblem>;
/**
* Creates an empty ClangProblem.
*/
ClangProblem();
/**
* Creates a deep copy of a ClangProblem.
*/
ClangProblem(const ClangProblem& other);
/**
* Import @p diagnostic into a ClangProblem object
*
......
......@@ -23,7 +23,6 @@
#include "parsesession.h"
#include <QStandardPaths>
#include "clangproblem.h"
#include "clangdiagnosticevaluator.h"
#include "todoextractor.h"
#include "clanghelpers.h"
......@@ -432,6 +431,77 @@ IndexedString ParseSession::languageString()
return lang;
}
ClangProblem::Ptr ParseSession::getOrCreateProblem(int indexInTU, CXDiagnostic diagnostic) const
{
auto& problem = d->m_diagnosticsCache[indexInTU];
if (!problem) {
problem = ClangDiagnosticEvaluator::createProblem(diagnostic, d->m_unit);
}
return problem;
}
ClangProblem::Ptr ParseSession::createExternalProblem(int indexInTU,
CXDiagnostic diagnostic,
const KLocalizedString& descriptionTemplate,
int childProblemFinalLocationIndex) const
{
// Make a copy of the original (cached) problem since it is modified later
auto problem = ClangProblem::Ptr(new ClangProblem(*getOrCreateProblem(indexInTU, diagnostic)));
// Insert a copy of the parent problem (without child problems) as the first
// child problem to preserve its location.
auto* problemCopy = new ClangProblem();
problemCopy->setSource(problem->source());
problemCopy->setFinalLocation(problem->finalLocation());
problemCopy->setFinalLocationMode(problem->finalLocationMode());
problemCopy->setDescription(problem->description());
problemCopy->setExplanation(problem->explanation());
problemCopy->setSeverity(problem->severity());
auto childProblems = problem->diagnostics();
childProblems.prepend(IProblem::Ptr(problemCopy));
problem->setDiagnostics(childProblems);
// Override the problem's finalLocation with that of the child problem in this document.
// This is required to make the problem show up in the problem reporter for this
// file, since it filters by finalLocation. It will also lead the user to the correct
// location when clicking the problem and cause proper error highlighting.
int index = (childProblemFinalLocationIndex >= 0) ?
(1 + childProblemFinalLocationIndex) :
(childProblems.size() - 1);
problem->setFinalLocation(childProblems[index]->finalLocation());
problem->setDescription(descriptionTemplate.subs(problem->description()).toString());
return problem;
}
QList<ClangProblem::Ptr> ParseSession::createRequestedHereProblems(int indexInTU, CXDiagnostic diagnostic, CXFile file) const
{
QList<ClangProblem::Ptr> results;
auto childDiagnostics = clang_getChildDiagnostics(diagnostic);
auto numChildDiagnostics = clang_getNumDiagnosticsInSet(childDiagnostics);
for (uint j = 0; j < numChildDiagnostics; ++j) {
auto childDiagnostic = clang_getDiagnosticInSet(childDiagnostics, j);
CXSourceLocation childLocation = clang_getDiagnosticLocation(childDiagnostic);
CXFile childDiagnosticFile;
clang_getFileLocation(childLocation, &childDiagnosticFile, nullptr, nullptr, nullptr);
if (childDiagnosticFile == file) {
QString description = ClangString(clang_getDiagnosticSpelling(childDiagnostic)).toString();
if (description.endsWith(QLatin1String("requested here"))) {
// Note: Using the index j here assumes a 1:1 mapping from clang child diagnostics to KDevelop
// problem diagnostics (i.e., child problems). If we wanted to avoid making this assumption, we'd have
// to use ClangDiagnosticEvaluator::createProblem() first and then search within its
// child problems to find the correct index.
results << createExternalProblem(indexInTU, diagnostic, ki18n("Requested here: %1"), j);
}
}
}
return results;
}
QList<ProblemPointer> ParseSession::problemsForFile(CXFile file) const
{
if (!d) {
......@@ -451,18 +521,21 @@ QList<ProblemPointer> ParseSession::problemsForFile(CXFile file) const
CXSourceLocation location = clang_getDiagnosticLocation(diagnostic);
CXFile diagnosticFile;
clang_getFileLocation(location, &diagnosticFile, nullptr, nullptr, nullptr);
auto requestedHereProblems = createRequestedHereProblems(i, diagnostic, file);
for (const auto& ptr : requestedHereProblems) {
problems.append(static_cast<const ProblemPointer&>(ptr));
}
// missing-include problems are so severe in clang that we always propagate
// them to this document, to ensure that the user will see the error.
if (diagnosticFile != file && ClangDiagnosticEvaluator::diagnosticType(diagnostic) != ClangDiagnosticEvaluator::IncludeFileNotFoundProblem) {
continue;
}
auto& problem = d->m_diagnosticsCache[i];
if (!problem) {
problem = ClangDiagnosticEvaluator::createProblem(diagnostic, d->m_unit);
}
problems << problem;
problems << ((diagnosticFile == file) ?
getOrCreateProblem(i, diagnostic) :
createExternalProblem(i, diagnostic, ki18n("In included file: %1")));
clang_disposeDiagnostic(diagnostic);
}
......
......@@ -36,7 +36,7 @@
#include <language/interfaces/iastcontainer.h>
#include "clangprivateexport.h"
#include "clangproblem.h"
#include "clangparsingenvironment.h"
#include "unsavedfile.h"
......@@ -80,7 +80,7 @@ private:
/// best would be a PCH, if possible
QTemporaryFile m_definesFile;
// cached ProblemPointer representation for diagnostics
QVector<KDevelop::ProblemPointer> m_diagnosticsCache;
QVector<ClangProblem::Ptr> m_diagnosticsCache;
};
/**
......@@ -133,6 +133,15 @@ public:
private:
Q_DISABLE_COPY(ParseSession)
ClangProblem::Ptr getOrCreateProblem(int indexInTU, CXDiagnostic diagnostic) const;
ClangProblem::Ptr createExternalProblem(int indexInTU,
CXDiagnostic diagnostic,
const KLocalizedString& descriptionTemplate,
int childProblemFinalLocationIndex = -1) const;
QList<ClangProblem::Ptr> createRequestedHereProblems(int indexInTU, CXDiagnostic diagnostic, CXFile file) const;
ParseSessionData::Ptr d;
};
......
......@@ -1902,6 +1902,129 @@ void TestDUChain::testVariadicTemplateArguments()
}
}
void TestDUChain::testProblemRequestedHere()
{
auto headerCode = QStringLiteral(R"(
#pragma once
template <typename T>
T AddObjects(const T& a, const T& b)
{
return a + b;
}
)");
TestFile header(headerCode, QStringLiteral("h"));
QString sourceCode = QStringLiteral(R"(
#include "%1"
struct A {};
int main()
{
A a, b;
AddObjects(a, b);
return 0;
}
)").arg(header.url().str());
TestFile impl(sourceCode, QStringLiteral("cpp"), &header);
QVERIFY(impl.parseAndWait(TopDUContext::AllDeclarationsContextsAndUses));
DUChainReadLocker lock;
auto top = impl.topContext();
QVERIFY(top);
auto* headerCtx = dynamic_cast<TopDUContext*>(top->importedParentContexts().first().context(top));
QVERIFY(headerCtx);
QCOMPARE(headerCtx->url(), header.url());
QCOMPARE(headerCtx->problems().count(), 1);
QCOMPARE(headerCtx->localDeclarations().count(), 1);
// Verify that the problem is reported for the source file.
QCOMPARE(top->problems().count(), 1);
QCOMPARE(top->localDeclarations().count(), 2);
}
void TestDUChain::testProblemRequestedHereSameFile()
{
auto sourceCode = QStringLiteral(R"(
struct A {};
template <typename T>
T AddObjects(const T& a, const T& b)
{
return a + b;
}
int main()
{
A a, b;
AddObjects(a, b);
return 0;
}
)");
TestFile impl(sourceCode, QStringLiteral("cpp"));
QVERIFY(impl.parseAndWait(TopDUContext::AllDeclarationsContextsAndUses));
DUChainReadLocker lock;
auto top = impl.topContext();
QVERIFY(top);
QCOMPARE(top->problems().count(), 2);
}
void TestDUChain::testProblemRequestedHereChain()
{
auto headerCode = QStringLiteral(R"(
#pragma once
template <typename T>
T AddObjects(const T& a, const T& b)
{
return a + b;
}
)");
TestFile header(headerCode, QStringLiteral("h"));
QString sourceCode = QStringLiteral(R"(
#include "%1"
struct A {};
template <typename T>
T AddObjects2(const T& a, const T& b)
{
return AddObjects(a, b);
}
int main()
{
A a, b;
AddObjects2(a, b);
return 0;
}
)").arg(header.url().str());
TestFile impl(sourceCode, QStringLiteral("cpp"), &header);
QVERIFY(impl.parseAndWait(TopDUContext::AllDeclarationsContextsAndUses));
DUChainReadLocker lock;
auto top = impl.topContext();
QVERIFY(top);
auto* headerCtx = dynamic_cast<TopDUContext*>(top->importedParentContexts().first().context(top));
QVERIFY(headerCtx);
QCOMPARE(headerCtx->url(), header.url());
QCOMPARE(headerCtx->problems().count(), 1);
QCOMPARE(headerCtx->localDeclarations().count(), 1);
// Verify that the problem is reported for the source file.
QCOMPARE(top->problems().count(), 2);
QCOMPARE(top->localDeclarations().count(), 3);
}
void TestDUChain::testGccCompatibility()
{
// TODO: make it easier to change the compiler provider for testing purposes
......
......@@ -97,6 +97,9 @@ private Q_SLOTS:
void testTemplateFunctionParameterName();
void testFriendDeclaration();
void testVariadicTemplateArguments();
void testProblemRequestedHere();
void testProblemRequestedHereSameFile();
void testProblemRequestedHereChain();
void testGccCompatibility();
void testQtIntegration();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment