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,11 @@ using Ptr = QExplicitlySharedDataPointer; using ConstPtr = QExplicitlySharedDataPointer; + /** + * Creates an empty ClangProblem. + */ + ClangProblem(); + /** * 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,8 @@ return debug; } +ClangProblem::ClangProblem() = default; + 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 @@ -79,8 +79,6 @@ /// TODO: share this file for all TUs that use the same defines (probably most in a project) /// best would be a PCH, if possible QTemporaryFile m_definesFile; - // cached ProblemPointer representation for diagnostics - QVector m_diagnosticsCache; }; /** 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 @@ -202,6 +202,65 @@ }) != includePaths.end(); } +ProblemPointer createExternalProblem(CXDiagnostic diagnostic, CXTranslationUnit unit, + const KLocalizedString& descriptionTemplate, + int childProblemFinalLocationIndex = -1) { + auto problem = ClangDiagnosticEvaluator::createProblem(diagnostic, unit); + + // 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 ProblemPointer(problem); +} + +QList createRequestedHereProblems(CXDiagnostic diagnostic, CXTranslationUnit unit, CXFile file) +{ + 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(diagnostic, unit, ki18n("Requested here: %1"), j); + } + } + } + + return results; +} + } ParseSessionData::ParseSessionData(const QVector& unsavedFiles, ClangIndex* index, @@ -373,7 +432,6 @@ void ParseSessionData::setUnit(CXTranslationUnit unit) { m_unit = unit; - m_diagnosticsCache.clear(); if (m_unit) { const ClangString unitFile(clang_getTranslationUnitSpelling(unit)); m_file = clang_getFile(m_unit, unitFile.c_str()); @@ -443,7 +501,6 @@ // extra clang diagnostics const uint numDiagnostics = clang_getNumDiagnostics(d->m_unit); problems.reserve(numDiagnostics); - d->m_diagnosticsCache.resize(numDiagnostics); for (uint i = 0; i < numDiagnostics; ++i) { auto diagnostic = clang_getDiagnostic(d->m_unit, i); @@ -451,18 +508,18 @@ CXSourceLocation location = clang_getDiagnosticLocation(diagnostic); CXFile diagnosticFile; clang_getFileLocation(location, &diagnosticFile, nullptr, nullptr, nullptr); + + problems.append(createRequestedHereProblems(diagnostic, d->m_unit, file)); + // 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) ? + ProblemPointer(ClangDiagnosticEvaluator::createProblem(diagnostic, d->m_unit)) : + createExternalProblem(diagnostic, d->m_unit, 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 @@ -1930,6 +1930,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