diff --git a/plugins/clang/clangsupport.cpp b/plugins/clang/clangsupport.cpp index 646486e7cf..4b6713aae6 100644 --- a/plugins/clang/clangsupport.cpp +++ b/plugins/clang/clangsupport.cpp @@ -1,439 +1,440 @@ /* This file is part of KDevelop Copyright 2013 Olivier de Gaalon Copyright 2013 Milian Wolff Copyright 2014 Kevin Funk This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "clangsupport.h" #include "clangparsejob.h" #include "util/clangdebug.h" #include "util/clangtypes.h" #include "util/clangutils.h" #include "codecompletion/model.h" #include "clanghighlighting.h" #include #include #include #include #include "codegen/clangrefactoring.h" #include "codegen/clangclasshelper.h" #include "codegen/adaptsignatureassistant.h" #include "duchain/documentfinderhelpers.h" -#include "duchain/clangindex.h" #include "duchain/navigationwidget.h" +#include "duchain/clangindex.h" +#include "duchain/clanghelpers.h" #include "duchain/macrodefinition.h" #include "duchain/clangparsingenvironmentfile.h" #include "duchain/duchainutils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "clangsettings/sessionsettings/sessionsettings.h" #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KDevClangSupportFactory, "kdevclangsupport.json", registerPlugin(); ) using namespace KDevelop; namespace { QPair lineInDocument(const QUrl &url, const KTextEditor::Cursor& position) { KDevelop::IDocument* doc = ICore::self()->documentController()->documentForUrl(url); if (!doc || !doc->textDocument() || !ICore::self()->documentController()->activeTextDocumentView()) { return {}; } const int lineNumber = position.line(); const int lineLength = doc->textDocument()->lineLength(lineNumber); KTextEditor::Range range(lineNumber, 0, lineNumber, lineLength); QString line = doc->textDocument()->text(range); return {line, range}; } QPair importedContextForPosition(const QUrl &url, const KTextEditor::Cursor& position) { auto pair = lineInDocument(url, position); const QString line = pair.first; if (line.isEmpty()) return {{}, KTextEditor::Range::invalid()}; KTextEditor::Range wordRange = ClangUtils::rangeForIncludePathSpec(line, pair.second); if (!wordRange.isValid() || !wordRange.contains(position)) { return {{}, KTextEditor::Range::invalid()}; } // Since this is called by the editor while editing, use a fast timeout so the editor stays responsive DUChainReadLocker lock(nullptr, 100); if (!lock.locked()) { clangDebug() << "Failed to lock the du-chain in time"; return {TopDUContextPointer(), KTextEditor::Range::invalid()}; } TopDUContext* topContext = DUChainUtils::standardContextForUrl(url); if (line.isEmpty() || !topContext || !topContext->parsingEnvironmentFile()) { return {TopDUContextPointer(), KTextEditor::Range::invalid()}; } // It's an #include, find out which file was included at the given line foreach(const DUContext::Import &imported, topContext->importedParentContexts()) { auto context = imported.context(nullptr); if (context) { if(topContext->transformFromLocalRevision(topContext->importPosition(context)).line() == wordRange.start().line()) { if (auto importedTop = dynamic_cast(context)) return {TopDUContextPointer(importedTop), wordRange}; } } } // The last resort. Check if the file is already included (maybe recursively from another files). // This is needed as clang doesn't visit (clang_getInclusions) those inclusions. // TODO: Maybe create an assistant that'll report whether the file is already included? auto includeName = line.mid(wordRange.start().column(), wordRange.end().column() - wordRange.start().column()); if (!includeName.isEmpty()) { if (includeName.startsWith(QLatin1Char('.'))) { const Path dir = Path(url).parent(); includeName = Path(dir, includeName).toLocalFile(); } const auto recursiveImports = topContext->recursiveImportIndices(); auto iterator = recursiveImports.iterator(); while (iterator) { const auto str = (*iterator).url().str(); if (str == includeName || (str.endsWith(includeName) && str[str.size()-includeName.size()-1] == QLatin1Char('/'))) { return {TopDUContextPointer((*iterator).data()), wordRange}; } ++iterator; } } return {{}, KTextEditor::Range::invalid()}; } QPair macroExpansionForPosition(const QUrl &url, const KTextEditor::Cursor& position) { TopDUContext* topContext = DUChainUtils::standardContextForUrl(url); if (topContext) { int useAt = topContext->findUseAt(topContext->transformToLocalRevision(position)); if (useAt >= 0) { Use use = topContext->uses()[useAt]; if (dynamic_cast(use.usedDeclaration(topContext))) { return {TopDUContextPointer(topContext), use}; } } } return {{}, Use()}; } } ClangSupport::ClangSupport(QObject* parent, const QVariantList& ) : IPlugin( QStringLiteral("kdevclangsupport"), parent ) , ILanguageSupport() , m_highlighting(nullptr) , m_refactoring(nullptr) , m_index(nullptr) { { - const auto builtinDir = ClangIntegration::DUChainUtils::clangBuiltinIncludePath(); + const auto builtinDir = ClangHelpers::clangBuiltinIncludePath(); const auto headerToCheck = QLatin1String("cpuid.h"); if (!QFile::exists(builtinDir + QLatin1Char('/') + headerToCheck)) { setErrorDescription(i18n("The clang builtin include path \"%1\" is invalid (missing %2 header).\n" "Try setting the KDEV_CLANG_BUILTIN_DIR environment variable manually to fix this.\n" "See also: https://bugs.kde.org/show_bug.cgi?id=393779", builtinDir, headerToCheck)); return; } } setXMLFile( QStringLiteral("kdevclangsupport.rc") ); ClangIntegration::DUChainUtils::registerDUChainItems(); m_highlighting = new ClangHighlighting(this); m_refactoring = new ClangRefactoring(this); m_index.reset(new ClangIndex); auto model = new KDevelop::CodeCompletion( this, new ClangCodeCompletionModel(m_index.data(), this), name() ); connect(model, &CodeCompletion::registeredToView, this, &ClangSupport::disableKeywordCompletion); connect(model, &CodeCompletion::unregisteredFromView, this, &ClangSupport::enableKeywordCompletion); const auto& mimeTypes = DocumentFinderHelpers::mimeTypesList(); for (const auto& type : mimeTypes) { KDevelop::IBuddyDocumentFinder::addFinder(type, this); } auto assistantsManager = core()->languageController()->staticAssistantsManager(); assistantsManager->registerAssistant(StaticAssistant::Ptr(new RenameAssistant(this))); assistantsManager->registerAssistant(StaticAssistant::Ptr(new AdaptSignatureAssistant(this))); connect(ICore::self()->documentController(), &IDocumentController::documentActivated, this, &ClangSupport::documentActivated); } ClangSupport::~ClangSupport() { parseLock()->lockForWrite(); // By locking the parse-mutexes, we make sure that parse jobs get a chance to finish in a good state parseLock()->unlock(); const auto& mimeTypes = DocumentFinderHelpers::mimeTypesList(); for (const auto& type : mimeTypes) { KDevelop::IBuddyDocumentFinder::removeFinder(type); } ClangIntegration::DUChainUtils::unregisterDUChainItems(); } KDevelop::ConfigPage* ClangSupport::configPage(int number, QWidget* parent) { return number == 0 ? new SessionSettings(parent) : nullptr; } int ClangSupport::configPages() const { return 1; } ParseJob* ClangSupport::createParseJob(const IndexedString& url) { return new ClangParseJob(url, this); } QString ClangSupport::name() const { return QStringLiteral("clang"); } ICodeHighlighting* ClangSupport::codeHighlighting() const { return m_highlighting; } BasicRefactoring* ClangSupport::refactoring() const { return m_refactoring; } ICreateClassHelper* ClangSupport::createClassHelper() const { return new ClangClassHelper; } ClangIndex* ClangSupport::index() { return m_index.data(); } bool ClangSupport::areBuddies(const QUrl &url1, const QUrl& url2) { return DocumentFinderHelpers::areBuddies(url1, url2); } bool ClangSupport::buddyOrder(const QUrl &url1, const QUrl& url2) { return DocumentFinderHelpers::buddyOrder(url1, url2); } QVector ClangSupport::potentialBuddies(const QUrl& url) const { return DocumentFinderHelpers::potentialBuddies(url); } void ClangSupport::createActionsForMainWindow (Sublime::MainWindow* /*window*/, QString& _xmlFile, KActionCollection& actions) { _xmlFile = xmlFile(); QAction* renameDeclarationAction = actions.addAction(QStringLiteral("code_rename_declaration")); renameDeclarationAction->setText( i18n("Rename Declaration") ); renameDeclarationAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); actions.setDefaultShortcut(renameDeclarationAction, Qt::CTRL | Qt::SHIFT | Qt::Key_R); connect(renameDeclarationAction, &QAction::triggered, m_refactoring, &ClangRefactoring::executeRenameAction); QAction* moveIntoSourceAction = actions.addAction(QStringLiteral("code_move_definition")); moveIntoSourceAction->setText(i18n("Move into Source")); actions.setDefaultShortcut(moveIntoSourceAction, Qt::CTRL | Qt::ALT | Qt::Key_S); connect(moveIntoSourceAction, &QAction::triggered, m_refactoring, &ClangRefactoring::executeMoveIntoSourceAction); } KDevelop::ContextMenuExtension ClangSupport::contextMenuExtension(KDevelop::Context* context, QWidget* parent) { ContextMenuExtension cm; EditorContext *ec = dynamic_cast(context); if (ec && ICore::self()->languageController()->languagesForUrl(ec->url()).contains(this)) { // It's a C++ file, let's add our context menu. m_refactoring->fillContextMenu(cm, context, parent); } return cm; } KTextEditor::Range ClangSupport::specialLanguageObjectRange(const QUrl &url, const KTextEditor::Cursor& position) { DUChainReadLocker lock; const QPair macroExpansion = macroExpansionForPosition(url, position); if (macroExpansion.first) { return macroExpansion.first->transformFromLocalRevision(macroExpansion.second.m_range); } const QPair import = importedContextForPosition(url, position); if(import.first) { return import.second; } return KTextEditor::Range::invalid(); } QPair ClangSupport::specialLanguageObjectJumpCursor(const QUrl &url, const KTextEditor::Cursor& position) { const QPair import = importedContextForPosition(url, position); DUChainReadLocker lock; if (import.first) { return qMakePair(import.first->url().toUrl(), KTextEditor::Cursor(0,0)); } return {{}, KTextEditor::Cursor::invalid()}; } QWidget* ClangSupport::specialLanguageObjectNavigationWidget(const QUrl &url, const KTextEditor::Cursor& position) { DUChainReadLocker lock; const QPair macroExpansion = macroExpansionForPosition(url, position); if (macroExpansion.first) { Declaration* declaration = macroExpansion.second.usedDeclaration(macroExpansion.first.data()); const MacroDefinition::Ptr macroDefinition(dynamic_cast(declaration)); Q_ASSERT(macroDefinition); auto rangeInRevision = macroExpansion.first->transformFromLocalRevision(macroExpansion.second.m_range.start); return new ClangNavigationWidget(macroDefinition, DocumentCursor(IndexedString(url), rangeInRevision)); } const QPair import = importedContextForPosition(url, position); if (import.first) { return import.first->createNavigationWidget(); } return nullptr; } TopDUContext* ClangSupport::standardContext(const QUrl &url, bool /*proxyContext*/) { ClangParsingEnvironment env; return DUChain::self()->chainForDocument(url, &env); } void ClangSupport::documentActivated(IDocument* doc) { TopDUContext::Features features; { DUChainReadLocker lock; auto ctx = DUChainUtils::standardContextForUrl(doc->url()); if (!ctx) { return; } auto file = ctx->parsingEnvironmentFile(); if (!file) { return; } if (file->type() != CppParsingEnvironment) { return; } if (file->needsUpdate()) { return; } features = ctx->features(); } const auto indexedUrl = IndexedString(doc->url()); auto sessionData = ClangIntegration::DUChainUtils::findParseSessionData(indexedUrl, index()->translationUnitForUrl(IndexedString(doc->url()))); if (sessionData) { return; } if ((features & TopDUContext::AllDeclarationsContextsAndUses) != TopDUContext::AllDeclarationsContextsAndUses) { // the file was parsed in simplified mode, we need to reparse it to get all data // now that its opened in the editor features = TopDUContext::AllDeclarationsContextsAndUses; } else { features = static_cast(ClangParseJob::AttachASTWithoutUpdating | features); if (ICore::self()->languageController()->backgroundParser()->isQueued(indexedUrl)) { // The document is already scheduled for parsing (happens when opening a project with an active document) // The background parser will optimize the previous request out, so we need to update highlighting features = static_cast(ClangParseJob::UpdateHighlighting | features); } } ICore::self()->languageController()->backgroundParser()->addDocument(indexedUrl, features); } static void setKeywordCompletion(KTextEditor::View* view, bool enabled) { if (auto config = qobject_cast(view)) { config->setConfigValue(QStringLiteral("keyword-completion"), enabled); } } int ClangSupport::suggestedReparseDelayForChange(KTextEditor::Document* /*doc*/, const KTextEditor::Range& /*changedRange*/, const QString& /*changedText*/, bool /*removal*/) const { return ILanguageSupport::DefaultDelay; } void ClangSupport::disableKeywordCompletion(KTextEditor::View* view) { setKeywordCompletion(view, false); } void ClangSupport::enableKeywordCompletion(KTextEditor::View* view) { setKeywordCompletion(view, true); } #include "clangsupport.moc" diff --git a/plugins/clang/duchain/clanghelpers.cpp b/plugins/clang/duchain/clanghelpers.cpp index a2ca7487b8..d8ad89aab6 100644 --- a/plugins/clang/duchain/clanghelpers.cpp +++ b/plugins/clang/duchain/clanghelpers.cpp @@ -1,342 +1,356 @@ /* * Copyright 2014 Olivier de Gaalon * Copyright 2014 Milian Wolff * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "clanghelpers.h" #include #include #include #include #include #include "builder.h" #include "parsesession.h" #include "clangparsingenvironmentfile.h" #include "clangindex.h" #include "clangducontext.h" #include "util/clangtypes.h" +#include "libclang_include_path.h" + #include using namespace KDevelop; namespace { CXChildVisitResult visitCursor(CXCursor cursor, CXCursor, CXClientData data) { if (cursor.kind != CXCursor_InclusionDirective) { return CXChildVisit_Continue; } auto imports = static_cast(data); CXFile file = clang_getIncludedFile(cursor); if(!file){ return CXChildVisit_Continue; } CXSourceLocation location = clang_getCursorLocation(cursor); CXFile parentFile; uint line, column; clang_getFileLocation(location, &parentFile, &line, &column, nullptr); foreach (const auto& import, imports->values(parentFile)) { // clang_getInclusions doesn't include the same import twice, so we shouldn't do it too. if (import.file == file) { return CXChildVisit_Continue; } } imports->insert(parentFile, {file, CursorInRevision(line-1, column-1)}); return CXChildVisit_Recurse; } ReferencedTopDUContext createTopContext(const IndexedString& path, const ClangParsingEnvironment& environment) { ClangParsingEnvironmentFile* file = new ClangParsingEnvironmentFile(path, environment); ReferencedTopDUContext context = new ClangTopDUContext(path, RangeInRevision(0, 0, INT_MAX, INT_MAX), file); DUChain::self()->addDocumentChain(context); context->updateImportsCache(); return context; } } Imports ClangHelpers::tuImports(CXTranslationUnit tu) { Imports imports; // Intentionally don't use clang_getInclusions here, as it skips already visited inclusions // which makes TestDUChain::testNestedImports fail CXCursor tuCursor = clang_getTranslationUnitCursor(tu); clang_visitChildren(tuCursor, &visitCursor, &imports); return imports; } bool importLocationLessThan(const Import& lhs, const Import& rhs) { return lhs.location.line < rhs.location.line; } ReferencedTopDUContext ClangHelpers::buildDUChain(CXFile file, const Imports& imports, const ParseSession& session, TopDUContext::Features features, IncludeFileContexts& includedFiles, ClangIndex* index, const std::function& abortFunction) { if (includedFiles.contains(file)) { return {}; } if (abortFunction && abortFunction()) { return {}; } // prevent recursion includedFiles.insert(file, {}); // ensure DUChain for imports are built properly, and in correct order QList sortedImports = imports.values(file); std::sort(sortedImports.begin(), sortedImports.end(), importLocationLessThan); foreach(const auto& import, sortedImports) { buildDUChain(import.file, imports, session, features, includedFiles, index, abortFunction); } const IndexedString path(QDir(ClangString(clang_getFileName(file)).toString()).canonicalPath()); if (path.isEmpty()) { // may happen when the file gets removed before the job is run return {}; } const auto& environment = session.environment(); bool update = false; UrlParseLock urlLock(path); ReferencedTopDUContext context; { DUChainWriteLocker lock; context = DUChain::self()->chainForDocument(path, &environment); if (!context) { context = ::createTopContext(path, environment); } else { update = true; } includedFiles.insert(file, context); if (update) { auto envFile = ClangParsingEnvironmentFile::Ptr(dynamic_cast(context->parsingEnvironmentFile().data())); Q_ASSERT(envFile); if (!envFile) return context; /* NOTE: When we are here, then either the translation unit or one of its headers was changed. * Thus we must always update the translation unit to propagate the change(s). * See also: https://bugs.kde.org/show_bug.cgi?id=356327 * This assumes that headers are independent, we may need to improve that in the future * and also update header files more often when other files included therein got updated. */ if (path != environment.translationUnitUrl() && !envFile->needsUpdate(&environment) && envFile->featuresSatisfied(features)) { return context; } else { //TODO: don't attempt to update if this environment is worse quality than the outdated one if (index && envFile->environmentQuality() < environment.quality()) { index->pinTranslationUnitForUrl(environment.translationUnitUrl(), path); } envFile->setEnvironment(environment); envFile->setModificationRevision(ModificationRevision::revisionForFile(context->url())); } context->clearImportedParentContexts(); } context->setFeatures(features); foreach(const auto& import, sortedImports) { auto ctx = includedFiles.value(import.file); if (!ctx) { // happens for cyclic imports continue; } context->addImportedParentContext(ctx, import.location); } context->updateImportsCache(); } const auto problems = session.problemsForFile(file); { DUChainWriteLocker lock; context->setProblems(problems); } Builder::visit(session.unit(), file, includedFiles, update); DUChain::self()->emitUpdateReady(path, context); return context; } DeclarationPointer ClangHelpers::findDeclaration(CXSourceLocation location, const QualifiedIdentifier& id, const ReferencedTopDUContext& top) { if (!top) { // may happen for cyclic includes return {}; } auto cursor = CursorInRevision(ClangLocation(location)); DUChainReadLocker lock; if (!id.isEmpty()) { const auto& decls = top->findDeclarations(id); for (Declaration* decl : decls) { if (decl->range().contains(cursor) || (decl->range().isEmpty() && decl->range().start == cursor)) { return DeclarationPointer(decl); } } } // there was no match based on the IDs, try the classical // range based search (very slow) Q_ASSERT(top); if (DUContext *local = top->findContextAt(cursor)) { if (local->owner() && local->owner()->range().contains(cursor)) { return DeclarationPointer(local->owner()); } return DeclarationPointer(local->findDeclarationAt(cursor)); } return {}; } DeclarationPointer ClangHelpers::findDeclaration(CXCursor cursor, const IncludeFileContexts& includes) { auto location = clang_getCursorLocation(cursor); CXFile file = nullptr; clang_getFileLocation(location, &file, nullptr, nullptr, nullptr); if (!file) { return {}; } // build a qualified identifier by following the chain of semantic parents QList ids; CXCursor currentCursor = cursor; while (currentCursor.kind != CXCursor_TranslationUnit && currentCursor.kind != CXCursor_InvalidFile) { ids << Identifier(ClangString(clang_getCursorSpelling(currentCursor)).toString()); currentCursor = clang_getCursorSemanticParent(currentCursor); } QualifiedIdentifier qid; for (int i = ids.size()-1; i >= 0; --i) { qid.push(ids[i]); } return findDeclaration(location, qid, includes.value(file)); } DeclarationPointer ClangHelpers::findDeclaration(CXType type, const IncludeFileContexts& includes) { CXCursor cursor = clang_getTypeDeclaration(type); return findDeclaration(cursor, includes); } DeclarationPointer ClangHelpers::findForwardDeclaration(CXType type, DUContext* context, CXCursor cursor) { if(type.kind != CXType_Record && type.kind != CXType_ObjCInterface && type.kind != CXType_ObjCClass){ return {}; } auto qualifiedIdentifier = QualifiedIdentifier(ClangString(clang_getTypeSpelling(type)).toString()); DUChainReadLocker lock; const auto decls = context->findDeclarations(qualifiedIdentifier, CursorInRevision(ClangLocation(clang_getCursorLocation(cursor))) ); for (auto decl : decls) { if (decl->isForwardDeclaration()) { return DeclarationPointer(decl); } } return {}; } RangeInRevision ClangHelpers::cursorSpellingNameRange(CXCursor cursor, const Identifier& id) { auto range = ClangRange(clang_Cursor_getSpellingNameRange(cursor, 0, 0)).toRangeInRevision(); #if CINDEX_VERSION_MINOR < 29 auto kind = clang_getCursorKind(cursor); // Clang used to report invalid ranges for destructors and methods like 'operator=' if (kind == CXCursor_Destructor || kind == CXCursor_CXXMethod) { range.end.column = range.start.column + id.toString().length(); } #endif Q_UNUSED(id); return range; } QStringList ClangHelpers::headerExtensions() { static const QStringList headerExtensions = { QStringLiteral("h"), QStringLiteral("H"), QStringLiteral("hh"), QStringLiteral("hxx"), QStringLiteral("hpp"), QStringLiteral("tlh"), QStringLiteral("h++"), }; return headerExtensions; } QStringList ClangHelpers::sourceExtensions() { static const QStringList sourceExtensions = { QStringLiteral("c"), QStringLiteral("cc"), QStringLiteral("cpp"), QStringLiteral("c++"), QStringLiteral("cxx"), QStringLiteral("C"), QStringLiteral("m"), QStringLiteral("mm"), QStringLiteral("M"), QStringLiteral("inl"), QStringLiteral("_impl.h"), }; return sourceExtensions; } bool ClangHelpers::isSource(const QString& path) { const auto& extensions = sourceExtensions(); return std::any_of(extensions.constBegin(), extensions.constEnd(), [&](const QString& ext) { return path.endsWith(ext); }); } bool ClangHelpers::isHeader(const QString& path) { const auto& extensions = headerExtensions(); return std::any_of(extensions.constBegin(), extensions.constEnd(), [&](const QString& ext) { return path.endsWith(ext); }); } + +QString ClangHelpers::clangBuiltinIncludePath() +{ + static const auto dir = []() -> QString { + auto dir = qgetenv("KDEV_CLANG_BUILTIN_DIR"); + if (dir.isEmpty()) { + dir = KDEV_CLANG_BUILTIN_DIR; + } + return QString::fromUtf8(dir); + }(); + return dir; +} diff --git a/plugins/clang/duchain/clanghelpers.h b/plugins/clang/duchain/clanghelpers.h index 117ac1312c..8459104b8f 100644 --- a/plugins/clang/duchain/clanghelpers.h +++ b/plugins/clang/duchain/clanghelpers.h @@ -1,105 +1,112 @@ /* * Copyright 2014 Olivier de Gaalon * Copyright 2014 Milian Wolff * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef CLANGHELPERS_H #define CLANGHELPERS_H #include "clangprivateexport.h" #include #include #include #include class ParseSession; class ClangIndex; struct Import { CXFile file; KDevelop::CursorInRevision location; }; Q_DECLARE_TYPEINFO(Import, Q_MOVABLE_TYPE); using Imports = QMultiHash; using IncludeFileContexts = QHash; namespace ClangHelpers { KDevelop::DeclarationPointer findDeclaration(CXSourceLocation cursor, const KDevelop::QualifiedIdentifier& id, const KDevelop::ReferencedTopDUContext& top); KDevelop::DeclarationPointer findDeclaration(CXCursor cursor, const IncludeFileContexts& includes); KDevelop::DeclarationPointer findDeclaration(CXType type, const IncludeFileContexts& includes); /** * Try to look up the first reachable forward declaration for type @a type * * @param context The context where this search is happening * @param cursor The location from which we're searching */ KDevelop::DeclarationPointer findForwardDeclaration(CXType type, KDevelop::DUContext* context, CXCursor cursor); /** * Wrapper for @ref clang_Cursor_getSpellingNameRange which sometimes reports invalid ranges */ KDevelop::RangeInRevision cursorSpellingNameRange(CXCursor cursor, const KDevelop::Identifier& id); /** * @returns all the Imports for each file in the @a tu */ KDEVCLANGPRIVATE_EXPORT Imports tuImports(CXTranslationUnit tu); /** * Recursively builds a duchain with the specified @a features for the * @a file and each of its @a imports using the TU from @a session. * The resulting contexts are placed in @a includedFiles. * @returns the context created for @a file */ KDEVCLANGPRIVATE_EXPORT KDevelop::ReferencedTopDUContext buildDUChain( CXFile file, const Imports& imports, const ParseSession& session, KDevelop::TopDUContext::Features features, IncludeFileContexts& includedFiles, ClangIndex* index = nullptr, const std::function& abortFunction = {}); /** * @return List of possible header extensions used for definition/declaration fallback switching */ QStringList headerExtensions(); /** * @return List of possible source extensions used for definition/declaration fallback switching */ QStringList sourceExtensions(); /** * @return True if the given file @a path has the extension of a C++ source file */ KDEVCLANGPRIVATE_EXPORT bool isSource(const QString& path); /** * @return True if the given file @a path has the extension of a C++ header file */ KDEVCLANGPRIVATE_EXPORT bool isHeader(const QString& path); +/** + * @return The path containing Clang built includes (e.g. stddef.h, stdarg.h, cpuid.h) + * + * Also see: https://clang.llvm.org/docs/FAQ.html + */ +KDEVCLANGPRIVATE_EXPORT QString clangBuiltinIncludePath(); + } #endif //CLANGHELPERS_H diff --git a/plugins/clang/duchain/duchainutils.cpp b/plugins/clang/duchain/duchainutils.cpp index 61314d2580..ef67fac118 100644 --- a/plugins/clang/duchain/duchainutils.cpp +++ b/plugins/clang/duchain/duchainutils.cpp @@ -1,119 +1,105 @@ /* * Copyright 2014 Kevin Funk * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "duchainutils.h" #include "util/clangdebug.h" #include #include #include #include #include #include "macrodefinition.h" #include "clangducontext.h" #include "clangparsingenvironmentfile.h" #include "types/classspecializationtype.h" -#include "libclang_include_path.h" - using namespace KDevelop; namespace ClangIntegration { KTextEditor::Range DUChainUtils::functionSignatureRange(const Declaration* decl) { if (!decl->isFunctionDeclaration()) { qCWarning(KDEV_CLANG) << "Invalid declaration:" << decl; return {}; } auto functionContext = decl->internalContext(); Q_ASSERT(functionContext); auto childContexts = functionContext->childContexts(); if (childContexts.isEmpty()) { return functionContext->rangeInCurrentRevision(); } const auto start = functionContext->rangeInCurrentRevision().start(); const auto end = childContexts[0]->rangeInCurrentRevision().start(); return {start, end}; } void DUChainUtils::registerDUChainItems() { duchainRegisterType(); duchainRegisterType(); duchainRegisterType(); duchainRegisterType(); TypeSystem::self().registerTypeClass(); } void DUChainUtils::unregisterDUChainItems() { TypeSystem::self().unregisterTypeClass(); /// FIXME: this is currently not supported by the DUChain code... /// When the items are unregistered on plugin destruction, we'll get hit by /// assertions later on when the DUChain is finalized. There, when the data is getting cleaned up, /// we try to load all kinds of items again which would fail to find our items if we unregister. /// So let's not do it... /* duchainUnregisterType(); duchainUnregisterType(); duchainUnregisterType(); duchainUnregisterType(); */ } ParseSessionData::Ptr DUChainUtils::findParseSessionData(const IndexedString &file, const IndexedString &tufile) { DUChainReadLocker lock; auto context = KDevelop::DUChainUtils::standardContextForUrl(file.toUrl()); if (!context || !context->ast()) { // no cached data found for the current file, but maybe // we are lucky and can grab it from the TU context // this happens e.g. when originally a .cpp file is open and then one // of its included files is opened in the editor. context = KDevelop::DUChainUtils::standardContextForUrl(tufile.toUrl()); } if (context) { return ParseSessionData::Ptr(dynamic_cast(context->ast().data())); } return {}; } -QString DUChainUtils::clangBuiltinIncludePath() -{ - static const auto dir = []() -> QString { - auto dir = qgetenv("KDEV_CLANG_BUILTIN_DIR"); - if (dir.isEmpty()) { - dir = KDEV_CLANG_BUILTIN_DIR; - } - return QString::fromUtf8(dir); - }(); - return dir; -} - } diff --git a/plugins/clang/duchain/duchainutils.h b/plugins/clang/duchain/duchainutils.h index 66119ced49..95785f0bd7 100644 --- a/plugins/clang/duchain/duchainutils.h +++ b/plugins/clang/duchain/duchainutils.h @@ -1,57 +1,55 @@ /* * Copyright 2014 Kevin Funk * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef DUCHAINUTILS_H #define DUCHAINUTILS_H #include "clangprivateexport.h" #include "duchain/parsesession.h" namespace KTextEditor { class Range; } namespace KDevelop { class Declaration; } namespace ClangIntegration { namespace DUChainUtils { KDEVCLANGPRIVATE_EXPORT KTextEditor::Range functionSignatureRange(const KDevelop::Declaration* decl); KDEVCLANGPRIVATE_EXPORT void registerDUChainItems(); KDEVCLANGPRIVATE_EXPORT void unregisterDUChainItems(); /** * Finds attached parse session data (aka AST) to the @p file * * If no session data found, then @p tuFile asked for the attached session data */ KDEVCLANGPRIVATE_EXPORT ParseSessionData::Ptr findParseSessionData(const KDevelop::IndexedString &file, const KDevelop::IndexedString &tufile); - -KDEVCLANGPRIVATE_EXPORT QString clangBuiltinIncludePath(); } } #endif // DUCHAINUTILS_H diff --git a/plugins/clang/duchain/parsesession.cpp b/plugins/clang/duchain/parsesession.cpp index 5d9366cbdc..5ef8899ffe 100644 --- a/plugins/clang/duchain/parsesession.cpp +++ b/plugins/clang/duchain/parsesession.cpp @@ -1,535 +1,534 @@ /* This file is part of KDevelop Copyright 2013 Olivier de Gaalon Copyright 2013 Milian Wolff Copyright 2013 Kevin Funk This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "parsesession.h" #include #include "clangproblem.h" #include "clangdiagnosticevaluator.h" #include "todoextractor.h" #include "clanghelpers.h" #include "clangindex.h" #include "clangparsingenvironment.h" -#include "duchainutils.h" #include "util/clangdebug.h" #include "util/clangtypes.h" #include "util/clangutils.h" #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { QVector extraArgs() { const auto extraArgsString = QString::fromLatin1(qgetenv("KDEV_CLANG_EXTRA_ARGUMENTS")); const auto extraArgs = KShell::splitArgs(extraArgsString); // transform to list of QByteArrays QVector result; result.reserve(extraArgs.size()); for (const QString& arg : extraArgs) { result << arg.toLatin1(); } clangDebug() << "Passing extra arguments to clang:" << result; return result; } void sanitizeArguments(QVector& arguments) { // We remove the -Werror flag, and replace -Werror=foo by -Wfoo. // Warning as error may cause problem to the clang parser. const auto asError = QByteArrayLiteral("-Werror="); const auto documentation = QByteArrayLiteral("-Wdocumentation"); for (auto& argument : arguments) { if (argument == "-Werror") { argument.clear(); } else if (argument.startsWith(asError)) { // replace -Werror=foo by -Wfoo argument.remove(2, asError.length() - 2); } #if CINDEX_VERSION_MINOR < 100 // FIXME https://bugs.llvm.org/show_bug.cgi?id=35333 if (argument == documentation) { argument.clear(); } #endif } } QVector argsForSession(const QString& path, ParseSessionData::Options options, const ParserSettings& parserSettings) { QMimeDatabase db; if(db.mimeTypeForFile(path).name() == QStringLiteral("text/x-objcsrc")) { return {QByteArrayLiteral("-xobjective-c++")}; } // TODO: No proper mime type detection possible yet // cf. https://bugs.freedesktop.org/show_bug.cgi?id=26913 if (path.endsWith(QLatin1String(".cl"), Qt::CaseInsensitive)) { return {QByteArrayLiteral("-xcl")}; } // TODO: No proper mime type detection possible yet // cf. https://bugs.freedesktop.org/show_bug.cgi?id=23700 if (path.endsWith(QLatin1String(".cu"), Qt::CaseInsensitive)) { return {QByteArrayLiteral("-xcuda")}; } if (parserSettings.parserOptions.isEmpty()) { // The parserOptions can be empty for some unit tests that use ParseSession directly auto defaultArguments = ClangSettingsManager::self()->parserSettings(path).toClangAPI(); defaultArguments.append(QByteArrayLiteral("-nostdinc")); defaultArguments.append(QByteArrayLiteral("-nostdinc++")); defaultArguments.append(QByteArrayLiteral("-xc++")); sanitizeArguments(defaultArguments); return defaultArguments; } auto result = parserSettings.toClangAPI(); result.append(QByteArrayLiteral("-nostdinc")); if (parserSettings.isCpp()) { result.append(QByteArrayLiteral("-nostdinc++")); } if (options & ParseSessionData::PrecompiledHeader) { result.append(parserSettings.isCpp() ? QByteArrayLiteral("-xc++-header") : QByteArrayLiteral("-xc-header")); sanitizeArguments(result); return result; } result.append(parserSettings.isCpp() ? QByteArrayLiteral("-xc++") : QByteArrayLiteral("-xc")); sanitizeArguments(result); return result; } void addIncludes(QVector* args, QVector* otherArgs, const Path::List& includes, const char* cliSwitch) { for (const Path& url : includes) { if (url.isEmpty()) { continue; } QFileInfo info(url.toLocalFile()); QByteArray path = url.toLocalFile().toUtf8(); if (info.isFile()) { path.prepend("-include"); } else { path.prepend(cliSwitch); } otherArgs->append(path); args->append(path.constData()); } } void addFrameworkDirectories(QVector* args, QVector* otherArgs, const Path::List& frameworkDirectories, const char* cliSwitch) { for (const Path& url : frameworkDirectories) { if (url.isEmpty()) { continue; } QFileInfo info(url.toLocalFile()); if (!info.isDir()) { qCWarning(KDEV_CLANG) << "supposed framework directory is not a directory:" << url.pathOrUrl(); continue; } QByteArray path = url.toLocalFile().toUtf8(); otherArgs->append(cliSwitch); otherArgs->append(path); args->append(cliSwitch); args->append(path.constData()); } } QVector toClangApi(const QVector& unsavedFiles) { QVector unsaved; unsaved.reserve(unsavedFiles.size()); std::transform(unsavedFiles.begin(), unsavedFiles.end(), std::back_inserter(unsaved), [] (const UnsavedFile& file) { return file.toClangApi(); }); return unsaved; } bool hasQtIncludes(const Path::List& includePaths) { return std::find_if(includePaths.begin(), includePaths.end(), [] (const Path& path) { return path.lastPathSegment() == QLatin1String("QtCore"); }) != includePaths.end(); } } ParseSessionData::ParseSessionData(const QVector& unsavedFiles, ClangIndex* index, const ClangParsingEnvironment& environment, Options options) : m_file(nullptr) , m_unit(nullptr) { unsigned int flags = CXTranslationUnit_DetailedPreprocessingRecord #if CINDEX_VERSION_MINOR >= 34 | CXTranslationUnit_KeepGoing #endif ; if (options.testFlag(SkipFunctionBodies)) { flags |= CXTranslationUnit_SkipFunctionBodies; } if (options.testFlag(PrecompiledHeader)) { flags |= CXTranslationUnit_ForSerialization; } else { flags |= CXTranslationUnit_CacheCompletionResults #if CINDEX_VERSION_MINOR >= 32 | CXTranslationUnit_CreatePreambleOnFirstParse #endif | CXTranslationUnit_PrecompiledPreamble; if (environment.quality() == ClangParsingEnvironment::Unknown) { flags |= CXTranslationUnit_Incomplete; } } const auto tuUrl = environment.translationUnitUrl(); Q_ASSERT(!tuUrl.isEmpty()); const auto arguments = argsForSession(tuUrl.str(), options, environment.parserSettings()); QVector clangArguments; const auto& includes = environment.includes(); const auto& pchInclude = environment.pchInclude(); // uses QByteArray as smart-pointer for const char* ownership QVector smartArgs; smartArgs.reserve(includes.system.size() + includes.project.size() + pchInclude.isValid() + arguments.size() + 1); clangArguments.reserve(smartArgs.size()); std::transform(arguments.constBegin(), arguments.constEnd(), std::back_inserter(clangArguments), [] (const QByteArray &argument) { return argument.constData(); }); // NOTE: the PCH include must come before all other includes! if (pchInclude.isValid()) { clangArguments << "-include"; QByteArray pchFile = pchInclude.toLocalFile().toUtf8(); smartArgs << pchFile; clangArguments << pchFile.constData(); } if (hasQtIncludes(includes.system)) { const auto wrappedQtHeaders = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kdevclangsupport/wrappedQtHeaders"), QStandardPaths::LocateDirectory).toUtf8(); if (!wrappedQtHeaders.isEmpty()) { smartArgs << wrappedQtHeaders; clangArguments << "-isystem" << wrappedQtHeaders.constData(); const QByteArray qtCore = wrappedQtHeaders + "/QtCore"; smartArgs << qtCore; clangArguments << "-isystem" << qtCore.constData(); } } addIncludes(&clangArguments, &smartArgs, includes.system, "-isystem"); addIncludes(&clangArguments, &smartArgs, includes.project, "-I"); const auto& frameworkDirectories = environment.frameworkDirectories(); addFrameworkDirectories(&clangArguments, &smartArgs, frameworkDirectories.system, "-iframework"); addFrameworkDirectories(&clangArguments, &smartArgs, frameworkDirectories.project, "-F"); // libclang cannot find it's builtin dir automatically, we have to specify it manually - smartArgs << ClangIntegration::DUChainUtils::clangBuiltinIncludePath().toUtf8(); + smartArgs << ClangHelpers::clangBuiltinIncludePath().toUtf8(); clangArguments << "-isystem" << smartArgs.last().constData(); smartArgs << writeDefinesFile(environment.defines()); clangArguments << "-imacros" << smartArgs.last().constData(); // append extra args from environment variable static const auto extraArgs = ::extraArgs(); for (const QByteArray& arg : extraArgs) { clangArguments << arg.constData(); } QVector unsaved; //For PrecompiledHeader, we don't want unsaved contents (and contents.isEmpty()) if (!options.testFlag(PrecompiledHeader)) { unsaved = toClangApi(unsavedFiles); } // debugging: print hypothetical clang invocation including args (for easy c&p for local testing) if (qEnvironmentVariableIsSet("KDEV_CLANG_DISPLAY_ARGS")) { QTextStream out(stdout); out << "Invocation: clang"; foreach (const auto& arg, clangArguments) { out << " " << arg; } out << " " << tuUrl.byteArray().constData() << "\n"; } const CXErrorCode code = clang_parseTranslationUnit2( index->index(), tuUrl.byteArray().constData(), clangArguments.constData(), clangArguments.size(), unsaved.data(), unsaved.size(), flags, &m_unit ); if (code != CXError_Success) { qCWarning(KDEV_CLANG) << "clang_parseTranslationUnit2 return with error code" << code; if (!qEnvironmentVariableIsSet("KDEV_CLANG_DISPLAY_DIAGS")) { qCWarning(KDEV_CLANG) << " (start KDevelop with `KDEV_CLANG_DISPLAY_DIAGS=1 kdevelop` to see more diagnostics)"; } } if (m_unit) { setUnit(m_unit); m_environment = environment; if (options.testFlag(PrecompiledHeader)) { clang_saveTranslationUnit(m_unit, QByteArray(tuUrl.byteArray() + ".pch").constData(), CXSaveTranslationUnit_None); } } else { qCWarning(KDEV_CLANG) << "Failed to parse translation unit:" << tuUrl; } } ParseSessionData::~ParseSessionData() { clang_disposeTranslationUnit(m_unit); } QByteArray ParseSessionData::writeDefinesFile(const QMap& defines) { m_definesFile.open(); Q_ASSERT(m_definesFile.isWritable()); { QTextStream definesStream(&m_definesFile); // don't show warnings about redefined macros definesStream << "#pragma clang system_header\n"; for (auto it = defines.begin(); it != defines.end(); ++it) { if (it.key().startsWith(QLatin1String("__has_include(")) || it.key().startsWith(QLatin1String("__has_include_next("))) { continue; } definesStream << QStringLiteral("#define ") << it.key() << ' ' << it.value() << '\n'; } } m_definesFile.close(); if (qEnvironmentVariableIsSet("KDEV_CLANG_DISPLAY_DEFINES")) { QFile f(m_definesFile.fileName()); f.open(QIODevice::ReadOnly); Q_ASSERT(f.isReadable()); QTextStream out(stdout); out << "Defines file: " << f.fileName() << "\n" << f.readAll() << f.size() << "\n VS defines:" << defines.size() << "\n"; } return m_definesFile.fileName().toUtf8(); } 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()); } else { m_file = nullptr; } } ClangParsingEnvironment ParseSessionData::environment() const { return m_environment; } ParseSession::ParseSession(const ParseSessionData::Ptr& data) : d(data) { if (d) { ENSURE_CHAIN_NOT_LOCKED d->m_mutex.lock(); } } ParseSession::~ParseSession() { if (d) { d->m_mutex.unlock(); } } void ParseSession::setData(const ParseSessionData::Ptr& data) { if (data == d) { return; } if (d) { d->m_mutex.unlock(); } d = data; if (d) { ENSURE_CHAIN_NOT_LOCKED d->m_mutex.lock(); } } ParseSessionData::Ptr ParseSession::data() const { return d; } IndexedString ParseSession::languageString() { static const IndexedString lang("Clang"); return lang; } QList ParseSession::problemsForFile(CXFile file) const { if (!d) { return {}; } QList problems; // 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); CXSourceLocation location = clang_getDiagnosticLocation(diagnostic); CXFile diagnosticFile; clang_getFileLocation(location, &diagnosticFile, nullptr, nullptr, nullptr); // 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; clang_disposeDiagnostic(diagnostic); } // other problem sources TodoExtractor extractor(unit(), file); problems << extractor.problems(); #if CINDEX_VERSION_MINOR > 30 // note that the below warning is triggered on every reparse when there is a precompiled preamble // see also TestDUChain::testReparseIncludeGuard const QString path = QDir(ClangString(clang_getFileName(file)).toString()).canonicalPath(); const IndexedString indexedPath(path); if (ClangHelpers::isHeader(path) && !clang_isFileMultipleIncludeGuarded(unit(), file) && !clang_Location_isInSystemHeader(clang_getLocationForOffset(d->m_unit, file, 0))) { ProblemPointer problem(new Problem); problem->setSeverity(IProblem::Warning); problem->setDescription(i18n("Header is not guarded against multiple inclusions")); problem->setExplanation(i18n("The given header is not guarded against multiple inclusions, " "either with the conventional #ifndef/#define/#endif macro guards or with #pragma once.")); problem->setFinalLocation({indexedPath, KTextEditor::Range()}); problem->setSource(IProblem::Preprocessor); problems << problem; // TODO: Easy to add an assistant here that adds the guards -- any takers? } #endif return problems; } CXTranslationUnit ParseSession::unit() const { return d ? d->m_unit : nullptr; } CXFile ParseSession::file(const QByteArray& path) const { return clang_getFile(unit(), path.constData()); } CXFile ParseSession::mainFile() const { return d ? d->m_file : nullptr; } bool ParseSession::reparse(const QVector& unsavedFiles, const ClangParsingEnvironment& environment) { if (!d || environment != d->m_environment) { return false; } auto unsaved = toClangApi(unsavedFiles); const auto code = clang_reparseTranslationUnit(d->m_unit, unsaved.size(), unsaved.data(), clang_defaultReparseOptions(d->m_unit)); if (code != CXError_Success) { qCWarning(KDEV_CLANG) << "clang_reparseTranslationUnit return with error code" << code; // if error code != 0 => clang_reparseTranslationUnit invalidates the old translation unit => clean up clang_disposeTranslationUnit(d->m_unit); d->setUnit(nullptr); return false; } // update state d->setUnit(d->m_unit); return true; } ClangParsingEnvironment ParseSession::environment() const { return d->m_environment; }