diff --git a/languages/clang/codecompletion/completionhelper.cpp b/languages/clang/codecompletion/completionhelper.cpp index 5565a727ae..6e7b6b36ef 100644 --- a/languages/clang/codecompletion/completionhelper.cpp +++ b/languages/clang/codecompletion/completionhelper.cpp @@ -1,379 +1,379 @@ /* * This file is part of KDevelop * Copyright 2014 David Stevens * * 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 "completionhelper.h" #include "../duchain/cursorkindtraits.h" #include "../duchain/parsesession.h" #include "../duchain/documentfinderhelpers.h" #include "../duchain/clanghelpers.h" #include "../util/clangdebug.h" #include "../util/clangtypes.h" #include "../util/clangutils.h" #include #include #include namespace { struct OverrideInfo { FunctionOverrideList* functions; QStringList templateTypes; QMap templateTypeMap; }; struct ImplementsInfo { CXCursor origin; CXCursor top; FunctionImplementsList* prototypes; QVector originScope; QVector fileFilter; int depth; QString templatePrefix; }; CXChildVisitResult templateParamsHelper(CXCursor cursor, CXCursor /*parent*/, CXClientData data) { CXCursorKind kind = clang_getCursorKind(cursor); if (kind == CXCursor_TemplateTypeParameter || kind == CXCursor_TemplateTemplateParameter || kind == CXCursor_NonTypeTemplateParameter) { (*static_cast(data)).append(ClangString(clang_getCursorSpelling(cursor)).toString()); } return CXChildVisit_Continue; } QStringList templateParams(CXCursor cursor) { QStringList types; clang_visitChildren(cursor, templateParamsHelper, &types); return types; } FuncOverrideInfo processCXXMethod(CXCursor cursor, OverrideInfo* info) { QStringList params; int numArgs = clang_Cursor_getNumArguments(cursor); for (int i = 0; i < numArgs; i++) { CXCursor arg = clang_Cursor_getArgument(cursor, i); QString id = ClangString(clang_getCursorDisplayName(arg)).toString(); QString type = ClangString(clang_getTypeSpelling(clang_getCursorType(arg))).toString(); if (info->templateTypeMap.contains(type)) { type = info->templateTypeMap.value(type); } params << type + QLatin1Char(' ') + id; } FuncOverrideInfo fp; QString retType = ClangString(clang_getTypeSpelling(clang_getCursorResultType(cursor))).toString(); if (info->templateTypeMap.contains(retType)) { retType = info->templateTypeMap.value(retType); } fp.returnType = retType; fp.name = ClangString(clang_getCursorSpelling(cursor)).toString(); fp.params = params; fp.isVirtual = clang_CXXMethod_isPureVirtual(cursor); fp.isConst = clang_CXXMethod_isConst(cursor); return fp; } CXChildVisitResult baseClassVisitor(CXCursor cursor, CXCursor /*parent*/, CXClientData data); void processBaseClass(CXCursor cursor, CXCursor parent, FunctionOverrideList* functionList) { QStringList concrete; CXCursor ref = clang_getCursorReferenced(cursor); if (clang_equalCursors(ref, parent)) { return; } CXCursor isTemplate = clang_getSpecializedCursorTemplate(ref); if (!clang_Cursor_isNull(isTemplate)) { concrete = ClangUtils::templateArgumentTypes(ref); ref = isTemplate; } OverrideInfo info{functionList, concrete, {}}; clang_visitChildren(ref, baseClassVisitor, &info); } CXChildVisitResult baseClassVisitor(CXCursor cursor, CXCursor parent, CXClientData data) { QString templateParam; OverrideInfo* info = static_cast(data); switch(clang_getCursorKind(cursor)) { case CXCursor_TemplateTypeParameter: templateParam = ClangString(clang_getCursorSpelling(cursor)).toString(); // TODO: this is probably just a hotfix, find a proper solution to // https://bugs.kde.org/show_bug.cgi?id=355163 if (info->templateTypes.size() > info->templateTypeMap.size()) { info->templateTypeMap.insert(templateParam, info->templateTypes.at(info->templateTypeMap.size())); } return CXChildVisit_Continue; case CXCursor_CXXBaseSpecifier: processBaseClass(cursor, parent, info->functions); return CXChildVisit_Continue; case CXCursor_CXXMethod: if (clang_CXXMethod_isVirtual(cursor)) { info->functions->append(processCXXMethod(cursor, info)); } return CXChildVisit_Continue; default: return CXChildVisit_Continue; } } CXChildVisitResult findBaseVisitor(CXCursor cursor, CXCursor parent, CXClientData data) { auto cursorKind = clang_getCursorKind(cursor); if (cursorKind == CXCursor_CXXBaseSpecifier) { processBaseClass(cursor, parent, static_cast(data)); } else if (cursorKind == CXCursor_CXXMethod) { if (!clang_CXXMethod_isVirtual(cursor)) { return CXChildVisit_Continue; } auto info = static_cast(data); OverrideInfo overrideInfo {info, {}, {}}; auto methodInfo = processCXXMethod(cursor, &overrideInfo); if (info->contains(methodInfo)) { // This method is already implemented, remove it from the list of methods that can be overridden. info->remove(info->indexOf(methodInfo), 1); } } return CXChildVisit_Continue; } // TODO: make sure we only skip this in classes that actually inherit QObject bool isQtMocFunction(CXCursor cursor) { static const QByteArray mocFunctions[] = { QByteArrayLiteral("metaObject"), QByteArrayLiteral("qt_metacast"), QByteArrayLiteral("qt_metacall"), QByteArrayLiteral("qt_static_metacall"), }; const ClangString function(clang_getCursorSpelling(cursor)); auto it = std::find(std::begin(mocFunctions), std::end(mocFunctions), function.toByteArray()); if (it != std::end(mocFunctions)) { auto range = ClangRange(clang_getCursorExtent(cursor)).toRange(); // tokenizing the above range fails for some reason, but // if the function comes from a range that happens to be just as wide // as the expected Q_OBJECT macro, then we assume this is a moc function // and skip it. return range.onSingleLine() && range.columnWidth() == strlen("Q_OBJECT"); } return false; } CXChildVisitResult declVisitor(CXCursor cursor, CXCursor parent, CXClientData d) { CXCursorKind kind = clang_getCursorKind(cursor); struct ImplementsInfo* data = static_cast(d); auto location = clang_getCursorLocation(cursor); if (clang_Location_isInSystemHeader(location)) { // never offer implementation items for system headers // TODO: also filter out non-system files unrelated to the current file // e.g. based on the path or similar return CXChildVisit_Continue; } CXFile file = nullptr; clang_getFileLocation(location, &file, nullptr, nullptr, nullptr); if (!data->fileFilter.contains(file)) { return CXChildVisit_Continue; } //Recurse into cursors which could contain a function declaration if (ClangUtils::isScopeKind(kind)) { //Don't enter a scope that branches from the origin's scope if (data->depth < data->originScope.count() && !clang_equalCursors(cursor, data->originScope.at(data->depth))) { return CXChildVisit_Continue; } // we must not declare a function outside of its anonymous namespace, so // don't recurse into anonymous namespaces if we are not in one already if (kind == CXCursor_Namespace && !clang_equalCursors(data->origin, cursor) && ClangString(clang_getCursorDisplayName(cursor)).isEmpty()) { return CXChildVisit_Continue; } QString templatePrefix; if (data->depth >= data->originScope.count()) { if (kind == CXCursor_ClassTemplate || kind == CXCursor_ClassTemplatePartialSpecialization) { //If we're at a template, we need to construct the template //which goes at the front of the prototype const QStringList templateTypes = templateParams(kind == CXCursor_ClassTemplate ? cursor : clang_getSpecializedCursorTemplate(cursor)); templatePrefix = QLatin1String("template<") + templateTypes.join(QStringLiteral(", typename ")) + QLatin1String("> "); } } ImplementsInfo info{data->origin, data->top, data->prototypes, data->originScope, data->fileFilter, data->depth + 1, data->templatePrefix + templatePrefix}; clang_visitChildren(cursor, declVisitor, &info); return CXChildVisit_Continue; } if (data->depth < data->originScope.count()) { return CXChildVisit_Continue; } //If the current cursor is not a function or if it is already defined, there's nothing to do here if (!CursorKindTraits::isFunction(clang_getCursorKind(cursor)) || !clang_equalCursors(clang_getNullCursor(), clang_getCursorDefinition(cursor))) { return CXChildVisit_Continue; } // don't try to implement pure virtual functions if (clang_CXXMethod_isPureVirtual(cursor)) { return CXChildVisit_Continue; } CXCursor origin = data->origin; //Don't try to redefine class/structure/union members if (clang_equalCursors(origin, parent) && (clang_getCursorKind(origin) != CXCursor_Namespace && !clang_equalCursors(origin, data->top))) { return CXChildVisit_Continue; } // skip explicitly defaulted/deleted functions as they don't need a definition if (ClangUtils::isExplicitlyDefaultedOrDeleted(cursor)) { return CXChildVisit_Continue; } if (isQtMocFunction(cursor) || ClangUtils::specialQtAttributes(cursor) == ClangUtils::QtSignalAttribute) { return CXChildVisit_Continue; } const auto scope = ClangUtils::getScope(cursor, data->origin); QString signature = ClangUtils::getCursorSignature(cursor, scope); QString returnType, rest; if (kind != CXCursor_Constructor && kind != CXCursor_Destructor) { int spaceIndex = signature.indexOf(QLatin1Char(' ')); returnType = signature.left(spaceIndex); rest = signature.right(signature.count() - spaceIndex - 1); } else { rest = signature; } //TODO Add support for pure virtual functions ReferencedTopDUContext top; { DUChainReadLocker lock; top = DUChain::self()->chainForDocument(ClangString(clang_getFileName(file)).toIndexed()); } - DeclarationPointer declaration = ClangHelpers::findDeclaration(clang_getCursorLocation(cursor), top); + DeclarationPointer declaration = ClangHelpers::findDeclaration(clang_getCursorLocation(cursor), QualifiedIdentifier(), top); data->prototypes->append(FuncImplementInfo{kind == CXCursor_Constructor, kind == CXCursor_Destructor, data->templatePrefix, returnType, rest, declaration}); return CXChildVisit_Continue; } } bool FuncOverrideInfo::operator==(const FuncOverrideInfo& rhs) const { return std::make_tuple(returnType, name, params, isConst) == std::make_tuple(rhs.returnType, rhs.name, rhs.params, rhs.isConst); } CompletionHelper::CompletionHelper() { } void CompletionHelper::computeCompletions(const ParseSession& session, CXFile file, const KTextEditor::Cursor& position) { const auto unit = session.unit(); CXSourceLocation location = clang_getLocation(unit, file, position.line() + 1, position.column() + 1); if (clang_equalLocations(clang_getNullLocation(), location)) { clangDebug() << "Completion helper given invalid position " << position << " in file " << clang_getFileName(file); return; } CXCursor topCursor = clang_getTranslationUnitCursor(unit); CXCursor currentCursor = clang_getCursor(unit, location); if (clang_getCursorKind(currentCursor) == CXCursor_NoDeclFound) { currentCursor = topCursor; } else if (KTextEditor::Cursor(ClangLocation(clang_getCursorLocation(currentCursor))) >= ClangLocation(location)) { currentCursor = clang_getCursorLexicalParent(currentCursor); } clang_visitChildren(currentCursor, findBaseVisitor, &m_overrides); if (clang_getCursorKind(currentCursor) == CXCursor_Namespace || clang_equalCursors(topCursor, currentCursor)) { QVector scopes; if (!clang_equalCursors(topCursor, currentCursor)) { CXCursor search = currentCursor; while (!clang_equalCursors(search, topCursor)) { scopes.append(clang_getCanonicalCursor(search)); search = clang_getCursorSemanticParent(search); } std::reverse(scopes.begin(), scopes.end()); } QVector fileFilter; fileFilter << file; const auto& buddies = DocumentFinderHelpers::getPotentialBuddies(QUrl::fromLocalFile(ClangString(clang_getFileName(file)).toString())); foreach(const auto& buddy, buddies) { auto buddyFile = clang_getFile(unit, qPrintable(buddy.toLocalFile())); if (buddyFile) { fileFilter << buddyFile; } } ImplementsInfo info{currentCursor, topCursor, &m_implements, scopes, fileFilter, 0, QString()}; clang_visitChildren(topCursor, declVisitor, &info); } } FunctionOverrideList CompletionHelper::overrides() const { return m_overrides; } FunctionImplementsList CompletionHelper::implements() const { return m_implements; } diff --git a/languages/clang/duchain/clanghelpers.cpp b/languages/clang/duchain/clanghelpers.cpp index cea8cd92a4..4be6e6b801 100644 --- a/languages/clang/duchain/clanghelpers.cpp +++ b/languages/clang/duchain/clanghelpers.cpp @@ -1,312 +1,345 @@ /* * 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 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 ReferencedTopDUContext& top) +DeclarationPointer ClangHelpers::findDeclaration(CXSourceLocation location, QualifiedIdentifier id, const ReferencedTopDUContext& top) { if (!top) { // may happen for cyclic includes return {}; } auto cursor = CursorInRevision(ClangLocation(location)); DUChainReadLocker lock; + + QList decls; + if (!id.isEmpty()) + { + decls = top->findDeclarations(id); + foreach (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 {}; } - return findDeclaration(location, includes.value(file)); + // 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; auto decls = context->findDeclarations(qualifiedIdentifier, CursorInRevision(ClangLocation(clang_getCursorLocation(cursor))) ); foreach (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); }); } diff --git a/languages/clang/duchain/clanghelpers.h b/languages/clang/duchain/clanghelpers.h index 1501ade3f6..cf29271acf 100644 --- a/languages/clang/duchain/clanghelpers.h +++ b/languages/clang/duchain/clanghelpers.h @@ -1,103 +1,103 @@ /* * 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; }; using Imports = QMultiHash; using IncludeFileContexts = QHash; namespace ClangHelpers { -KDevelop::DeclarationPointer findDeclaration(CXSourceLocation cursor, const KDevelop::ReferencedTopDUContext& top); +KDevelop::DeclarationPointer findDeclaration(CXSourceLocation cursor, 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 @p 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 @param tu */ KDEVCLANGPRIVATE_EXPORT Imports tuImports(CXTranslationUnit tu); /** * Recursively builds a duchain with the specified @param features for the * @param file and each of its @param imports using the TU from @param session. * The resulting contexts are placed in @param includedFiles. * @returns the context created for @param 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 @p path has the extension of a C++ source file */ KDEVCLANGPRIVATE_EXPORT bool isSource(const QString& path); /** * @return True if the given file @p path has the extension of a C++ header file */ KDEVCLANGPRIVATE_EXPORT bool isHeader(const QString& path); } #endif //CLANGHELPERS_H diff --git a/languages/clang/tests/test_duchain.cpp b/languages/clang/tests/test_duchain.cpp index b1bc3d26f2..979b65fdd4 100644 --- a/languages/clang/tests/test_duchain.cpp +++ b/languages/clang/tests/test_duchain.cpp @@ -1,1810 +1,1875 @@ /* * Copyright 2014 Milian Wolff * Copyright 2014 Kevin Funk * Copyright 2015 Sergey Kalinichev * * 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 "test_duchain.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "duchain/clangparsingenvironmentfile.h" #include "duchain/clangparsingenvironment.h" #include "duchain/parsesession.h" #include #include QTEST_MAIN(TestDUChain); using namespace KDevelop; class TestEnvironmentProvider final : public IDefinesAndIncludesManager::BackgroundProvider { public: ~TestEnvironmentProvider() override = default; QHash< QString, QString > definesInBackground(const QString& /*path*/) const override { return defines; } Path::List includesInBackground(const QString& /*path*/) const override { return includes; } IDefinesAndIncludesManager::Type type() const override { return IDefinesAndIncludesManager::UserDefined; } QHash defines; Path::List includes; }; TestDUChain::~TestDUChain() = default; void TestDUChain::initTestCase() { QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\nkdevelop.plugins.clang.debug=true\n")); QVERIFY(qputenv("KDEV_DISABLE_PLUGINS", "kdevcppsupport")); QVERIFY(qputenv("KDEV_CLANG_DISPLAY_DIAGS", "1")); AutoTestShell::init({QStringLiteral("kdevclangsupport")}); auto core = TestCore::initialize(); delete core->projectController(); m_projectController = new TestProjectController(core); core->setProjectController(m_projectController); } void TestDUChain::cleanupTestCase() { TestCore::shutdown(); } void TestDUChain::cleanup() { if (m_provider) { IDefinesAndIncludesManager::manager()->unregisterBackgroundProvider(m_provider.data()); } } void TestDUChain::init() { m_provider.reset(new TestEnvironmentProvider); IDefinesAndIncludesManager::manager()->registerBackgroundProvider(m_provider.data()); } struct ExpectedComment { QString identifier; QString comment; }; Q_DECLARE_METATYPE(ExpectedComment) Q_DECLARE_METATYPE(AbstractType::WhichType) void TestDUChain::testComments() { QFETCH(QString, code); QFETCH(ExpectedComment, expectedComment); TestFile file(code, "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); auto candidates = top->findDeclarations(QualifiedIdentifier(expectedComment.identifier)); QVERIFY(!candidates.isEmpty()); auto decl = candidates.first(); QString comment = QString::fromLocal8Bit(decl->comment()); comment = KDevelop::htmlToPlainText(comment, KDevelop::CompleteMode); QCOMPARE(comment, expectedComment.comment); } void TestDUChain::testComments_data() { QTest::addColumn("code"); QTest::addColumn("expectedComment"); // note: Clang only retrieves the comments when in doxygen-style format (i.e. '///', '/**', '///<') QTest::newRow("invalid1") << "//this is foo\nint foo;" << ExpectedComment{"foo", QString()}; QTest::newRow("invalid2") << "/*this is foo*/\nint foo;" << ExpectedComment{"foo", QString()}; QTest::newRow("basic1") << "///this is foo\nint foo;" << ExpectedComment{"foo", "this is foo"}; QTest::newRow("basic2") << "/**this is foo*/\nint foo;" << ExpectedComment{"foo", "this is foo"}; QTest::newRow("enumerator") << "enum Foo { bar1, ///localDeclarations().size(), 2); auto decl = file.topContext()->localDeclarations()[1]; QVERIFY(decl); auto function = dynamic_cast(decl); QVERIFY(function); auto functionType = function->type(); QVERIFY(functionType); #if CINDEX_VERSION_MINOR < 34 QEXPECT_FAIL("namespace", "The ElaboratedType is not exposed through the libclang interface, not much we can do here", Abort); #endif QVERIFY(functionType->returnType()->whichType() != AbstractType::TypeDelayed); #if CINDEX_VERSION_MINOR < 34 QEXPECT_FAIL("typedef", "After using clang_getCanonicalType on ElaboratedType all typedef information get's stripped away", Continue); #endif QCOMPARE(functionType->returnType()->whichType(), type); } void TestDUChain::testElaboratedType_data() { QTest::addColumn("code"); QTest::addColumn("type"); QTest::newRow("namespace") << "namespace NS{struct Type{};} struct NS::Type foo();" << AbstractType::TypeStructure; QTest::newRow("enum") << "enum Enum{}; enum Enum foo();" << AbstractType::TypeEnumeration; QTest::newRow("typedef") << "namespace NS{typedef int type;} NS::type foo();" << AbstractType::TypeAlias; } void TestDUChain::testInclude() { TestFile header("int foo() { return 42; }\n", "h"); // NOTE: header is _not_ explictly being parsed, instead the impl job does that TestFile impl("#include \"" + header.url().byteArray() + "\"\n" "int main() { return foo(); }", "cpp", &header); impl.parse(TopDUContext::AllDeclarationsContextsAndUses); auto implCtx = impl.topContext(); QVERIFY(implCtx); DUChainReadLocker lock; QCOMPARE(implCtx->localDeclarations().size(), 1); auto headerCtx = DUChain::self()->chainForDocument(header.url()); QVERIFY(headerCtx); QVERIFY(!headerCtx->parsingEnvironmentFile()->needsUpdate()); QCOMPARE(headerCtx->localDeclarations().size(), 1); QVERIFY(implCtx->imports(headerCtx, CursorInRevision(0, 10))); Declaration* foo = headerCtx->localDeclarations().first(); QCOMPARE(foo->uses().size(), 1); QCOMPARE(foo->uses().begin().key(), impl.url()); QCOMPARE(foo->uses().begin()->size(), 1); QCOMPARE(foo->uses().begin()->first(), RangeInRevision(1, 20, 1, 23)); } void TestDUChain::testMissingInclude() { auto code = R"( #pragma once #include "missing1.h" template class A { T a; }; #include "missing2.h" class B : public A { }; )"; // NOTE: This fails and needs fixing. If the include of "missing2.h" // above is commented out, then it doesn't fail. Maybe // clang stops processing when it encounters the second missing // header, or similar. TestFile header(code, "h"); TestFile impl("#include \"" + header.url().byteArray() + "\"\n", "cpp", &header); QVERIFY(impl.parseAndWait(TopDUContext::AllDeclarationsContextsAndUses)); DUChainReadLocker lock; auto top = impl.topContext(); QVERIFY(top); QCOMPARE(top->importedParentContexts().count(), 1); TopDUContext* headerCtx = dynamic_cast(top->importedParentContexts().first().context(top)); QVERIFY(headerCtx); QCOMPARE(headerCtx->url(), header.url()); #if CINDEX_VERSION_MINOR < 34 QEXPECT_FAIL("", "Second missing header isn't reported", Continue); #endif QCOMPARE(headerCtx->problems().count(), 2); QCOMPARE(headerCtx->localDeclarations().count(), 2); auto a = dynamic_cast(headerCtx->localDeclarations().first()); QVERIFY(a); auto b = dynamic_cast(headerCtx->localDeclarations().last()); QVERIFY(b); #if CINDEX_VERSION_MINOR < 34 QEXPECT_FAIL("", "Base class isn't assigned correctly", Continue); #endif QCOMPARE(b->baseClassesSize(), 1u); #if CINDEX_VERSION_MINOR < 34 // at least the one problem we have should have been propagated QCOMPARE(top->problems().count(), 1); #else // two errors: // /tmp/testfile_f32415.h:3:10: error: 'missing1.h' file not found // /tmp/testfile_f32415.h:11:10: error: 'missing2.h' file not found QCOMPARE(top->problems().count(), 2); #endif } QByteArray createCode(const QByteArray& prefix, const int functions) { QByteArray code; code += "#ifndef " + prefix + "_H\n"; code += "#define " + prefix + "_H\n"; for (int i = 0; i < functions; ++i) { code += "void myFunc_" + prefix + "(int arg1, char arg2, const char* arg3);\n"; } code += "#endif\n"; return code; } void TestDUChain::testIncludeLocking() { TestFile header1(createCode("Header1", 1000), "h"); TestFile header2(createCode("Header2", 1000), "h"); TestFile header3(createCode("Header3", 1000), "h"); ICore::self()->languageController()->backgroundParser()->setThreadCount(3); TestFile impl1("#include \"" + header1.url().byteArray() + "\"\n" "#include \"" + header2.url().byteArray() + "\"\n" "#include \"" + header3.url().byteArray() + "\"\n" "int main() { return 0; }", "cpp"); TestFile impl2("#include \"" + header2.url().byteArray() + "\"\n" "#include \"" + header1.url().byteArray() + "\"\n" "#include \"" + header3.url().byteArray() + "\"\n" "int main() { return 0; }", "cpp"); TestFile impl3("#include \"" + header3.url().byteArray() + "\"\n" "#include \"" + header1.url().byteArray() + "\"\n" "#include \"" + header2.url().byteArray() + "\"\n" "int main() { return 0; }", "cpp"); impl1.parse(TopDUContext::AllDeclarationsContextsAndUses); impl2.parse(TopDUContext::AllDeclarationsContextsAndUses); impl3.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(impl1.waitForParsed(5000)); QVERIFY(impl2.waitForParsed(5000)); QVERIFY(impl3.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(DUChain::self()->chainForDocument(header1.url())); QVERIFY(DUChain::self()->chainForDocument(header2.url())); QVERIFY(DUChain::self()->chainForDocument(header3.url())); } void TestDUChain::testReparse() { TestFile file("int main() { int i = 42; return i; }", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); DeclarationPointer mainDecl; DeclarationPointer iDecl; for (int i = 0; i < 3; ++i) { QVERIFY(file.waitForParsed(500)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->childContexts().size(), 1); QCOMPARE(file.topContext()->localDeclarations().size(), 1); DUContext *exprContext = file.topContext()->childContexts().first()->childContexts().first(); QCOMPARE(exprContext->localDeclarations().size(), 1); if (i) { QVERIFY(mainDecl); QCOMPARE(mainDecl.data(), file.topContext()->localDeclarations().first()); QVERIFY(iDecl); QCOMPARE(iDecl.data(), exprContext->localDeclarations().first()); } mainDecl = file.topContext()->localDeclarations().first(); iDecl = exprContext->localDeclarations().first(); QVERIFY(mainDecl->uses().isEmpty()); QCOMPARE(iDecl->uses().size(), 1); QCOMPARE(iDecl->uses().begin()->size(), 1); if (i == 1) { file.setFileContents("int main()\n{\nfloat i = 13; return i - 5;\n}\n"); } file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); } } void TestDUChain::testReparseError() { TestFile file("int i = 1 / 0;\n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); for (int i = 0; i < 2; ++i) { QVERIFY(file.waitForParsed(500)); DUChainReadLocker lock; QVERIFY(file.topContext()); if (!i) { QCOMPARE(file.topContext()->problems().size(), 1); file.setFileContents("int i = 0;\n"); } else { QCOMPARE(file.topContext()->problems().size(), 0); } file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); } } void TestDUChain::testTemplate() { TestFile file("template struct foo { T bar; };\n" "int main() { foo myFoo; return myFoo.bar; }\n", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto fooDecl = file.topContext()->localDeclarations().first(); QVERIFY(fooDecl->internalContext()); QCOMPARE(fooDecl->internalContext()->localDeclarations().size(), 2); QCOMPARE(file.topContext()->findDeclarations(QualifiedIdentifier("foo")).size(), 1); QCOMPARE(file.topContext()->findDeclarations(QualifiedIdentifier("foo::bar")).size(), 1); auto mainCtx = file.topContext()->localDeclarations().last()->internalContext()->childContexts().first(); QVERIFY(mainCtx); auto myFoo = mainCtx->localDeclarations().first(); QVERIFY(myFoo); QCOMPARE(myFoo->abstractType()->toString().remove(' '), QStringLiteral("foo")); } void TestDUChain::testNamespace() { TestFile file("namespace foo { struct bar { int baz; }; }\n" "int main() { foo::bar myBar; }\n", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto fooDecl = file.topContext()->localDeclarations().first(); QVERIFY(fooDecl->internalContext()); QCOMPARE(fooDecl->internalContext()->localDeclarations().size(), 1); DUContext* top = file.topContext().data(); DUContext* mainCtx = file.topContext()->childContexts().last(); auto foo = top->localDeclarations().first(); QCOMPARE(foo->qualifiedIdentifier().toString(), QString("foo")); DUContext* fooCtx = file.topContext()->childContexts().first(); QCOMPARE(fooCtx->localScopeIdentifier().toString(), QString("foo")); QCOMPARE(fooCtx->scopeIdentifier(true).toString(), QString("foo")); QCOMPARE(fooCtx->localDeclarations().size(), 1); auto bar = fooCtx->localDeclarations().first(); QCOMPARE(bar->qualifiedIdentifier().toString(), QString("foo::bar")); QCOMPARE(fooCtx->childContexts().size(), 1); DUContext* barCtx = fooCtx->childContexts().first(); QCOMPARE(barCtx->localScopeIdentifier().toString(), QString("bar")); QCOMPARE(barCtx->scopeIdentifier(true).toString(), QString("foo::bar")); QCOMPARE(barCtx->localDeclarations().size(), 1); auto baz = barCtx->localDeclarations().first(); QCOMPARE(baz->qualifiedIdentifier().toString(), QString("foo::bar::baz")); for (auto ctx : {top, mainCtx}) { QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("foo")).size(), 1); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("foo::bar")).size(), 1); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("foo::bar::baz")).size(), 1); } } void TestDUChain::testAutoTypeDeduction() { TestFile file(R"( const volatile auto foo = 5; template struct myTemplate {}; myTemplate& > templRefParam; auto autoTemplRefParam = templRefParam; )", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; DUContext* ctx = file.topContext().data(); QVERIFY(ctx); QCOMPARE(ctx->localDeclarations().size(), 4); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("foo")).size(), 1); Declaration* decl = ctx->findDeclarations(QualifiedIdentifier("foo"))[0]; QCOMPARE(decl->identifier(), Identifier("foo")); #if CINDEX_VERSION_MINOR < 31 QEXPECT_FAIL("", "No type deduction here unfortunately, missing API in Clang", Continue); #endif QVERIFY(decl->type()); #if CINDEX_VERSION_MINOR < 31 QCOMPARE(decl->toString(), QStringLiteral("const volatile auto foo")); #else QCOMPARE(decl->toString(), QStringLiteral("const volatile int foo")); #endif decl = ctx->findDeclarations(QualifiedIdentifier("autoTemplRefParam"))[0]; QVERIFY(decl); QVERIFY(decl->abstractType()); #if CINDEX_VERSION_MINOR < 31 QEXPECT_FAIL("", "Auto type is not exposed via LibClang", Continue); #endif QCOMPARE(decl->abstractType()->toString(), QStringLiteral("myTemplate< myTemplate< int >& >")); } void TestDUChain::testTypeDeductionInTemplateInstantiation() { // see: http://clang-developers.42468.n3.nabble.com/RFC-missing-libclang-query-functions-features-td2504253.html TestFile file("template struct foo { T member; } foo f; auto i = f.member;", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; DUContext* ctx = file.topContext().data(); QVERIFY(ctx); QCOMPARE(ctx->localDeclarations().size(), 3); Declaration* decl = 0; // check 'foo' declaration decl = ctx->localDeclarations()[0]; QVERIFY(decl); QCOMPARE(decl->identifier(), Identifier("foo")); // check type of 'member' inside declaration-scope QCOMPARE(ctx->childContexts().size(), 1); DUContext* fooCtx = ctx->childContexts().first(); QVERIFY(fooCtx); // Should there really be two declarations? QCOMPARE(fooCtx->localDeclarations().size(), 2); decl = fooCtx->localDeclarations()[1]; QCOMPARE(decl->identifier(), Identifier("member")); // check type of 'member' in definition of 'f' decl = ctx->localDeclarations()[1]; QCOMPARE(decl->identifier(), Identifier("f")); decl = ctx->localDeclarations()[2]; QCOMPARE(decl->identifier(), Identifier("i")); #if CINDEX_VERSION_MINOR < 31 QEXPECT_FAIL("", "No type deduction here unfortunately, missing API in Clang", Continue); #endif QVERIFY(decl->type()); } void TestDUChain::testVirtualMemberFunction() { //Forward-declarations with "struct" or "class" are considered equal, so make sure the override is detected correctly. TestFile file("struct S {}; struct A { virtual S* ret(); }; struct B : public A { virtual S* ret(); };", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; DUContext* top = file.topContext().data(); QVERIFY(top); QCOMPARE(top->childContexts().count(), 3); QCOMPARE(top->localDeclarations().count(), 3); QCOMPARE(top->childContexts()[2]->localDeclarations().count(), 1); Declaration* decl = top->childContexts()[2]->localDeclarations()[0]; QCOMPARE(decl->identifier(), Identifier("ret")); QVERIFY(DUChainUtils::getOverridden(decl)); } void TestDUChain::testBaseClasses() { TestFile file("class Base {}; class Inherited : public Base {};", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; DUContext* top = file.topContext().data(); QVERIFY(top); QCOMPARE(top->localDeclarations().count(), 2); Declaration* baseDecl = top->localDeclarations().first(); QCOMPARE(baseDecl->identifier(), Identifier("Base")); ClassDeclaration* inheritedDecl = dynamic_cast(top->localDeclarations()[1]); QCOMPARE(inheritedDecl->identifier(), Identifier("Inherited")); QVERIFY(inheritedDecl); QCOMPARE(inheritedDecl->baseClassesSize(), 1u); QCOMPARE(baseDecl->uses().count(), 1); QCOMPARE(baseDecl->uses().first().count(), 1); QCOMPARE(baseDecl->uses().first().first(), RangeInRevision(0, 40, 0, 44)); } void TestDUChain::testReparseBaseClasses() { TestFile file("struct a{}; struct b : a {};\n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); for (int i = 0; i < 2; ++i) { qDebug() << "run: " << i; QVERIFY(file.waitForParsed(500)); DUChainWriteLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->childContexts().size(), 2); QCOMPARE(file.topContext()->childContexts().first()->importers().size(), 1); QCOMPARE(file.topContext()->childContexts().last()->importedParentContexts().size(), 1); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto aDecl = dynamic_cast(file.topContext()->localDeclarations().first()); QVERIFY(aDecl); QCOMPARE(aDecl->baseClassesSize(), 0u); auto bDecl = dynamic_cast(file.topContext()->localDeclarations().last()); QVERIFY(bDecl); QCOMPARE(bDecl->baseClassesSize(), 1u); int distance = 0; QVERIFY(bDecl->isPublicBaseClass(aDecl, file.topContext(), &distance)); QCOMPARE(distance, 1); file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); } } void TestDUChain::testReparseBaseClassesTemplates() { TestFile file("template struct a{}; struct b : a {};\n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); for (int i = 0; i < 2; ++i) { qDebug() << "run: " << i; QVERIFY(file.waitForParsed(500)); DUChainWriteLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->childContexts().size(), 2); QCOMPARE(file.topContext()->childContexts().first()->importers().size(), 1); QCOMPARE(file.topContext()->childContexts().last()->importedParentContexts().size(), 1); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto aDecl = dynamic_cast(file.topContext()->localDeclarations().first()); QVERIFY(aDecl); QCOMPARE(aDecl->baseClassesSize(), 0u); auto bDecl = dynamic_cast(file.topContext()->localDeclarations().last()); QVERIFY(bDecl); QCOMPARE(bDecl->baseClassesSize(), 1u); int distance = 0; QVERIFY(bDecl->isPublicBaseClass(aDecl, file.topContext(), &distance)); QCOMPARE(distance, 1); file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); } } void TestDUChain::testGetInheriters_data() { QTest::addColumn("code"); QTest::newRow("inline") << "struct Base { struct Inner {}; }; struct Inherited : Base, Base::Inner {};"; QTest::newRow("outline") << "struct Base { struct Inner; }; struct Base::Inner {}; struct Inherited : Base, Base::Inner {};"; } void TestDUChain::testGetInheriters() { QFETCH(QString, code); TestFile file(code, "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); QVERIFY(top->problems().isEmpty()); QCOMPARE(top->localDeclarations().count(), 2); Declaration* baseDecl = top->localDeclarations().first(); QCOMPARE(baseDecl->identifier(), Identifier("Base")); DUContext* baseCtx = baseDecl->internalContext(); QVERIFY(baseCtx); QCOMPARE(baseCtx->localDeclarations().count(), 1); Declaration* innerDecl = baseCtx->localDeclarations().first(); QCOMPARE(innerDecl->identifier(), Identifier("Inner")); if (auto forward = dynamic_cast(innerDecl)) { innerDecl = forward->resolve(top); } QVERIFY(dynamic_cast(innerDecl)); Declaration* inheritedDecl = top->localDeclarations().last(); QVERIFY(inheritedDecl); QCOMPARE(inheritedDecl->identifier(), Identifier("Inherited")); uint maxAllowedSteps = uint(-1); auto baseInheriters = DUChainUtils::getInheriters(baseDecl, maxAllowedSteps); QCOMPARE(baseInheriters, QList() << inheritedDecl); maxAllowedSteps = uint(-1); auto innerInheriters = DUChainUtils::getInheriters(innerDecl, maxAllowedSteps); QCOMPARE(innerInheriters, QList() << inheritedDecl); maxAllowedSteps = uint(-1); auto inheritedInheriters = DUChainUtils::getInheriters(inheritedDecl, maxAllowedSteps); QCOMPARE(inheritedInheriters.count(), 0); } void TestDUChain::testGlobalFunctionDeclaration() { TestFile file("void foo(int arg1, char arg2);\n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); file.waitForParsed(); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 1); QCOMPARE(file.topContext()->childContexts().size(), 1); QVERIFY(!file.topContext()->childContexts().first()->inSymbolTable()); } void TestDUChain::testFunctionDefinitionVsDeclaration() { TestFile file("void func(); void func() {}\n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed()); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto funcDecl = file.topContext()->localDeclarations()[0]; QVERIFY(!funcDecl->isDefinition()); QVERIFY(!dynamic_cast(funcDecl)); auto funcDef = file.topContext()->localDeclarations()[1]; QVERIFY(dynamic_cast(funcDef)); QVERIFY(funcDef->isDefinition()); } void TestDUChain::testEnsureNoDoubleVisit() { // On some language construct, we may up visiting the same cursor multiple times // Example: "struct SomeStruct {} s;" // decl: "SomeStruct SomeStruct " of kind StructDecl (2) in main.cpp@[(1,1),(1,17)] // decl: "struct SomeStruct s " of kind VarDecl (9) in main.cpp@[(1,1),(1,19)] // decl: "SomeStruct SomeStruct " of kind StructDecl (2) in main.cpp@[(1,1),(1,17)] // // => We end up visiting the StructDecl twice (or more) // That's because we use clang_visitChildren not just on the translation unit cursor. // Apparently just "recursing" vs. "visiting children explicitly" // results in a different AST traversal TestFile file("struct SomeStruct {} s;\n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed()); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); // there should only be one declaration for "SomeStruct" auto candidates = top->findDeclarations(QualifiedIdentifier("SomeStruct")); QCOMPARE(candidates.size(), 1); } void TestDUChain::testParsingEnvironment() { const TopDUContext::Features features = TopDUContext::AllDeclarationsContextsAndUses; IndexedTopDUContext indexed; ClangParsingEnvironment lastEnv; { TestFile file("int main() {}\n", "cpp"); auto astFeatures = static_cast(features | TopDUContext::AST); file.parse(astFeatures); file.setKeepDUChainData(true); QVERIFY(file.waitForParsed()); DUChainWriteLocker lock; auto top = file.topContext(); QVERIFY(top); auto sessionData = ParseSessionData::Ptr(dynamic_cast(top->ast().data())); lock.unlock(); ParseSession session(sessionData); lock.lock(); QVERIFY(session.data()); QVERIFY(top); auto envFile = QExplicitlySharedDataPointer( dynamic_cast(file.topContext()->parsingEnvironmentFile().data())); QCOMPARE(envFile->features(), astFeatures); QVERIFY(envFile->featuresSatisfied(astFeatures)); QCOMPARE(envFile->environmentQuality(), ClangParsingEnvironment::Source); // if no environment is given, no update should be triggered QVERIFY(!envFile->needsUpdate()); // same env should also not trigger a reparse ClangParsingEnvironment env = session.environment(); QCOMPARE(env.quality(), ClangParsingEnvironment::Source); QVERIFY(!envFile->needsUpdate(&env)); // but changing the environment should trigger an update env.addIncludes(Path::List() << Path("/foo/bar/baz")); QVERIFY(envFile->needsUpdate(&env)); envFile->setEnvironment(env); QVERIFY(!envFile->needsUpdate(&env)); // setting the environment quality higher should require an update env.setQuality(ClangParsingEnvironment::BuildSystem); QVERIFY(envFile->needsUpdate(&env)); envFile->setEnvironment(env); QVERIFY(!envFile->needsUpdate(&env)); // changing defines requires an update env.addDefines(QHash{ { "foo", "bar" } }); QVERIFY(envFile->needsUpdate(&env)); // but only when changing the defines for the envFile's TU const auto barTU = IndexedString("bar.cpp"); const auto oldTU = env.translationUnitUrl(); env.setTranslationUnitUrl(barTU); QCOMPARE(env.translationUnitUrl(), barTU); QVERIFY(!envFile->needsUpdate(&env)); env.setTranslationUnitUrl(oldTU); QVERIFY(envFile->needsUpdate(&env)); // update it again envFile->setEnvironment(env); QVERIFY(!envFile->needsUpdate(&env)); lastEnv = env; // now compare against a lower quality environment // in such a case, we do not want to trigger an update env.setQuality(ClangParsingEnvironment::Unknown); env.setTranslationUnitUrl(barTU); QVERIFY(!envFile->needsUpdate(&env)); // even when the environment changes env.addIncludes(Path::List() << Path("/lalalala")); QVERIFY(!envFile->needsUpdate(&env)); indexed = top->indexed(); } DUChain::self()->storeToDisk(); { DUChainWriteLocker lock; QVERIFY(!DUChain::self()->isInMemory(indexed.index())); QVERIFY(indexed.data()); QVERIFY(DUChain::self()->environmentFileForDocument(indexed)); auto envFile = QExplicitlySharedDataPointer( dynamic_cast(DUChain::self()->environmentFileForDocument(indexed).data())); QVERIFY(envFile); QCOMPARE(envFile->features(), features); QVERIFY(envFile->featuresSatisfied(features)); QVERIFY(!envFile->needsUpdate(&lastEnv)); DUChain::self()->removeDocumentChain(indexed.data()); } } void TestDUChain::testActiveDocumentHasASTAttached() { const TopDUContext::Features features = TopDUContext::AllDeclarationsContextsAndUses; IndexedTopDUContext indexed; ClangParsingEnvironment lastEnv; { TestFile file("int main() {}\n", "cpp"); auto astFeatures = static_cast(features | TopDUContext::AST); file.parse(astFeatures); file.setKeepDUChainData(true); QVERIFY(file.waitForParsed()); DUChainWriteLocker lock; auto top = file.topContext(); QVERIFY(top); auto sessionData = ParseSessionData::Ptr(dynamic_cast(top->ast().data())); lock.unlock(); ParseSession session(sessionData); lock.lock(); QVERIFY(session.data()); QVERIFY(top); QVERIFY(top->ast()); indexed = top->indexed(); } DUChain::self()->storeToDisk(); { DUChainWriteLocker lock; QVERIFY(!DUChain::self()->isInMemory(indexed.index())); QVERIFY(indexed.data()); } QUrl url; { DUChainReadLocker lock; auto ctx = indexed.data(); QVERIFY(ctx); QVERIFY(!ctx->ast()); url = ctx->url().toUrl(); } QVERIFY(!QFileInfo::exists(url.toLocalFile())); QFile file(url.toLocalFile()); file.open(QIODevice::WriteOnly); Q_ASSERT(file.isOpen()); auto document = ICore::self()->documentController()->openDocument(url); QVERIFY(document); ICore::self()->documentController()->activateDocument(document); QApplication::processEvents(); ICore::self()->languageController()->backgroundParser()->parseDocuments(); QThread::sleep(1); document->close(KDevelop::IDocument::Discard); { DUChainReadLocker lock; auto ctx = indexed.data(); QVERIFY(ctx); QVERIFY(ctx->ast()); } DUChainWriteLocker lock; DUChain::self()->removeDocumentChain(indexed.data()); } void TestDUChain::testActiveDocumentsGetBestPriority() { // note: this test would make more sense in kdevplatform, but we don't have a language plugin available there // (required for background parsing) // TODO: Create a fake-language plugin in kdevplatform for testing purposes, use that. TestFile file1("int main() {}\n", "cpp"); TestFile file2("int main() {}\n", "cpp"); TestFile file3("int main() {}\n", "cpp"); DUChain::self()->storeToDisk(); auto backgroundParser = ICore::self()->languageController()->backgroundParser(); QVERIFY(!backgroundParser->isQueued(file1.url())); auto documentController = ICore::self()->documentController(); // open first document (no activation) auto doc = documentController->openDocument(file1.url().toUrl(), KTextEditor::Range::invalid(), {IDocumentController::DoNotActivate}); QVERIFY(doc); QVERIFY(backgroundParser->isQueued(file1.url())); QCOMPARE(backgroundParser->priorityForDocument(file1.url()), (int)BackgroundParser::NormalPriority); // open second document, activate doc = documentController->openDocument(file2.url().toUrl()); QVERIFY(doc); QVERIFY(backgroundParser->isQueued(file2.url())); QCOMPARE(backgroundParser->priorityForDocument(file2.url()), (int)BackgroundParser::BestPriority); // open third document, activate, too doc = documentController->openDocument(file3.url().toUrl()); QVERIFY(doc); QVERIFY(backgroundParser->isQueued(file3.url())); QCOMPARE(backgroundParser->priorityForDocument(file3.url()), (int)BackgroundParser::BestPriority); } void TestDUChain::testSystemIncludes() { ClangParsingEnvironment env; Path::List projectIncludes = { Path("/projects/1"), Path("/projects/1/sub"), Path("/projects/2"), Path("/projects/2/sub") }; env.addIncludes(projectIncludes); auto includes = env.includes(); // no project paths set, so everything is considered a system include QCOMPARE(includes.system, projectIncludes); QVERIFY(includes.project.isEmpty()); Path::List systemIncludes = { Path("/sys"), Path("/sys/sub") }; env.addIncludes(systemIncludes); includes = env.includes(); QCOMPARE(includes.system, projectIncludes + systemIncludes); QVERIFY(includes.project.isEmpty()); Path::List projects = { Path("/projects/1"), Path("/projects/2") }; env.setProjectPaths(projects); // now the list should be properly separated QCOMPARE(env.projectPaths(), projects); includes = env.includes(); QCOMPARE(includes.system, systemIncludes); QCOMPARE(includes.project, projectIncludes); } void TestDUChain::benchDUChainBuilder() { QBENCHMARK_ONCE { TestFile file( "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(60000)); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); } } void TestDUChain::testReparseWithAllDeclarationsContextsAndUses() { TestFile file("int foo() { return 0; } int main() { return foo(); }", "cpp"); file.parse(TopDUContext::VisibleDeclarationsAndContexts); QVERIFY(file.waitForParsed(1000)); { DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->childContexts().size(), 2); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto dec = file.topContext()->localDeclarations().at(0); QEXPECT_FAIL("", "Skipping of function bodies is disabled for now", Continue); QVERIFY(dec->uses().isEmpty()); } file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(500)); { DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->childContexts().size(), 2); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto mainDecl = file.topContext()->localDeclarations()[1]; QVERIFY(mainDecl->uses().isEmpty()); auto foo = file.topContext()->localDeclarations().first(); QCOMPARE(foo->uses().size(), 1); } } void TestDUChain::testReparseOnDocumentActivated() { TestFile file("int foo() { return 0; } int main() { return foo(); }", "cpp"); file.parse(TopDUContext::VisibleDeclarationsAndContexts); QVERIFY(file.waitForParsed(1000)); { DUChainReadLocker lock; auto ctx = file.topContext(); QVERIFY(ctx); QCOMPARE(ctx->childContexts().size(), 2); QCOMPARE(ctx->localDeclarations().size(), 2); auto dec = ctx->localDeclarations().at(0); QEXPECT_FAIL("", "Skipping of function bodies was disabled for now", Continue); QVERIFY(dec->uses().isEmpty()); QVERIFY(!ctx->ast()); } auto backgroundParser = ICore::self()->languageController()->backgroundParser(); QVERIFY(!backgroundParser->isQueued(file.url())); auto doc = ICore::self()->documentController()->openDocument(file.url().toUrl()); QVERIFY(doc); QVERIFY(backgroundParser->isQueued(file.url())); QSignalSpy spy(backgroundParser, &BackgroundParser::parseJobFinished); spy.wait(); doc->close(KDevelop::IDocument::Discard); { DUChainReadLocker lock; auto ctx = file.topContext(); QCOMPARE(ctx->features() & TopDUContext::AllDeclarationsContextsAndUses, static_cast(TopDUContext::AllDeclarationsContextsAndUses)); QVERIFY(ctx->topContext()->ast()); } } void TestDUChain::testReparseInclude() { TestFile header("int foo() { return 42; }\n", "h"); TestFile impl("#include \"" + header.url().byteArray() + "\"\n" "int main() { return foo(); }", "cpp", &header); // Use TopDUContext::AST to imitate that document is opened in the editor, so that ClangParseJob can store translation unit, that'll be used for reparsing. impl.parse(TopDUContext::Features(TopDUContext::AllDeclarationsAndContexts|TopDUContext::AST)); QVERIFY(impl.waitForParsed(5000)); { DUChainReadLocker lock; auto implCtx = impl.topContext(); QVERIFY(implCtx); QCOMPARE(implCtx->importedParentContexts().size(), 1); } impl.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST)); QVERIFY(impl.waitForParsed(5000)); DUChainReadLocker lock; auto implCtx = impl.topContext(); QVERIFY(implCtx); QCOMPARE(implCtx->localDeclarations().size(), 1); QCOMPARE(implCtx->importedParentContexts().size(), 1); auto headerCtx = DUChain::self()->chainForDocument(header.url()); QVERIFY(headerCtx); QVERIFY(!headerCtx->parsingEnvironmentFile()->needsUpdate()); QCOMPARE(headerCtx->localDeclarations().size(), 1); QVERIFY(implCtx->imports(headerCtx, CursorInRevision(0, 10))); Declaration* foo = headerCtx->localDeclarations().first(); QCOMPARE(foo->uses().size(), 1); QCOMPARE(foo->uses().begin().key(), impl.url()); QCOMPARE(foo->uses().begin()->size(), 1); QCOMPARE(foo->uses().begin()->first(), RangeInRevision(1, 20, 1, 23)); QCOMPARE(DUChain::self()->allEnvironmentFiles(header.url()).size(), 1); QCOMPARE(DUChain::self()->allEnvironmentFiles(impl.url()).size(), 1); QCOMPARE(DUChain::self()->chainsForDocument(header.url()).size(), 1); QCOMPARE(DUChain::self()->chainsForDocument(impl.url()).size(), 1); } void TestDUChain::testReparseChangeEnvironment() { TestFile header("int foo() { return 42; }\n", "h"); TestFile impl("#include \"" + header.url().byteArray() + "\"\n" "int main() { return foo(); }", "cpp", &header); uint hashes[3] = {0, 0, 0}; for (int i = 0; i < 3; ++i) { impl.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST|TopDUContext::ForceUpdate)); QVERIFY(impl.waitForParsed(5000)); { DUChainReadLocker lock; QVERIFY(impl.topContext()); auto env = dynamic_cast(impl.topContext()->parsingEnvironmentFile().data()); QVERIFY(env); QCOMPARE(env->environmentQuality(), ClangParsingEnvironment::Source); hashes[i] = env->environmentHash(); QVERIFY(hashes[i]); // we should never end up with multiple env files or chains in memory for these files QCOMPARE(DUChain::self()->allEnvironmentFiles(impl.url()).size(), 1); QCOMPARE(DUChain::self()->chainsForDocument(impl.url()).size(), 1); QCOMPARE(DUChain::self()->allEnvironmentFiles(header.url()).size(), 1); QCOMPARE(DUChain::self()->chainsForDocument(header.url()).size(), 1); } // in every run, we expect the environment to have changed for (int j = 0; j < i; ++j) { QVERIFY(hashes[i] != hashes[j]); } if (i == 0) { // 1) change defines m_provider->defines.insert("foooooooo", "baaar!"); } else if (i == 1) { // 2) change includes m_provider->includes.append(Path("/foo/bar/asdf/lalala")); } // 3) stop } } +void TestDUChain::testMacroDependentHeader() +{ + TestFile header("struct MY_CLASS { class Q{Q(); int m;}; int m; };\n", "h"); + TestFile impl("#define MY_CLASS A\n" + "#include \"" + header.url().byteArray() + "\"\n" + "#undef MY_CLASS\n" + "#define MY_CLASS B\n" + "#include \"" + header.url().byteArray() + "\"\n" + "#undef MY_CLASS\n" + "A a;\n" + "const A::Q aq;\n" + "B b;\n" + "const B::Q bq;\n" + "int am = a.m;\n" + "int aqm = aq.m;\n" + "int bm = b.m;\n" + "int bqm = bq.m;\n" + , "cpp", &header); + + impl.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST|TopDUContext::ForceUpdate)); + QVERIFY(impl.waitForParsed(500000)); + + DUChainReadLocker lock; + TopDUContext* top = impl.topContext().data(); + QVERIFY(top); + QCOMPARE(top->localDeclarations().size(), 10); // 2x macro, then a, aq, b, bq + QCOMPARE(top->importedParentContexts().size(), 1); + AbstractType::Ptr type = top->localDeclarations()[2]->abstractType(); + StructureType* sType = dynamic_cast(type.data()); + QVERIFY(sType); + QCOMPARE(sType->toString(), QString("A")); + Declaration* decl = sType->declaration(top); + QVERIFY(decl); + AbstractType::Ptr type2 = top->localDeclarations()[4]->abstractType(); + StructureType* sType2 = dynamic_cast(type2.data()); + QVERIFY(sType2); + QCOMPARE(sType2->toString(), QString("B")); + Declaration* decl2 = sType2->declaration(top); + QVERIFY(decl2); + + TopDUContext* top2 = dynamic_cast(top->importedParentContexts()[0].context(top)); + QVERIFY(top2); + QCOMPARE(top2->localDeclarations().size(), 2); + QCOMPARE(top2->localDeclarations()[0], decl); + QCOMPARE(top2->localDeclarations()[1], decl2); + qDebug() << "DECL RANGE:" << top2->localDeclarations()[0]->range().castToSimpleRange().toString(); + qDebug() << "CTX RANGE:" << top2->localDeclarations()[0]->internalContext()->range().castToSimpleRange().toString(); + + // validate uses: + QCOMPARE(top->usesCount(), 14); + QCOMPARE(top->uses()[0].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("A")); + QCOMPARE(top->uses()[1].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("A")); + QCOMPARE(top->uses()[2].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("A::Q")); + QCOMPARE(top->uses()[3].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("B")); + QCOMPARE(top->uses()[4].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("B")); + QCOMPARE(top->uses()[5].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("B::Q")); + QCOMPARE(top->uses()[6].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("a")); + QCOMPARE(top->uses()[7].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("A::m")); + QCOMPARE(top->uses()[8].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("aq")); + QCOMPARE(top->uses()[9].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("A::Q::m")); + QCOMPARE(top->uses()[10].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("b")); + QCOMPARE(top->uses()[11].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("B::m")); + QCOMPARE(top->uses()[12].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("bq")); + QCOMPARE(top->uses()[13].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("B::Q::m")); +} + void TestDUChain::testHeaderParsingOrder1() { TestFile header("typedef const A B;\n", "h"); TestFile impl("template class A{};\n" "#include \"" + header.url().byteArray() + "\"\n" "B c;", "cpp", &header); impl.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST|TopDUContext::ForceUpdate)); QVERIFY(impl.waitForParsed(500000)); DUChainReadLocker lock; TopDUContext* top = impl.topContext().data(); QVERIFY(top); QCOMPARE(top->localDeclarations().size(), 2); QCOMPARE(top->importedParentContexts().size(), 1); AbstractType::Ptr type = top->localDeclarations()[1]->abstractType(); TypeAliasType* aType = dynamic_cast(type.data()); QVERIFY(aType); AbstractType::Ptr targetType = aType->type(); QVERIFY(targetType); IdentifiedType *idType = dynamic_cast(targetType.data()); QVERIFY(idType); // this declaration could be resolved, because it was created with an // indirect DeclarationId that is resolved from the perspective of 'top' Declaration* decl = idType->declaration(top); // NOTE: the decl. doesn't know (yet) about the template insantiation QVERIFY(decl); QCOMPARE(decl, top->localDeclarations()[0]); // now ensure that a use was build for 'A' in header1 TopDUContext* top2 = dynamic_cast(top->importedParentContexts()[0].context(top)); QVERIFY(top2); QEXPECT_FAIL("", "the use could not be created because the corresponding declaration didn't exist yet", Continue); QCOMPARE(top2->usesCount(), 1); // Declaration* decl2 = top2->uses()[0].usedDeclaration(top2); // QVERIFY(decl2); // QCOMPARE(decl, decl2); } void TestDUChain::testHeaderParsingOrder2() { TestFile header("template class A{};\n", "h"); TestFile header2("typedef const A B;\n", "h"); TestFile impl("#include \"" + header.url().byteArray() + "\"\n" "#include \"" + header2.url().byteArray() + "\"\n" "B c;", "cpp", &header); impl.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST|TopDUContext::ForceUpdate)); QVERIFY(impl.waitForParsed(500000)); DUChainReadLocker lock; TopDUContext* top = impl.topContext().data(); QVERIFY(top); QCOMPARE(top->localDeclarations().size(), 1); QCOMPARE(top->importedParentContexts().size(), 2); AbstractType::Ptr type = top->localDeclarations()[0]->abstractType(); TypeAliasType* aType = dynamic_cast(type.data()); QVERIFY(aType); AbstractType::Ptr targetType = aType->type(); QVERIFY(targetType); IdentifiedType *idType = dynamic_cast(targetType.data()); QVERIFY(idType); Declaration* decl = idType->declaration(top); // NOTE: the decl. doesn't know (yet) about the template insantiation QVERIFY(decl); // now ensure that a use was build for 'A' in header2 TopDUContext* top2 = dynamic_cast(top->importedParentContexts()[1].context(top)); QVERIFY(top2); QCOMPARE(top2->usesCount(), 1); Declaration* decl2 = top2->uses()[0].usedDeclaration(top2); QCOMPARE(decl, decl2); } void TestDUChain::testMacrosRanges() { TestFile file("#define FUNC_MACROS(x) struct str##x{};\nFUNC_MACROS(x);", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto macroDefinition = file.topContext()->localDeclarations()[0]; QVERIFY(macroDefinition); QCOMPARE(macroDefinition->range(), RangeInRevision(0,8,0,19)); auto structDeclaration = file.topContext()->localDeclarations()[1]; QVERIFY(structDeclaration); QCOMPARE(structDeclaration->range(), RangeInRevision(1,0,1,0)); QCOMPARE(macroDefinition->uses().size(), 1); QCOMPARE(macroDefinition->uses().begin()->first(), RangeInRevision(1,0,1,11)); } void TestDUChain::testMultiLineMacroRanges() { TestFile file("#define FUNC_MACROS(x) struct str##x{};\nFUNC_MACROS(x\n);", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto macroDefinition = file.topContext()->localDeclarations()[0]; QVERIFY(macroDefinition); QCOMPARE(macroDefinition->range(), RangeInRevision(0,8,0,19)); auto structDeclaration = file.topContext()->localDeclarations()[1]; QVERIFY(structDeclaration); QCOMPARE(structDeclaration->range(), RangeInRevision(1,0,1,0)); QCOMPARE(macroDefinition->uses().size(), 1); QCOMPARE(macroDefinition->uses().begin()->first(), RangeInRevision(1,0,1,11)); } void TestDUChain::testNestedMacroRanges() { TestFile file("#define INNER int var; var = 0;\n#define MACRO() INNER\nint main(){MACRO(\n);}", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 3); auto main = file.topContext()->localDeclarations()[2]; QVERIFY(main); auto mainCtx = main->internalContext()->childContexts().first(); QVERIFY(mainCtx); QCOMPARE(mainCtx->localDeclarations().size(), 1); auto var = mainCtx->localDeclarations().first(); QVERIFY(var); QCOMPARE(var->range(), RangeInRevision(2,11,2,11)); QCOMPARE(var->uses().size(), 1); QCOMPARE(var->uses().begin()->first(), RangeInRevision(2,11,2,11)); } void TestDUChain::testNestedImports() { TestFile B("#pragma once\nint B();\n", "h"); TestFile C("#pragma once\n#include \"" + B.url().byteArray() + "\"\nint C();\n", "h"); TestFile A("#include \"" + B.url().byteArray() + "\"\n" + "#include \"" + C.url().byteArray() + "\"\nint A();\n", "cpp"); A.parse(); QVERIFY(A.waitForParsed(5000)); DUChainReadLocker lock; auto BCtx = DUChain::self()->chainForDocument(B.url().toUrl()); QVERIFY(BCtx); QVERIFY(BCtx->importedParentContexts().isEmpty()); auto CCtx = DUChain::self()->chainForDocument(C.url().toUrl()); QVERIFY(CCtx); QCOMPARE(CCtx->importedParentContexts().size(), 1); QVERIFY(CCtx->imports(BCtx, CursorInRevision(1, 10))); auto ACtx = A.topContext(); QVERIFY(ACtx); QCOMPARE(ACtx->importedParentContexts().size(), 2); QVERIFY(ACtx->imports(BCtx, CursorInRevision(0, 10))); QVERIFY(ACtx->imports(CCtx, CursorInRevision(1, 10))); } void TestDUChain::testEnvironmentWithDifferentOrderOfElements() { TestFile file("int main();\n", "cpp"); m_provider->includes.clear(); m_provider->includes.append(Path("/path1")); m_provider->includes.append(Path("/path2")); m_provider->defines.clear(); m_provider->defines.insert("key1", "value1"); m_provider->defines.insert("key2", "value2"); m_provider->defines.insert("key3", "value3"); uint previousHash = 0; for (int i: {0, 1, 2, 3}) { file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST|TopDUContext::ForceUpdate)); QVERIFY(file.waitForParsed(5000)); { DUChainReadLocker lock; QVERIFY(file.topContext()); auto env = dynamic_cast(file.topContext()->parsingEnvironmentFile().data()); QVERIFY(env); QCOMPARE(env->environmentQuality(), ClangParsingEnvironment::Source); if (previousHash) { if (i == 3) { QVERIFY(previousHash != env->environmentHash()); } else { QCOMPARE(previousHash, env->environmentHash()); } } previousHash = env->environmentHash(); QVERIFY(previousHash); } if (i == 0) { //Change order of defines. Hash of the environment should stay the same. m_provider->defines.clear(); m_provider->defines.insert("key3", "value3"); m_provider->defines.insert("key1", "value1"); m_provider->defines.insert("key2", "value2"); } else if (i == 1) { //Add the same macros twice. Hash of the environment should stay the same. m_provider->defines.clear(); m_provider->defines.insert("key2", "value2"); m_provider->defines.insert("key3", "value3"); m_provider->defines.insert("key3", "value3"); m_provider->defines.insert("key1", "value1"); } else if (i == 2) { //OTOH order of includes should change hash of the environment. m_provider->includes.clear(); m_provider->includes.append(Path("/path2")); m_provider->includes.append(Path("/path1")); } } } void TestDUChain::testReparseMacro() { TestFile file("#define DECLARE(a) typedef struct a##_ {} *a;\nDECLARE(D);\nD d;", "cpp"); file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST)); QVERIFY(file.waitForParsed(5000)); { DUChainReadLocker lock; QVERIFY(file.topContext()); } file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST|TopDUContext::ForceUpdate)); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 5); auto macroDefinition = file.topContext()->localDeclarations()[0]; QVERIFY(macroDefinition); QCOMPARE(macroDefinition->range(), RangeInRevision(0,8,0,15)); QCOMPARE(macroDefinition->uses().size(), 1); QCOMPARE(macroDefinition->uses().begin()->first(), RangeInRevision(1,0,1,7)); auto structDeclaration = file.topContext()->localDeclarations()[1]; QVERIFY(structDeclaration); QCOMPARE(structDeclaration->range(), RangeInRevision(1,0,1,0)); auto structTypedef = file.topContext()->localDeclarations()[3]; QVERIFY(structTypedef); QCOMPARE(structTypedef->range(), RangeInRevision(1,8,1,9)); QCOMPARE(structTypedef->uses().size(), 1); QCOMPARE(structTypedef->uses().begin()->first(), RangeInRevision(2,0,2,1)); } void TestDUChain::testGotoStatement() { TestFile file("int main() {\ngoto label;\ngoto label;\nlabel: return 0;}", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 1); auto main = file.topContext()->localDeclarations()[0]; QVERIFY(main); auto mainCtx = main->internalContext()->childContexts().first(); QVERIFY(mainCtx); QCOMPARE(mainCtx->localDeclarations().size(), 1); auto label = mainCtx->localDeclarations().first(); QVERIFY(label); QCOMPARE(label->range(), RangeInRevision(3,0,3,5)); QCOMPARE(label->uses().size(), 1); QCOMPARE(label->uses().begin()->first(), RangeInRevision(1,5,1,10)); QCOMPARE(label->uses().begin()->last(), RangeInRevision(2,5,2,10)); } void TestDUChain::testRangesOfOperatorsInsideMacro() { TestFile file("class Test{public: Test& operator++(int);};\n#define MACRO(var) var++;\nint main(){\nTest tst; MACRO(tst)}", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 3); auto testClass = file.topContext()->localDeclarations()[0]; QVERIFY(testClass); auto operatorPlusPlus = testClass->internalContext()->localDeclarations().first(); QVERIFY(operatorPlusPlus); QCOMPARE(operatorPlusPlus->uses().size(), 1); QCOMPARE(operatorPlusPlus->uses().begin()->first(), RangeInRevision(3,10,3,10)); } void TestDUChain::testUsesCreatedForDeclarations() { auto code = R"(template void functionTemplate(T); template void functionTemplate(U) {} namespace NS { class Class{}; } using NS::Class; Class function(); NS::Class function() { return {}; } int main () { functionTemplate(int()); function(); } )"; TestFile file(code, "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed()); DUChainReadLocker lock; QVERIFY(file.topContext()); auto functionTemplate = file.topContext()->findDeclarations(QualifiedIdentifier("functionTemplate")); QVERIFY(!functionTemplate.isEmpty()); auto functionTemplateDeclaration = DUChainUtils::declarationForDefinition(functionTemplate.first()); QVERIFY(!functionTemplateDeclaration->isDefinition()); #if CINDEX_VERSION_MINOR < 29 QEXPECT_FAIL("", "No API in LibClang to determine function template type", Continue); #endif QCOMPARE(functionTemplateDeclaration->uses().count(), 1); auto function = file.topContext()->findDeclarations(QualifiedIdentifier("function")); QVERIFY(!function.isEmpty()); auto functionDeclaration = DUChainUtils::declarationForDefinition(function.first()); QVERIFY(!functionDeclaration->isDefinition()); QCOMPARE(functionDeclaration->uses().count(), 1); } void TestDUChain::testReparseIncludeGuard() { TestFile header("#ifndef GUARD\n#define GUARD\nint something;\n#endif\n", "h"); TestFile impl("#include \"" + header.url().byteArray() + "\"\n", "cpp", &header); impl.parseAndWait(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::AST )); { DUChainReadLocker lock; QCOMPARE(static_cast(impl.topContext()-> importedParentContexts().first().context(impl.topContext()))->problems().size(), 0); } impl.parseAndWait(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); { DUChainReadLocker lock; QCOMPARE(static_cast(impl.topContext()-> importedParentContexts().first().context(impl.topContext()))->problems().size(), 0); } } void TestDUChain::testExternC() { auto code = R"(extern "C" { void foo(); })"; TestFile file(code, "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed()); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); QVERIFY(!top->findDeclarations(QualifiedIdentifier("foo")).isEmpty()); } void TestDUChain::testReparseUnchanged_data() { QTest::addColumn("headerCode"); QTest::addColumn("implCode"); QTest::newRow("include-guards") << R"( #ifndef GUARD #define GUARD int something; #endif )" << R"( #include "%1" )"; QTest::newRow("template-default-parameters") << R"( #ifndef TEST_H #define TEST_H template class dummy; template class dummy { int field[T]; }; #endif )" << R"( #include "%1" int main(int, char **) { dummy<> x; (void)x; } )"; } void TestDUChain::testReparseUnchanged() { QFETCH(QString, headerCode); QFETCH(QString, implCode); TestFile header(headerCode, "h"); TestFile impl(implCode.arg(header.url().str()), "cpp", &header); auto checkProblems = [&] (bool reparsed) { DUChainReadLocker lock; auto headerCtx = DUChain::self()->chainForDocument(header.url()); QVERIFY(headerCtx); QVERIFY(headerCtx->problems().isEmpty()); auto implCtx = DUChain::self()->chainForDocument(impl.url()); QVERIFY(implCtx); if (reparsed && CINDEX_VERSION_MINOR > 29 && CINDEX_VERSION_MINOR < 33) { QEXPECT_FAIL("template-default-parameters", "the precompiled preamble messes the default template parameters up in clang 3.7", Continue); } QVERIFY(implCtx->problems().isEmpty()); }; impl.parseAndWait(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::AST )); checkProblems(false); impl.parseAndWait(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); checkProblems(true); } void TestDUChain::testTypeAliasTemplate() { TestFile file("template using TypeAliasTemplate = T;", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; QVERIFY(file.topContext()); auto templateAlias = file.topContext()->localDeclarations().last(); QVERIFY(templateAlias); #if CINDEX_VERSION_MINOR < 31 QEXPECT_FAIL("", "TypeAliasTemplate is not exposed via LibClang", Abort); #endif QVERIFY(templateAlias->abstractType()); QCOMPARE(templateAlias->abstractType()->toString(), QStringLiteral("TypeAliasTemplate")); } void TestDUChain::testDeclarationsInsideMacroExpansion() { TestFile header("#define DECLARE(a) typedef struct a##__ {int var;} *a\nDECLARE(D);\n", "h"); TestFile file("#include \"" + header.url().byteArray() + "\"\nint main(){\nD d; d->var;}\n", "cpp"); file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST)); QVERIFY(file.waitForParsed(5000)); { DUChainReadLocker lock; QVERIFY(file.topContext()); } file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST|TopDUContext::ForceUpdate)); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 1); auto context = file.topContext()->childContexts().first()->childContexts().first(); QVERIFY(context); QCOMPARE(context->localDeclarations().size(), 1); - QEXPECT_FAIL("", "Clang assigns the same range for all declarations inside a macro expansion. Therefore we can't find their uses", Abort); QCOMPARE(context->usesCount(), 3); QCOMPARE(context->uses()[0].m_range, RangeInRevision({2, 0}, {2, 1})); QCOMPARE(context->uses()[1].m_range, RangeInRevision({2, 5}, {2, 6})); QCOMPARE(context->uses()[2].m_range, RangeInRevision({2, 8}, {2, 11})); } static bool containsErrors(const QList& problems) { auto it = std::find_if(problems.begin(), problems.end(), [] (const Problem::Ptr& problem) { return problem->severity() == Problem::Error; }); return it != problems.end(); } static bool expectedXmmintrinErrors(const QList& problems) { foreach (const auto& problem, problems) { if (problem->severity() == Problem::Error && !problem->description().contains("Cannot initialize a parameter of type")) { return false; } } return true; } static void verifyNoErrors(TopDUContext* top, QSet& checked) { const auto problems = top->problems(); if (containsErrors(problems)) { qDebug() << top->url() << top->problems(); if (top->url().str().endsWith("xmmintrin.h") && expectedXmmintrinErrors(problems)) { QEXPECT_FAIL("", "there are still some errors in xmmintrin.h b/c some clang provided intrinsincs are more strict than the GCC ones.", Continue); QVERIFY(false); } else { QFAIL("parse error detected"); } } const auto imports = top->importedParentContexts(); foreach (const auto& import, imports) { auto ctx = import.context(top); QVERIFY(ctx); auto importedTop = ctx->topContext(); if (checked.contains(importedTop)) { continue; } checked.insert(importedTop); verifyNoErrors(importedTop, checked); } } void TestDUChain::testGccCompatibility() { // TODO: make it easier to change the compiler provider for testing purposes QTemporaryDir dir; auto project = new TestProject(Path(dir.path()), this); auto definesAndIncludesConfig = project->projectConfiguration()->group("CustomDefinesAndIncludes"); auto pathConfig = definesAndIncludesConfig.group("ProjectPath0"); pathConfig.writeEntry("Path", "."); pathConfig.group("Compiler").writeEntry("Name", "GCC"); m_projectController->addProject(project); { // TODO: Also test in C mode. Currently it doesn't work (some intrinsics missing?) TestFile file(R"( #include int main() { return 0; } )", "cpp", project, dir.path()); file.parse(); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QSet checked; verifyNoErrors(file.topContext(), checked); } m_projectController->closeAllProjects(); } void TestDUChain::testQtIntegration() { QTemporaryDir includeDir; { QDir dir(includeDir.path()); dir.mkdir("QtCore"); // create the file but don't put anything in it QFile header(includeDir.path() + "/QtCore/qobjectdefs.h"); QVERIFY(header.open(QIODevice::WriteOnly | QIODevice::Text)); } QTemporaryDir dir; auto project = new TestProject(Path(dir.path()), this); m_provider->defines.clear(); m_provider->includes = {Path(includeDir.path() + "/QtCore")}; m_projectController->addProject(project); { TestFile file(R"( #define slots #define signals #define Q_SLOTS #define Q_SIGNALS #include struct MyObject { public: void other1(); public slots: void slot1(); signals: void signal1(); private Q_SLOTS: void slot2(); Q_SIGNALS: void signal2(); public: void other2(); }; )", "cpp", project, dir.path()); file.parse(); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); QVERIFY(top->problems().isEmpty()); const auto methods = top->childContexts().last()->localDeclarations(); QCOMPARE(methods.size(), 6); foreach(auto method, methods) { auto classFunction = dynamic_cast(method); QVERIFY(classFunction); auto id = classFunction->identifier().toString(); QCOMPARE(classFunction->isSignal(), id.startsWith(QLatin1String("signal"))); QCOMPARE(classFunction->isSlot(), id.startsWith(QLatin1String("slot"))); } } m_projectController->closeAllProjects(); } diff --git a/languages/clang/tests/test_duchain.h b/languages/clang/tests/test_duchain.h index 927a95860f..9b89959329 100644 --- a/languages/clang/tests/test_duchain.h +++ b/languages/clang/tests/test_duchain.h @@ -1,103 +1,104 @@ /* * 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 DUCHAINTEST_H #define DUCHAINTEST_H #include class TestEnvironmentProvider; namespace KDevelop { class TestProjectController; } class TestDUChain : public QObject { Q_OBJECT public: ~TestDUChain() override; private slots: void initTestCase(); void cleanupTestCase(); void init(); void cleanup(); void testComments(); void testComments_data(); void testElaboratedType(); void testElaboratedType_data(); void testInclude(); void testMissingInclude(); void testIncludeLocking(); void testReparse(); void testReparseError(); void testTemplate(); void testNamespace(); void testAutoTypeDeduction(); void testTypeDeductionInTemplateInstantiation(); void testVirtualMemberFunction(); void testBaseClasses(); void testReparseBaseClasses(); void testReparseBaseClassesTemplates(); void testGetInheriters(); void testGetInheriters_data(); void testGlobalFunctionDeclaration(); void testFunctionDefinitionVsDeclaration(); void testEnsureNoDoubleVisit(); void testReparseWithAllDeclarationsContextsAndUses(); void testReparseOnDocumentActivated(); void testParsingEnvironment(); void testSystemIncludes(); void testReparseInclude(); void testReparseChangeEnvironment(); void testMacrosRanges(); void testHeaderParsingOrder1(); void testHeaderParsingOrder2(); + void testMacroDependentHeader(); void testNestedImports(); void testEnvironmentWithDifferentOrderOfElements(); void testReparseMacro(); void testMultiLineMacroRanges(); void testNestedMacroRanges(); void testGotoStatement(); void testRangesOfOperatorsInsideMacro(); void testActiveDocumentHasASTAttached(); void testActiveDocumentsGetBestPriority(); void testUsesCreatedForDeclarations(); void testReparseIncludeGuard(); void testExternC(); void testReparseUnchanged_data(); void testReparseUnchanged(); void testTypeAliasTemplate(); void testDeclarationsInsideMacroExpansion(); void benchDUChainBuilder(); void testGccCompatibility(); void testQtIntegration(); private: QScopedPointer m_provider; KDevelop::TestProjectController* m_projectController; }; #endif // DUCHAINTEST_H