Changeset View
Standalone View
plugins/clang/duchain/parsesession.cpp
Show All 17 Lines | 1 | /* | |||
---|---|---|---|---|---|
18 | You should have received a copy of the GNU Library General Public License | 18 | You should have received a copy of the GNU Library General Public License | ||
19 | along with this library; see the file COPYING.LIB. If not, write to | 19 | along with this library; see the file COPYING.LIB. If not, write to | ||
20 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | 20 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||
21 | Boston, MA 02110-1301, USA. | 21 | Boston, MA 02110-1301, USA. | ||
22 | */ | 22 | */ | ||
23 | 23 | | |||
24 | #include "parsesession.h" | 24 | #include "parsesession.h" | ||
25 | #include <QStandardPaths> | 25 | #include <QStandardPaths> | ||
26 | #include "clangproblem.h" | | |||
27 | #include "clangdiagnosticevaluator.h" | 26 | #include "clangdiagnosticevaluator.h" | ||
28 | #include "todoextractor.h" | 27 | #include "todoextractor.h" | ||
29 | #include "clanghelpers.h" | 28 | #include "clanghelpers.h" | ||
30 | #include "clangindex.h" | 29 | #include "clangindex.h" | ||
31 | #include "clangparsingenvironment.h" | 30 | #include "clangparsingenvironment.h" | ||
32 | #include "util/clangdebug.h" | 31 | #include "util/clangdebug.h" | ||
33 | #include "util/clangtypes.h" | 32 | #include "util/clangtypes.h" | ||
34 | #include "util/clangutils.h" | 33 | #include "util/clangutils.h" | ||
▲ Show 20 Lines • Show All 163 Lines • ▼ Show 20 Line(s) | |||||
198 | bool hasQtIncludes(const Path::List& includePaths) | 197 | bool hasQtIncludes(const Path::List& includePaths) | ||
199 | { | 198 | { | ||
200 | return std::find_if(includePaths.begin(), includePaths.end(), [] (const Path& path) { | 199 | return std::find_if(includePaths.begin(), includePaths.end(), [] (const Path& path) { | ||
201 | return path.lastPathSegment() == QLatin1String("QtCore"); | 200 | return path.lastPathSegment() == QLatin1String("QtCore"); | ||
202 | }) != includePaths.end(); | 201 | }) != includePaths.end(); | ||
203 | } | 202 | } | ||
204 | 203 | | |||
205 | } | 204 | } | ||
206 | 205 | | |||
mwolff: make -> create | |||||
207 | ParseSessionData::ParseSessionData(const QVector<UnsavedFile>& unsavedFiles, ClangIndex* index, | 206 | ParseSessionData::ParseSessionData(const QVector<UnsavedFile>& unsavedFiles, ClangIndex* index, | ||
mwolff: pass a `KLocalizedString` here | |||||
208 | const ClangParsingEnvironment& environment, Options options) | 207 | const ClangParsingEnvironment& environment, Options options) | ||
209 | : m_file(nullptr) | 208 | : m_file(nullptr) | ||
210 | , m_unit(nullptr) | 209 | , m_unit(nullptr) | ||
211 | { | 210 | { | ||
212 | unsigned int flags = CXTranslationUnit_DetailedPreprocessingRecord | 211 | unsigned int flags = CXTranslationUnit_DetailedPreprocessingRecord | ||
213 | #if CINDEX_VERSION_MINOR >= 34 | 212 | #if CINDEX_VERSION_MINOR >= 34 | ||
mwolff: auto | |||||
214 | | CXTranslationUnit_KeepGoing | 213 | | CXTranslationUnit_KeepGoing | ||
215 | #endif | 214 | #endif | ||
216 | ; | 215 | ; | ||
OOC, does clang support translations of its messages? if so, would this check be problematic? pino: OOC, does clang support translations of its messages? if so, would this check be problematic? | |||||
aaronpuchert: [Not yet](http://clang.llvm.org/docs/InternalsManual.html#internals-diag-translation), but it… | |||||
217 | if (options.testFlag(SkipFunctionBodies)) { | 216 | if (options.testFlag(SkipFunctionBodies)) { | ||
218 | flags |= CXTranslationUnit_SkipFunctionBodies; | 217 | flags |= CXTranslationUnit_SkipFunctionBodies; | ||
219 | } | 218 | } | ||
220 | if (options.testFlag(PrecompiledHeader)) { | 219 | if (options.testFlag(PrecompiledHeader)) { | ||
221 | flags |= CXTranslationUnit_ForSerialization; | 220 | flags |= CXTranslationUnit_ForSerialization; | ||
222 | } else { | 221 | } else { | ||
223 | flags |= CXTranslationUnit_CacheCompletionResults | 222 | flags |= CXTranslationUnit_CacheCompletionResults | ||
224 | #if CINDEX_VERSION_MINOR >= 32 | 223 | #if CINDEX_VERSION_MINOR >= 32 | ||
225 | | CXTranslationUnit_CreatePreambleOnFirstParse | 224 | | CXTranslationUnit_CreatePreambleOnFirstParse | ||
226 | #endif | 225 | #endif | ||
227 | | CXTranslationUnit_PrecompiledPreamble; | 226 | | CXTranslationUnit_PrecompiledPreamble; | ||
228 | if (environment.quality() == ClangParsingEnvironment::Unknown) { | 227 | if (environment.quality() == ClangParsingEnvironment::Unknown) { | ||
229 | flags |= CXTranslationUnit_Incomplete; | 228 | flags |= CXTranslationUnit_Incomplete; | ||
230 | } | 229 | } | ||
231 | } | 230 | } | ||
232 | 231 | | |||
233 | const auto tuUrl = environment.translationUnitUrl(); | 232 | const auto tuUrl = environment.translationUnitUrl(); | ||
234 | Q_ASSERT(!tuUrl.isEmpty()); | 233 | Q_ASSERT(!tuUrl.isEmpty()); | ||
once using KLS, use descriptionTemplate.subs(problem->description()).toString() mwolff: once using KLS, use `descriptionTemplate.subs(problem->description()).toString()` | |||||
235 | 234 | | |||
236 | const auto arguments = argsForSession(tuUrl.str(), options, environment.parserSettings()); | 235 | const auto arguments = argsForSession(tuUrl.str(), options, environment.parserSettings()); | ||
237 | QVector<const char*> clangArguments; | 236 | QVector<const char*> clangArguments; | ||
238 | 237 | | |||
239 | const auto& includes = environment.includes(); | 238 | const auto& includes = environment.includes(); | ||
mwolff: make -> create | |||||
240 | const auto& pchInclude = environment.pchInclude(); | 239 | const auto& pchInclude = environment.pchInclude(); | ||
241 | 240 | | |||
242 | // uses QByteArray as smart-pointer for const char* ownership | 241 | // uses QByteArray as smart-pointer for const char* ownership | ||
243 | QVector<QByteArray> smartArgs; | 242 | QVector<QByteArray> smartArgs; | ||
aaronpuchert: Wouldn't it be easier to relax that filtering? | |||||
that's not so easy either, since it would require to recursively iterate through all imported contexts in the current file and then find ones that are associated with the current one. I.e. that would basically mean we always "show imported", which can become slow. So I would prefer to do it here, as suggested by this patch mwolff: that's not so easy either, since it would require to recursively iterate through all imported… | |||||
244 | smartArgs.reserve(includes.system.size() + includes.project.size() | 243 | smartArgs.reserve(includes.system.size() + includes.project.size() | ||
245 | + pchInclude.isValid() + arguments.size() + 1); | 244 | + pchInclude.isValid() + arguments.size() + 1); | ||
246 | clangArguments.reserve(smartArgs.size()); | 245 | clangArguments.reserve(smartArgs.size()); | ||
247 | 246 | | |||
248 | std::transform(arguments.constBegin(), arguments.constEnd(), | 247 | std::transform(arguments.constBegin(), arguments.constEnd(), | ||
249 | std::back_inserter(clangArguments), | 248 | std::back_inserter(clangArguments), | ||
250 | [] (const QByteArray &argument) { return argument.constData(); }); | 249 | [] (const QByteArray &argument) { return argument.constData(); }); | ||
251 | 250 | | |||
252 | // NOTE: the PCH include must come before all other includes! | 251 | // NOTE: the PCH include must come before all other includes! | ||
253 | if (pchInclude.isValid()) { | 252 | if (pchInclude.isValid()) { | ||
254 | clangArguments << "-include"; | 253 | clangArguments << "-include"; | ||
no string puzzles please: pino: no string puzzles please:
https://techbase.kde. | |||||
mwolff: can't we use the original description of the child diagnostic? | |||||
255 | QByteArray pchFile = pchInclude.toLocalFile().toUtf8(); | 254 | QByteArray pchFile = pchInclude.toLocalFile().toUtf8(); | ||
256 | smartArgs << pchFile; | 255 | smartArgs << pchFile; | ||
257 | clangArguments << pchFile.constData(); | 256 | clangArguments << pchFile.constData(); | ||
mwolff: once using KLS, use `ki18n("Requested here: %1")` here | |||||
258 | } | 257 | } | ||
259 | 258 | | |||
260 | if (hasQtIncludes(includes.system)) { | 259 | if (hasQtIncludes(includes.system)) { | ||
261 | const auto wrappedQtHeaders = QStandardPaths::locate(QStandardPaths::GenericDataLocation, | 260 | const auto wrappedQtHeaders = QStandardPaths::locate(QStandardPaths::GenericDataLocation, | ||
262 | QStringLiteral("kdevclangsupport/wrappedQtHeaders"), | 261 | QStringLiteral("kdevclangsupport/wrappedQtHeaders"), | ||
263 | QStandardPaths::LocateDirectory).toUtf8(); | 262 | QStandardPaths::LocateDirectory).toUtf8(); | ||
264 | if (!wrappedQtHeaders.isEmpty()) { | 263 | if (!wrappedQtHeaders.isEmpty()) { | ||
265 | smartArgs << wrappedQtHeaders; | 264 | smartArgs << wrappedQtHeaders; | ||
pino: ditto | |||||
266 | clangArguments << "-isystem" << wrappedQtHeaders.constData(); | 265 | clangArguments << "-isystem" << wrappedQtHeaders.constData(); | ||
267 | const QByteArray qtCore = wrappedQtHeaders + "/QtCore"; | 266 | const QByteArray qtCore = wrappedQtHeaders + "/QtCore"; | ||
268 | smartArgs << qtCore; | 267 | smartArgs << qtCore; | ||
269 | clangArguments << "-isystem" << qtCore.constData(); | 268 | clangArguments << "-isystem" << qtCore.constData(); | ||
270 | } | 269 | } | ||
271 | } | 270 | } | ||
272 | 271 | | |||
273 | addIncludes(&clangArguments, &smartArgs, includes.system, "-isystem"); | 272 | addIncludes(&clangArguments, &smartArgs, includes.system, "-isystem"); | ||
▲ Show 20 Lines • Show All 153 Lines • ▼ Show 20 Line(s) | |||||
427 | } | 426 | } | ||
428 | 427 | | |||
429 | IndexedString ParseSession::languageString() | 428 | IndexedString ParseSession::languageString() | ||
430 | { | 429 | { | ||
431 | static const IndexedString lang("Clang"); | 430 | static const IndexedString lang("Clang"); | ||
432 | return lang; | 431 | return lang; | ||
433 | } | 432 | } | ||
434 | 433 | | |||
434 | ClangProblem::Ptr ParseSession::getOrCreateProblem(int indexInTU, CXDiagnostic diagnostic) const | ||||
435 | { | ||||
436 | auto& problem = d->m_diagnosticsCache[indexInTU]; | ||||
437 | if (!problem) { | ||||
438 | problem = ClangDiagnosticEvaluator::createProblem(diagnostic, d->m_unit); | ||||
439 | } | ||||
440 | return problem; | ||||
441 | } | ||||
442 | | ||||
443 | ClangProblem::Ptr ParseSession::createExternalProblem(int indexInTU, | ||||
444 | CXDiagnostic diagnostic, | ||||
445 | const KLocalizedString& descriptionTemplate, | ||||
446 | int childProblemFinalLocationIndex) const | ||||
447 | { | ||||
448 | // Make a copy of the original (cached) problem since it is modified later | ||||
449 | auto problem = ClangProblem::Ptr(new ClangProblem(*getOrCreateProblem(indexInTU, diagnostic))); | ||||
450 | | ||||
451 | // Insert a copy of the parent problem (without child problems) as the first | ||||
452 | // child problem to preserve its location. | ||||
453 | auto* problemCopy = new ClangProblem(); | ||||
454 | problemCopy->setSource(problem->source()); | ||||
455 | problemCopy->setFinalLocation(problem->finalLocation()); | ||||
456 | problemCopy->setFinalLocationMode(problem->finalLocationMode()); | ||||
457 | problemCopy->setDescription(problem->description()); | ||||
458 | problemCopy->setExplanation(problem->explanation()); | ||||
459 | problemCopy->setSeverity(problem->severity()); | ||||
460 | | ||||
461 | auto childProblems = problem->diagnostics(); | ||||
462 | childProblems.prepend(IProblem::Ptr(problemCopy)); | ||||
463 | problem->setDiagnostics(childProblems); | ||||
464 | | ||||
465 | // Override the problem's finalLocation with that of the child problem in this document. | ||||
466 | // This is required to make the problem show up in the problem reporter for this | ||||
467 | // file, since it filters by finalLocation. It will also lead the user to the correct | ||||
468 | // location when clicking the problem and cause proper error highlighting. | ||||
469 | int index = (childProblemFinalLocationIndex >= 0) ? | ||||
470 | (1 + childProblemFinalLocationIndex) : | ||||
471 | (childProblems.size() - 1); | ||||
472 | problem->setFinalLocation(childProblems[index]->finalLocation()); | ||||
473 | | ||||
474 | problem->setDescription(descriptionTemplate.subs(problem->description()).toString()); | ||||
475 | | ||||
476 | return problem; | ||||
477 | } | ||||
478 | | ||||
479 | QList<ClangProblem::Ptr> ParseSession::createRequestedHereProblems(int indexInTU, CXDiagnostic diagnostic, CXFile file) const | ||||
480 | { | ||||
481 | QList<ClangProblem::Ptr> results; | ||||
482 | | ||||
483 | auto childDiagnostics = clang_getChildDiagnostics(diagnostic); | ||||
484 | auto numChildDiagnostics = clang_getNumDiagnosticsInSet(childDiagnostics); | ||||
485 | for (uint j = 0; j < numChildDiagnostics; ++j) { | ||||
486 | auto childDiagnostic = clang_getDiagnosticInSet(childDiagnostics, j); | ||||
487 | CXSourceLocation childLocation = clang_getDiagnosticLocation(childDiagnostic); | ||||
488 | CXFile childDiagnosticFile; | ||||
489 | clang_getFileLocation(childLocation, &childDiagnosticFile, nullptr, nullptr, nullptr); | ||||
490 | if (childDiagnosticFile == file) { | ||||
491 | QString description = ClangString(clang_getDiagnosticSpelling(childDiagnostic)).toString(); | ||||
492 | if (description.endsWith(QLatin1String("requested here"))) { | ||||
493 | // Note: Using the index j here assumes a 1:1 mapping from clang child diagnostics to KDevelop | ||||
494 | // problem diagnostics (i.e., child problems). If we wanted to avoid making this assumption, we'd have | ||||
495 | // to use ClangDiagnosticEvaluator::createProblem() first and then search within its | ||||
496 | // child problems to find the correct index. | ||||
497 | results << createExternalProblem(indexInTU, diagnostic, ki18n("Requested here: %1"), j); | ||||
498 | } | ||||
499 | } | ||||
500 | } | ||||
501 | | ||||
502 | return results; | ||||
503 | } | ||||
504 | | ||||
435 | QList<ProblemPointer> ParseSession::problemsForFile(CXFile file) const | 505 | QList<ProblemPointer> ParseSession::problemsForFile(CXFile file) const | ||
this isn't correct anymore: When we run into this code path with, say, 5 problems, we would reserve with 5 and then fill the cache. When we then get back here the next time with 0 problems, we would reserve 0, but the size would still be 5! the easiest solution I can come up with for now is to resize to output_index at the end... but I kind of dislike that we need that to begin with :( can you come up with something better? otherwise, please at least introduce a lambda helper that wraps the "lookup in cache and reuse or insert new problem" mwolff: this isn't correct anymore:
When we run into this code path with, say, 5 problems, we would… | |||||
To be honest, I am not sure how that cache is supposed to work. I did some testing with debug output added to ParseSession::problemsForFile(), and my impression is that it is currently broken. The behavior seems to be the following:
In case these observations are true and I didn't miss anything, could the cache be simply removed? thomassc: To be honest, I am not sure how that cache is supposed to work. I did some testing with debug… | |||||
436 | { | 506 | { | ||
437 | if (!d) { | 507 | if (!d) { | ||
438 | return {}; | 508 | return {}; | ||
439 | } | 509 | } | ||
440 | 510 | | |||
441 | QList<ProblemPointer> problems; | 511 | QList<ProblemPointer> problems; | ||
442 | 512 | | |||
443 | // extra clang diagnostics | 513 | // extra clang diagnostics | ||
444 | const uint numDiagnostics = clang_getNumDiagnostics(d->m_unit); | 514 | const uint numDiagnostics = clang_getNumDiagnostics(d->m_unit); | ||
445 | problems.reserve(numDiagnostics); | 515 | problems.reserve(numDiagnostics); | ||
446 | d->m_diagnosticsCache.resize(numDiagnostics); | 516 | d->m_diagnosticsCache.resize(numDiagnostics); | ||
447 | 517 | | |||
448 | for (uint i = 0; i < numDiagnostics; ++i) { | 518 | for (uint i = 0; i < numDiagnostics; ++i) { | ||
449 | auto diagnostic = clang_getDiagnostic(d->m_unit, i); | 519 | auto diagnostic = clang_getDiagnostic(d->m_unit, i); | ||
450 | 520 | | |||
451 | CXSourceLocation location = clang_getDiagnosticLocation(diagnostic); | 521 | CXSourceLocation location = clang_getDiagnosticLocation(diagnostic); | ||
452 | CXFile diagnosticFile; | 522 | CXFile diagnosticFile; | ||
453 | clang_getFileLocation(location, &diagnosticFile, nullptr, nullptr, nullptr); | 523 | clang_getFileLocation(location, &diagnosticFile, nullptr, nullptr, nullptr); | ||
524 | | ||||
525 | auto requestedHereProblems = createRequestedHereProblems(i, diagnostic, file); | ||||
526 | for (const auto& ptr : requestedHereProblems) { | ||||
527 | problems.append(static_cast<const ProblemPointer&>(ptr)); | ||||
528 | } | ||||
529 | | ||||
454 | // missing-include problems are so severe in clang that we always propagate | 530 | // missing-include problems are so severe in clang that we always propagate | ||
455 | // them to this document, to ensure that the user will see the error. | 531 | // them to this document, to ensure that the user will see the error. | ||
456 | if (diagnosticFile != file && ClangDiagnosticEvaluator::diagnosticType(diagnostic) != ClangDiagnosticEvaluator::IncludeFileNotFoundProblem) { | 532 | if (diagnosticFile != file && ClangDiagnosticEvaluator::diagnosticType(diagnostic) != ClangDiagnosticEvaluator::IncludeFileNotFoundProblem) { | ||
457 | continue; | 533 | continue; | ||
mwolff: put this into a helper function to reduce the complexity here | |||||
458 | } | 534 | } | ||
459 | 535 | | |||
460 | auto& problem = d->m_diagnosticsCache[i]; | 536 | problems << ((diagnosticFile == file) ? | ||
461 | if (!problem) { | 537 | getOrCreateProblem(i, diagnostic) : | ||
mwolff: similarly, create a helper function for this chunk | |||||
462 | problem = ClangDiagnosticEvaluator::createProblem(diagnostic, d->m_unit); | 538 | createExternalProblem(i, diagnostic, ki18n("In included file: %1"))); | ||
463 | } | | |||
464 | | ||||
465 | problems << problem; | | |||
466 | 539 | | |||
467 | clang_disposeDiagnostic(diagnostic); | 540 | clang_disposeDiagnostic(diagnostic); | ||
468 | } | 541 | } | ||
use ki18n too are we sure that this external problem will always have a location within the current file? couldn't it happen that we encounter completely unrelated errors? I would assume that e.g. in the following scenario, we may end up creating unhelpful issues? A > B > C if B has an error due to C, then A shouldn't show anything (except when we "show imports") mwolff: use ki18n too
are we sure that this external problem will always have a location within the… | |||||
This should only happen in case of missing includes. In this case, it is intentional to show these errors since they may cause other errors later. Other types of problems with location outside of the current file are filtered out above: // 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; } thomassc: This should only happen in case of missing includes. In this case, it is intentional to show… | |||||
469 | 542 | | |||
470 | // other problem sources | 543 | // other problem sources | ||
471 | 544 | | |||
472 | TodoExtractor extractor(unit(), file); | 545 | TodoExtractor extractor(unit(), file); | ||
473 | problems << extractor.problems(); | 546 | problems << extractor.problems(); | ||
474 | 547 | | |||
475 | #if CINDEX_VERSION_MINOR > 30 | 548 | #if CINDEX_VERSION_MINOR > 30 | ||
476 | // note that the below warning is triggered on every reparse when there is a precompiled preamble | 549 | // note that the below warning is triggered on every reparse when there is a precompiled preamble | ||
▲ Show 20 Lines • Show All 64 Lines • Show Last 20 Lines |
make -> create