diff --git a/plugins/clang/clangparsejob.cpp b/plugins/clang/clangparsejob.cpp index a1975503a7..a0b0ca801b 100644 --- a/plugins/clang/clangparsejob.cpp +++ b/plugins/clang/clangparsejob.cpp @@ -1,388 +1,392 @@ /* This file is part of KDevelop Copyright 2013 Olivier de Gaalon Copyright 2013 Milian Wolff 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 "clangparsejob.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "clangsettings/clangsettingsmanager.h" #include "duchain/clanghelpers.h" #include "duchain/clangpch.h" #include "duchain/duchainutils.h" #include "duchain/parsesession.h" #include "duchain/clangindex.h" #include "duchain/clangparsingenvironmentfile.h" #include "util/clangdebug.h" #include "util/clangtypes.h" #include "util/clangutils.h" #include "clangsupport.h" #include "duchain/documentfinderhelpers.h" #include #include #include #include #include using namespace KDevelop; namespace { QString findConfigFile(const QString& forFile, const QString& configFileName) { QDir dir = QFileInfo(forFile).dir(); while (dir.exists()) { const QFileInfo customIncludePaths(dir, configFileName); if (customIncludePaths.exists()) { return customIncludePaths.absoluteFilePath(); } if (!dir.cdUp()) { break; } } return {}; } Path::List readPathListFile(const QString& filepath) { if (filepath.isEmpty()) { return {}; } QFile f(filepath); if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { return {}; } const QString text = QString::fromLocal8Bit(f.readAll()); const QStringList lines = text.split(QLatin1Char('\n'), QString::SkipEmptyParts); Path::List paths; paths.reserve(lines.length()); for (const auto& line : lines) { paths << Path(line); } return paths; } /** * File should contain the header to precompile and use while parsing * @returns the first path in the file */ Path userDefinedPchIncludeForFile(const QString& sourcefile) { const QString pchIncludeFilename = QStringLiteral(".kdev_pch_include"); const auto paths = readPathListFile(findConfigFile(sourcefile, pchIncludeFilename)); return paths.isEmpty() ? Path() : paths.first(); } ProjectFileItem* findProjectFileItem(const IndexedString& url, bool* hasBuildSystemInfo) { ProjectFileItem* file = nullptr; *hasBuildSystemInfo = false; const auto& projects = ICore::self()->projectController()->projects(); for (auto project : projects) { auto files = project->filesForPath(url); if (files.isEmpty()) { continue; } file = files.last(); // A file might be defined in different targets. // Prefer file items defined inside a target with non-empty includes. for (auto f: files) { if (!dynamic_cast(f->parent())) { continue; } file = f; if (!IDefinesAndIncludesManager::manager()->includes(f, IDefinesAndIncludesManager::ProjectSpecific).isEmpty()) { break; } } } if (file && file->project()) { if (auto bsm = file->project()->buildSystemManager()) { *hasBuildSystemInfo = bsm->hasBuildInfo(file); } } return file; } ClangParsingEnvironmentFile* parsingEnvironmentFile(const TopDUContext* context) { return dynamic_cast(context->parsingEnvironmentFile().data()); } DocumentChangeTracker* trackerForUrl(const IndexedString& url) { return ICore::self()->languageController()->backgroundParser()->trackerForUrl(url); } } ClangParseJob::ClangParseJob(const IndexedString& url, ILanguageSupport* languageSupport) : ParseJob(url, languageSupport) { const auto tuUrl = clang()->index()->translationUnitForUrl(url); bool hasBuildSystemInfo; if (auto file = findProjectFileItem(tuUrl, &hasBuildSystemInfo)) { m_environment.addIncludes(IDefinesAndIncludesManager::manager()->includes(file)); m_environment.addFrameworkDirectories(IDefinesAndIncludesManager::manager()->frameworkDirectories(file)); m_environment.addDefines(IDefinesAndIncludesManager::manager()->defines(file)); m_environment.setParserSettings(ClangSettingsManager::self()->parserSettings(file)); + if (hasBuildSystemInfo) { + // Assume the builder invokes the compiler in the build directory. + m_environment.setWorkingDirectory(file->project()->buildSystemManager()->buildDirectory(file)); + } } else { m_environment.addIncludes(IDefinesAndIncludesManager::manager()->includes(tuUrl.str())); m_environment.addFrameworkDirectories(IDefinesAndIncludesManager::manager()->frameworkDirectories(tuUrl.str())); m_environment.addDefines(IDefinesAndIncludesManager::manager()->defines(tuUrl.str())); m_environment.setParserSettings(ClangSettingsManager::self()->parserSettings(tuUrl.str())); } const bool isSource = ClangHelpers::isSource(tuUrl.str()); m_environment.setQuality( isSource ? (hasBuildSystemInfo ? ClangParsingEnvironment::BuildSystem : ClangParsingEnvironment::Source) : ClangParsingEnvironment::Unknown ); m_environment.setTranslationUnitUrl(tuUrl); Path::List projectPaths; const auto& projects = ICore::self()->projectController()->projects(); projectPaths.reserve(projects.size()); for (auto project : projects) { projectPaths.append(project->path()); } m_environment.setProjectPaths(projectPaths); m_unsavedFiles = ClangUtils::unsavedFiles(); const auto documents = ICore::self()->documentController()->openDocuments(); for (auto* document : documents) { auto textDocument = document->textDocument(); if ( !textDocument ) { continue; } const IndexedString indexedUrl(textDocument->url()); if (indexedUrl == tuUrl) { m_tuDocumentIsUnsaved = true; } m_unsavedRevisions.insert(indexedUrl, ModificationRevision::revisionForFile(indexedUrl)); } if (auto tracker = trackerForUrl(url)) { tracker->reset(); } } ClangSupport* ClangParseJob::clang() const { return static_cast(languageSupport()); } void ClangParseJob::run(ThreadWeaver::JobPointer /*self*/, ThreadWeaver::Thread* /*thread*/) { QReadLocker parseLock(languageSupport()->parseLock()); if (abortRequested()) { return; } { const auto tuUrlStr = m_environment.translationUnitUrl().str(); if (!m_tuDocumentIsUnsaved && !QFile::exists(tuUrlStr)) { // maybe we requested a parse job some time ago but now the file // does not exist anymore. return early then clang()->index()->unpinTranslationUnitForUrl(document()); return; } m_environment.addIncludes(IDefinesAndIncludesManager::manager()->includesInBackground(tuUrlStr)); m_environment.addFrameworkDirectories(IDefinesAndIncludesManager::manager()->frameworkDirectoriesInBackground(tuUrlStr)); m_environment.addDefines(IDefinesAndIncludesManager::manager()->definesInBackground(tuUrlStr)); m_environment.addParserArguments(IDefinesAndIncludesManager::manager()->parserArgumentsInBackground(tuUrlStr)); m_environment.setPchInclude(userDefinedPchIncludeForFile(tuUrlStr)); } if (abortRequested()) { return; } // NOTE: we must have all declarations, contexts and uses available for files that are opened in the editor // it is very hard to check this for all included files of this TU, and previously lead to problems // when we tried to skip function bodies as an optimization for files that where not open in the editor. // now, we always build everything, which is correct but a tad bit slower. we can try to optimize later. setMinimumFeatures(static_cast(minimumFeatures() | TopDUContext::AllDeclarationsContextsAndUses)); if (minimumFeatures() & AttachASTWithoutUpdating) { // The context doesn't need to be updated, but has no AST attached (restored from disk), // so attach AST to it, without updating DUChain ParseSession session(createSessionData()); DUChainWriteLocker lock; auto ctx = DUChainUtils::standardContextForUrl(document().toUrl()); if (!ctx) { clangDebug() << "Lost context while attaching AST"; return; } ctx->setAst(IAstContainer::Ptr(session.data())); if (minimumFeatures() & UpdateHighlighting) { lock.unlock(); languageSupport()->codeHighlighting()->highlightDUChain(ctx); } return; } { UrlParseLock urlLock(document()); if (abortRequested() || !isUpdateRequired(ParseSession::languageString())) { return; } } ParseSession session(ClangIntegration::DUChainUtils::findParseSessionData(document(), m_environment.translationUnitUrl())); if (abortRequested()) { return; } if (!session.data() || !session.reparse(m_unsavedFiles, m_environment)) { session.setData(createSessionData()); } if (!session.unit()) { // failed to parse file, unpin and don't try again clang()->index()->unpinTranslationUnitForUrl(document()); return; } if (!clang_getFile(session.unit(), document().byteArray().constData())) { // this parse job's document does not exist in the pinned translation unit // so we need to unpin and re-add this document // Ideally we'd reset m_environment and session, but this is much simpler // and shouldn't be a common case clang()->index()->unpinTranslationUnitForUrl(document()); if (!(minimumFeatures() & Rescheduled)) { auto features = static_cast(minimumFeatures() | Rescheduled); ICore::self()->languageController()->backgroundParser()->addDocument(document(), features, priority()); } return; } Imports imports = ClangHelpers::tuImports(session.unit()); IncludeFileContexts includedFiles; if (auto pch = clang()->index()->pch(m_environment)) { auto pchFile = pch->mapFile(session.unit()); includedFiles = pch->mapIncludes(session.unit()); includedFiles.insert(pchFile, pch->context()); auto tuFile = clang_getFile(session.unit(), m_environment.translationUnitUrl().byteArray().constData()); imports.insert(tuFile, { pchFile, CursorInRevision(0, 0) } ); } if (abortRequested()) { return; } auto context = ClangHelpers::buildDUChain(session.mainFile(), imports, session, minimumFeatures(), includedFiles, clang()->index(), [this] { return abortRequested(); }); setDuChain(context); if (abortRequested()) { return; } if (context) { if (minimumFeatures() & TopDUContext::AST) { DUChainWriteLocker lock; context->setAst(IAstContainer::Ptr(session.data())); } #ifdef QT_DEBUG DUChainReadLocker lock; auto file = parsingEnvironmentFile(context); Q_ASSERT(file); // verify that features and environment where properly set in ClangHelpers::buildDUChain Q_ASSERT(file->featuresSatisfied(TopDUContext::Features(minimumFeatures() & ~TopDUContext::ForceUpdateRecursive))); if (trackerForUrl(context->url())) { Q_ASSERT(file->featuresSatisfied(TopDUContext::AllDeclarationsContextsAndUses)); } #endif } for (const auto& context : qAsConst(includedFiles)) { if (!context) { continue; } { // prefer the editor modification revision, instead of the on-disk revision auto it = m_unsavedRevisions.find(context->url()); if (it != m_unsavedRevisions.end()) { DUChainWriteLocker lock; auto file = parsingEnvironmentFile(context); Q_ASSERT(file); file->setModificationRevision(it.value()); } } if (trackerForUrl(context->url())) { if (clang()->index()->translationUnitForUrl(context->url()) == m_environment.translationUnitUrl()) { // cache the parse session and the contained translation unit for this chain // this then allows us to quickly reparse the document if it is changed by // the user // otherwise no editor component is open for this document and we can dispose // the TU to save memory // share the session data with all contexts that are pinned to this TU DUChainWriteLocker lock; context->setAst(IAstContainer::Ptr(session.data())); } languageSupport()->codeHighlighting()->highlightDUChain(context); } } } ParseSessionData::Ptr ClangParseJob::createSessionData() const { return ParseSessionData::Ptr(new ParseSessionData(m_unsavedFiles, clang()->index(), m_environment, ParseSessionData::NoOption)); } const ParsingEnvironment* ClangParseJob::environment() const { return &m_environment; } diff --git a/plugins/clang/duchain/clangparsingenvironment.cpp b/plugins/clang/duchain/clangparsingenvironment.cpp index e9a75659d5..88f671ae10 100644 --- a/plugins/clang/duchain/clangparsingenvironment.cpp +++ b/plugins/clang/duchain/clangparsingenvironment.cpp @@ -1,175 +1,185 @@ /* * This file is part of KDevelop * * Copyright 2014 Milian Wolff * * This program 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 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, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "clangparsingenvironment.h" using namespace KDevelop; int ClangParsingEnvironment::type() const { return CppParsingEnvironment; } void ClangParsingEnvironment::setProjectPaths(const Path::List& projectPaths) { m_projectPaths = projectPaths; } Path::List ClangParsingEnvironment::projectPaths() const { return m_projectPaths; } void ClangParsingEnvironment::addIncludes(const Path::List& includes) { m_includes += includes; } void ClangParsingEnvironment::addFrameworkDirectories(const KDevelop::Path::List& frameworkDirectories) { m_frameworkDirectories += frameworkDirectories; } template static PathType appendPaths(const KDevelop::Path::List &paths, const KDevelop::Path::List &projectPaths) { PathType ret; ret.project.reserve(paths.size()); ret.system.reserve(paths.size()); for (const auto& path : paths) { bool inProject = false; for (const auto& project : projectPaths) { if (project.isParentOf(path) || project == path) { inProject = true; break; } } if (inProject) { ret.project.append(path); } else { ret.system.append(path); } } return ret; } ClangParsingEnvironment::IncludePaths ClangParsingEnvironment::includes() const { return appendPaths(m_includes, m_projectPaths); } ClangParsingEnvironment::FrameworkDirectories ClangParsingEnvironment::frameworkDirectories() const { return appendPaths(m_frameworkDirectories, m_projectPaths); } void ClangParsingEnvironment::addDefines(const QHash& defines) { for (auto it = defines.constBegin(); it != defines.constEnd(); ++it) { m_defines[it.key()] = it.value(); } } QMap ClangParsingEnvironment::defines() const { return m_defines; } void ClangParsingEnvironment::setPchInclude(const Path& path) { m_pchInclude = path; } Path ClangParsingEnvironment::pchInclude() const { return m_pchInclude; } +void ClangParsingEnvironment::setWorkingDirectory(const Path& path) +{ + m_workingDirectory = path; +} + +Path ClangParsingEnvironment::workingDirectory() const +{ + return m_workingDirectory; +} + void ClangParsingEnvironment::setTranslationUnitUrl(const IndexedString& url) { m_tuUrl = url; } IndexedString ClangParsingEnvironment::translationUnitUrl() const { return m_tuUrl; } void ClangParsingEnvironment::setQuality(Quality quality) { m_quality = quality; } ClangParsingEnvironment::Quality ClangParsingEnvironment::quality() const { return m_quality; } uint ClangParsingEnvironment::hash() const { KDevHash hash; hash << m_defines.size(); for (auto it = m_defines.constBegin(); it != m_defines.constEnd(); ++it) { hash << qHash(it.key()) << qHash(it.value()); } hash << m_includes.size(); for (const auto& include : m_includes) { hash << qHash(include); } hash << m_frameworkDirectories.size(); for (const auto& fwDir : m_frameworkDirectories) { hash << qHash(fwDir); } hash << qHash(m_pchInclude); hash << qHash(m_parserSettings.parserOptions); return hash; } bool ClangParsingEnvironment::operator==(const ClangParsingEnvironment& other) const { return m_defines == other.m_defines && m_includes == other.m_includes && m_frameworkDirectories == other.m_frameworkDirectories && m_pchInclude == other.m_pchInclude && m_quality == other.m_quality && m_tuUrl == other.m_tuUrl && m_parserSettings == other.m_parserSettings; } void ClangParsingEnvironment::setParserSettings(const ParserSettings& parserSettings) { m_parserSettings = parserSettings; } ParserSettings ClangParsingEnvironment::parserSettings() const { return m_parserSettings; } void ClangParsingEnvironment::addParserArguments(const QString& parserArguments) { m_parserSettings.parserOptions += QLatin1Char(' ') + parserArguments; } diff --git a/plugins/clang/duchain/clangparsingenvironment.h b/plugins/clang/duchain/clangparsingenvironment.h index 574d7dae39..1ddd02df44 100644 --- a/plugins/clang/duchain/clangparsingenvironment.h +++ b/plugins/clang/duchain/clangparsingenvironment.h @@ -1,131 +1,135 @@ /* * This file is part of KDevelop * * Copyright 2014 Milian Wolff * * This program 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 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, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef CLANGPARSINGENVIRONMENT_H #define CLANGPARSINGENVIRONMENT_H #include #include #include "clangprivateexport.h" #include "clangsettings/clangsettingsmanager.h" class KDEVCLANGPRIVATE_EXPORT ClangParsingEnvironment : public KDevelop::ParsingEnvironment { public: ~ClangParsingEnvironment() override = default; int type() const override; /** * Sets the list of project paths. * * Any include path outside these project paths is considered * to be a system include. */ void setProjectPaths(const KDevelop::Path::List& projectPaths); KDevelop::Path::List projectPaths() const; /** * Add the given list of @p include paths to this environment. */ void addIncludes(const KDevelop::Path::List& includes); /** * Add the given list of @p framework-directories to this environment. */ void addFrameworkDirectories(const KDevelop::Path::List& frameworkDirectories); struct IncludePaths { /// This list contains all include paths outside the known projects paths. KDevelop::Path::List system; /// This list contains all include paths inside the known projects paths. KDevelop::Path::List project; }; /** * Returns the list of includes, split into a list of system includes and project includes. */ IncludePaths includes() const; struct FrameworkDirectories { /// This list contains all framework directories outside the known projects paths. KDevelop::Path::List system; /// This list contains all framework directories inside the known projects paths. KDevelop::Path::List project; }; /** * Returns the list of framework directories, split into a list of system paths and project paths. */ FrameworkDirectories frameworkDirectories() const; void addDefines(const QHash& defines); QMap defines() const; void setPchInclude(const KDevelop::Path& path); KDevelop::Path pchInclude() const; + void setWorkingDirectory(const KDevelop::Path& path); + KDevelop::Path workingDirectory() const; + void setTranslationUnitUrl(const KDevelop::IndexedString& url); KDevelop::IndexedString translationUnitUrl() const; enum Quality { Unknown, Source, BuildSystem }; void setQuality(Quality quality); Quality quality() const; void setParserSettings(const ParserSettings& arguments); ParserSettings parserSettings() const; void addParserArguments(const QString &parserArguments); /** * Hash all contents of this environment and return the result. * * This is useful for a quick comparison, and enough to store on-disk * to figure out if the environment changed or not. */ uint hash() const; bool operator==(const ClangParsingEnvironment& other) const; bool operator!=(const ClangParsingEnvironment& other) const { return !(*this == other); } private: KDevelop::Path::List m_projectPaths; KDevelop::Path::List m_includes; KDevelop::Path::List m_frameworkDirectories; // NOTE: As elements in QHash stored in an unordered sequence, we're using QMap instead QMap m_defines; KDevelop::Path m_pchInclude; + KDevelop::Path m_workingDirectory; KDevelop::IndexedString m_tuUrl; Quality m_quality = Unknown; ParserSettings m_parserSettings; }; #endif // CLANGPARSINGENVIRONMENT_H diff --git a/plugins/clang/duchain/parsesession.cpp b/plugins/clang/duchain/parsesession.cpp index 744c93a778..2de2b0b519 100644 --- a/plugins/clang/duchain/parsesession.cpp +++ b/plugins/clang/duchain/parsesession.cpp @@ -1,613 +1,620 @@ /* 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 "clangdiagnosticevaluator.h" #include "todoextractor.h" #include "clanghelpers.h" #include "clangindex.h" #include "clangparsingenvironment.h" #include "util/clangdebug.h" #include "util/clangtypes.h" #include "util/clangutils.h" #include "headerguardassistant.h" #include #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) || path.endsWith(QLatin1String(".cuh"), Qt::CaseInsensitive)) { auto result = parserSettings.toClangAPI(); result.append(QByteArrayLiteral("-xcuda")); return result; } 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 << ClangHelpers::clangBuiltinIncludePath().toUtf8(); clangArguments << "-isystem" << smartArgs.last().constData(); smartArgs << writeDefinesFile(environment.defines()); clangArguments << "-imacros" << smartArgs.last().constData(); + if (!environment.workingDirectory().isEmpty()) { + QByteArray workingDirectory = environment.workingDirectory().toLocalFile().toUtf8(); + workingDirectory.prepend("-working-directory"); + smartArgs << workingDirectory; + clangArguments << workingDirectory.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"; for (const auto& arg : qAsConst(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; } 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) { 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); 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; } problems << ((diagnosticFile == file) ? getOrCreateProblem(i, diagnostic) : createExternalProblem(i, diagnostic, ki18n("In included file: %1"))); 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))) { QExplicitlySharedDataPointer problem(new StaticAssistantProblem); 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.")); const KTextEditor::Range problemRange(0, 0, KDevelop::createCodeRepresentation(indexedPath)->lines(), 0); problem->setFinalLocation(DocumentRange{indexedPath, problemRange}); problem->setSource(IProblem::Preprocessor); problem->setSolutionAssistant(KDevelop::IAssistant::Ptr(new HeaderGuardAssistant(d->m_unit, file))); problems << problem; } #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; }