diff --git a/plugins/clang/duchain/clangproblem.h b/plugins/clang/duchain/clangproblem.h --- a/plugins/clang/duchain/clangproblem.h +++ b/plugins/clang/duchain/clangproblem.h @@ -71,6 +71,16 @@ using Ptr = QExplicitlySharedDataPointer; using ConstPtr = QExplicitlySharedDataPointer; + /** + * Creates an empty ClangProblem. + */ + ClangProblem(); + + /** + * Creates a deep copy of a ClangProblem. + */ + ClangProblem(const ClangProblem& other); + /** * Import @p diagnostic into a ClangProblem object * diff --git a/plugins/clang/duchain/clangproblem.cpp b/plugins/clang/duchain/clangproblem.cpp --- a/plugins/clang/duchain/clangproblem.cpp +++ b/plugins/clang/duchain/clangproblem.cpp @@ -96,6 +96,28 @@ 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(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(); diff --git a/plugins/clang/duchain/parsesession.h b/plugins/clang/duchain/parsesession.h --- a/plugins/clang/duchain/parsesession.h +++ b/plugins/clang/duchain/parsesession.h @@ -36,7 +36,7 @@ #include #include "clangprivateexport.h" - +#include "clangproblem.h" #include "clangparsingenvironment.h" #include "unsavedfile.h" @@ -80,7 +80,7 @@ /// best would be a PCH, if possible QTemporaryFile m_definesFile; // cached ProblemPointer representation for diagnostics - QVector m_diagnosticsCache; + QVector m_diagnosticsCache; }; /** @@ -133,6 +133,15 @@ 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 createRequestedHereProblems(int indexInTU, CXDiagnostic diagnostic, CXFile file) const; + ParseSessionData::Ptr d; }; diff --git a/plugins/clang/duchain/parsesession.cpp b/plugins/clang/duchain/parsesession.cpp --- a/plugins/clang/duchain/parsesession.cpp +++ b/plugins/clang/duchain/parsesession.cpp @@ -23,7 +23,6 @@ #include "parsesession.h" #include -#include "clangproblem.h" #include "clangdiagnosticevaluator.h" #include "todoextractor.h" #include "clanghelpers.h" @@ -432,6 +431,77 @@ 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 ParseSession::createRequestedHereProblems(int indexInTU, CXDiagnostic diagnostic, CXFile file) const +{ + QList 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 ParseSession::problemsForFile(CXFile file) const { if (!d) { @@ -451,18 +521,21 @@ 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(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); } diff --git a/plugins/clang/tests/test_duchain.h b/plugins/clang/tests/test_duchain.h --- a/plugins/clang/tests/test_duchain.h +++ b/plugins/clang/tests/test_duchain.h @@ -97,6 +97,9 @@ void testTemplateFunctionParameterName(); void testFriendDeclaration(); void testVariadicTemplateArguments(); + void testProblemRequestedHere(); + void testProblemRequestedHereSameFile(); + void testProblemRequestedHereChain(); void testGccCompatibility(); void testQtIntegration(); diff --git a/plugins/clang/tests/test_duchain.cpp b/plugins/clang/tests/test_duchain.cpp --- a/plugins/clang/tests/test_duchain.cpp +++ b/plugins/clang/tests/test_duchain.cpp @@ -1902,6 +1902,129 @@ } } +void TestDUChain::testProblemRequestedHere() +{ + auto headerCode = QStringLiteral(R"( +#pragma once + +template +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(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 +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 +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 +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(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