diff --git a/plugins/clang/duchain/builder.cpp b/plugins/clang/duchain/builder.cpp index e4115be3af..484265dc01 100644 --- a/plugins/clang/duchain/builder.cpp +++ b/plugins/clang/duchain/builder.cpp @@ -1,1585 +1,1590 @@ /* * This file is part of KDevelop * * Copyright 2013 Olivier de Gaalon * Copyright 2015 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 "builder.h" #include "util/clangdebug.h" #include "templatehelpers.h" #include "cursorkindtraits.h" #include "clangducontext.h" #include "macrodefinition.h" #include "types/classspecializationtype.h" #include "util/clangutils.h" #include "util/clangtypes.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /// Turn on for debugging the declaration building #define IF_DEBUG(x) namespace { // TODO: this is ugly, can we find a better alternative? bool jsonTestRun() { static bool runningTest = qEnvironmentVariableIsSet("KDEV_CLANG_JSON_TEST_RUN"); return runningTest; } //BEGIN helpers // HACK: current alias type template machinery is badly broken wrt spelling // location, work around this by adjusting all references to point to child // type alias node with proper location // TODO: investigate upstream implementation of CXCursor_TypeAliasTemplateDecl CXCursor findEmbeddedTypeAlias(CXCursor aliasTemplate) { auto result = clang_getNullCursor(); clang_visitChildren(aliasTemplate, [] (CXCursor cursor, CXCursor, CXClientData data) { if (clang_getCursorKind(cursor) == CXCursor_TypeAliasDecl) { auto res = reinterpret_cast(data); *res = cursor; return CXChildVisit_Break; } return CXChildVisit_Continue; }, &result); return result; } /** * Find the cursor that cursor @p cursor references * * First tries to get the referenced cursor via clang_getCursorReferenced, * and if that fails, tries to get them via clang_getOverloadedDecl * (which returns the referenced cursor for CXCursor_OverloadedDeclRef, for example) * * @return Valid cursor on success, else null cursor */ CXCursor referencedCursor(CXCursor cursor) { auto referenced = clang_getCursorReferenced(cursor); // HACK: see notes at getEmbeddedTypeAlias() if (clang_getCursorKind(referenced) == CXCursor_TypeAliasTemplateDecl) { return findEmbeddedTypeAlias(referenced); } if (!clang_equalCursors(cursor, referenced)) { return referenced; } // get the first result for now referenced = clang_getOverloadedDecl(cursor, 0); if (!clang_Cursor_isNull(referenced)) { return referenced; } return clang_getNullCursor(); } Identifier makeId(CXCursor cursor) { if (CursorKindTraits::isClassTemplate(cursor.kind)) { // TODO: how to handle functions here? We don't want to add the "real" function arguments here // and there does not seem to be an API to get the template arguments for non-specializations easily // NOTE: using the QString overload of the Identifier ctor here, so that the template name gets parsed return Identifier(ClangString(clang_getCursorDisplayName(cursor)).toString()); } return Identifier(ClangString(clang_getCursorSpelling(cursor)).toIndexed()); } #if CINDEX_VERSION_MINOR >= 100 // FIXME https://bugs.llvm.org/show_bug.cgi?id=35333 QByteArray makeComment(CXComment comment) { if (Q_UNLIKELY(jsonTestRun())) { auto kind = clang_Comment_getKind(comment); if (kind == CXComment_Text) return ClangString(clang_TextComment_getText(comment)).toByteArray(); QByteArray text; int numChildren = clang_Comment_getNumChildren(comment); for (int i = 0; i < numChildren; ++i) text += makeComment(clang_Comment_getChild(comment, i)); return text; } return ClangString(clang_FullComment_getAsHTML(comment)).toByteArray(); } #endif AbstractType* createDelayedType(CXType type) { auto t = new DelayedType; QString typeName = ClangString(clang_getTypeSpelling(type)).toString(); typeName.remove(QStringLiteral("const ")); typeName.remove(QStringLiteral("volatile ")); t->setIdentifier(IndexedTypeIdentifier(typeName)); return t; } void contextImportDecl(DUContext* context, const DeclarationPointer& decl) { auto top = context->topContext(); if (auto import = decl->logicalInternalContext(top)) { context->addImportedParentContext(import); context->topContext()->updateImportsCache(); } } //END helpers CXChildVisitResult visitCursor(CXCursor cursor, CXCursor parent, CXClientData data); //BEGIN IdType template struct IdType; template struct IdType::type> { typedef StructureType Type; }; template struct IdType::type> { typedef TypeAliasType Type; }; template struct IdType::type> { typedef TypeAliasType Type; }; template struct IdType::type> { typedef EnumerationType Type; }; template struct IdType::type> { typedef EnumeratorType Type; }; //END IdType //BEGIN DeclType template struct DeclType; template struct DeclType::type> { typedef Declaration Type; }; template struct DeclType::type> { typedef MacroDefinition Type; }; template struct DeclType::type> { typedef ForwardDeclaration Type; }; template struct DeclType::type> { typedef ClassDeclaration Type; }; template struct DeclType::type> { typedef ClassFunctionDeclaration Type; }; template struct DeclType::type> { typedef FunctionDeclaration Type; }; template struct DeclType::type> { typedef FunctionDefinition Type; }; template struct DeclType::type> { typedef NamespaceAliasDeclaration Type; }; template struct DeclType::type> { typedef ClassMemberDeclaration Type; }; //END DeclType //BEGIN CurrentContext struct CurrentContext { CurrentContext(DUContext* context, QSet keepAliveContexts) : context(context) , keepAliveContexts(keepAliveContexts) { DUChainReadLocker lock; previousChildContexts = context->childContexts(); previousChildDeclarations = context->localDeclarations(); } ~CurrentContext() { DUChainWriteLocker lock; foreach (auto childContext, previousChildContexts) { if (!keepAliveContexts.contains(childContext)) { delete childContext; } } qDeleteAll(previousChildDeclarations); if (resortChildContexts) { context->resortChildContexts(); } if (resortLocalDeclarations) { context->resortLocalDeclarations(); } } DUContext* context; // when updating, this contains child contexts of the current parent context QVector previousChildContexts; // when updating, this contains contexts that must not be deleted QSet keepAliveContexts; // when updating, this contains child declarations of the current parent context QVector previousChildDeclarations; bool resortChildContexts = false; bool resortLocalDeclarations = false; }; //END CurrentContext //BEGIN Visitor struct Visitor { explicit Visitor(CXTranslationUnit tu, CXFile file, const IncludeFileContexts& includes, const bool update); AbstractType *makeType(CXType type, CXCursor parent); AbstractType::Ptr makeAbsType(CXType type, CXCursor parent) { return AbstractType::Ptr(makeType(type, parent)); } //BEGIN dispatch* template = dummy> CXChildVisitResult dispatchCursor(CXCursor cursor, CXCursor parent); template = dummy> CXChildVisitResult dispatchCursor(CXCursor cursor, CXCursor parent); template = dummy> CXChildVisitResult dispatchCursor(CXCursor cursor, CXCursor parent); CXChildVisitResult dispatchTypeAliasTemplate(CXCursor cursor, CXCursor parent) { return CursorKindTraits::isClass(clang_getCursorKind(parent)) ? buildTypeAliasTemplateDecl(cursor) : buildTypeAliasTemplateDecl(cursor); } template AbstractType *dispatchType(CXType type, CXCursor cursor) { IF_DEBUG(clangDebug() << "TK:" << type.kind;) auto kdevType = createType(type, cursor); if (kdevType) { setTypeModifiers(type, kdevType); } return kdevType; } //BEGIN dispatch* //BEGIN build* template CXChildVisitResult buildDeclaration(CXCursor cursor); template CXChildVisitResult buildTypeAliasTemplateDecl(CXCursor cursor); CXChildVisitResult buildUse(CXCursor cursor); CXChildVisitResult buildMacroExpansion(CXCursor cursor); + + template CXChildVisitResult buildCompoundStatement(CXCursor cursor); CXChildVisitResult buildCXXBaseSpecifier(CXCursor cursor); CXChildVisitResult buildParmDecl(CXCursor cursor); //END build* //BEGIN create* template DeclType* createDeclarationCommon(CXCursor cursor, const Identifier& id) { auto range = ClangHelpers::cursorSpellingNameRange(cursor, id); if (id.isEmpty()) { // This is either an anonymous function parameter e.g.: void f(int); // Or anonymous struct/class/union e.g.: struct {} anonymous; // Set empty range for it range.end = range.start; } // check if cursor is inside a macro expansion auto clangRange = clang_Cursor_getSpellingNameRange(cursor, 0, 0); unsigned int expansionLocOffset; const auto spellingLocation = clang_getRangeStart(clangRange); clang_getExpansionLocation(spellingLocation, nullptr, nullptr, nullptr, &expansionLocOffset); if (m_macroExpansionLocations.contains(expansionLocOffset)) { unsigned int spellingLocOffset; clang_getSpellingLocation(spellingLocation, nullptr, nullptr, nullptr, &spellingLocOffset); // Set empty ranges for declarations inside macro expansion if (spellingLocOffset == expansionLocOffset) { range.end = range.start; } } if (m_update) { const IndexedIdentifier indexedId(id); DUChainWriteLocker lock; auto it = m_parentContext->previousChildDeclarations.begin(); while (it != m_parentContext->previousChildDeclarations.end()) { auto decl = dynamic_cast(*it); if (decl && decl->indexedIdentifier() == indexedId) { decl->setRange(range); m_parentContext->resortLocalDeclarations = true; setDeclData(cursor, decl); m_cursorToDeclarationCache[cursor] = decl; m_parentContext->previousChildDeclarations.erase(it); return decl; } ++it; } } auto decl = new DeclType(range, nullptr); decl->setIdentifier(id); m_cursorToDeclarationCache[cursor] = decl; setDeclData(cursor, decl); { DUChainWriteLocker lock; decl->setContext(m_parentContext->context); } return decl; } template Declaration* createDeclaration(CXCursor cursor, const Identifier& id, DUContext *context) { auto decl = createDeclarationCommon(cursor, id); auto type = createType(cursor); DUChainWriteLocker lock; if (context) decl->setInternalContext(context); setDeclType(decl, type); setDeclInCtxtData(cursor, decl); return decl; } template DUContext* createContext(CXCursor cursor, const QualifiedIdentifier& scopeId = {}) { // wtf: why is the DUContext API requesting a QID when it needs a plain Id?! // see: testNamespace auto range = ClangRange(clang_getCursorExtent(cursor)).toRangeInRevision(); DUChainWriteLocker lock; if (m_update) { const IndexedQualifiedIdentifier indexedScopeId(scopeId); auto it = m_parentContext->previousChildContexts.begin(); while (it != m_parentContext->previousChildContexts.end()) { auto ctx = *it; if (ctx->type() == Type && ctx->indexedLocalScopeIdentifier() == indexedScopeId) { ctx->setRange(range); m_parentContext->resortChildContexts = true; m_parentContext->previousChildContexts.erase(it); return ctx; } ++it; } } //TODO: (..type, id..) constructor for DUContext? auto context = new ClangNormalDUContext(range, m_parentContext->context); context->setType(Type); context->setLocalScopeIdentifier(scopeId); if (Type == DUContext::Other || Type == DUContext::Function) context->setInSymbolTable(false); if (CK == CXCursor_CXXMethod) { CXCursor semParent = clang_getCursorSemanticParent(cursor); // only import the semantic parent if it differs from the lexical parent if (!clang_Cursor_isNull(semParent) && !clang_equalCursors(semParent, clang_getCursorLexicalParent(cursor))) { auto semParentDecl = findDeclaration(semParent); if (semParentDecl) { contextImportDecl(context, semParentDecl); } } } return context; } template = dummy> AbstractType *createType(CXType, CXCursor) { // TODO: would be nice to instantiate a ConstantIntegralType here and set a value if possible // but unfortunately libclang doesn't offer API to that // also see http://marc.info/?l=cfe-commits&m=131609142917881&w=2 return new IntegralType(CursorKindTraits::integralType(TK)); } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto ptr = new PointerType; ptr->setBaseType(makeAbsType(clang_getPointeeType(type), parent)); return ptr; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto arr = new ArrayType; arr->setDimension((TK == CXType_IncompleteArray || TK == CXType_VariableArray || TK == CXType_DependentSizedArray) ? 0 : clang_getArraySize(type)); arr->setElementType(makeAbsType(clang_getArrayElementType(type), parent)); return arr; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto ref = new ReferenceType; ref->setIsRValue(type.kind == CXType_RValueReference); ref->setBaseType(makeAbsType(clang_getPointeeType(type), parent)); return ref; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto func = new FunctionType; func->setReturnType(makeAbsType(clang_getResultType(type), parent)); const int numArgs = clang_getNumArgTypes(type); for (int i = 0; i < numArgs; ++i) { func->addArgument(makeAbsType(clang_getArgType(type, i), parent)); } if (clang_isFunctionTypeVariadic(type)) { auto type = new DelayedType; static const auto id = IndexedTypeIdentifier(QStringLiteral("...")); type->setIdentifier(id); type->setKind(DelayedType::Unresolved); func->addArgument(AbstractType::Ptr(type)); } return func; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { DeclarationPointer decl = findDeclaration(clang_getTypeDeclaration(type)); DUChainReadLocker lock; if (!decl) { // probably a forward-declared type decl = ClangHelpers::findForwardDeclaration(type, m_parentContext->context, parent); } if (clang_Type_getNumTemplateArguments(type) != -1) { return createClassTemplateSpecializationType(type, decl); } auto t = new StructureType; if (decl) { t->setDeclaration(decl.data()); } else { // fallback, at least give the spelling to the user t->setDeclarationId(DeclarationId(IndexedQualifiedIdentifier(QualifiedIdentifier(ClangString(clang_getTypeSpelling(type)).toString())))); } return t; } template = dummy> AbstractType *createType(CXType type, CXCursor) { auto t = new EnumerationType; setIdTypeDecl(clang_getTypeDeclaration(type), t); return t; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto t = new TypeAliasType; CXCursor location = clang_getTypeDeclaration(type); t->setType(makeAbsType(clang_getTypedefDeclUnderlyingType(location), parent)); setIdTypeDecl(location, t); return t; } template = dummy> AbstractType *createType(CXType, CXCursor /*parent*/) { auto t = new DelayedType; static const IndexedTypeIdentifier id(QLatin1String(CursorKindTraits::delayedTypeName(TK))); t->setIdentifier(id); return t; } template = dummy> AbstractType *createType(CXType type, CXCursor /*parent*/) { return createDelayedType(type); } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto numTA = clang_Type_getNumTemplateArguments(type); // TODO: We should really expose more types to libclang! if (numTA != -1 && ClangString(clang_getTypeSpelling(type)).toString().contains(QLatin1Char('<'))) { return createClassTemplateSpecializationType(type); } // Maybe it's the ElaboratedType. E.g.: "struct Type foo();" or "NS::Type foo();" or "void foo(enum Enum e);" e.t.c. auto oldType = type; type = clang_getCanonicalType(type); bool isElaboratedType = type.kind != CXType_FunctionProto && type.kind != CXType_FunctionNoProto && type.kind != CXType_Unexposed && type.kind != CXType_Invalid && type.kind != CXType_Record; return !isElaboratedType ? createDelayedType(oldType) : makeType(type, parent); } template = dummy> typename IdType::Type *createType(CXCursor) { return new typename IdType::Type; } template = dummy> EnumeratorType *createType(CXCursor cursor) { auto type = new EnumeratorType; type->setValue(clang_getEnumConstantDeclUnsignedValue(cursor)); return type; } template = dummy> TypeAliasType *createType(CXCursor cursor) { auto type = new TypeAliasType; type->setType(makeAbsType(clang_getTypedefDeclUnderlyingType(cursor), cursor)); return type; } template = dummy> AbstractType* createType(CXCursor cursor) { auto clangType = clang_getCursorType(cursor); #if CINDEX_VERSION_MINOR < 31 if (clangType.kind == CXType_Unexposed) { // Clang sometimes can return CXType_Unexposed for CXType_FunctionProto kind. E.g. if it's AttributedType. return dispatchType(clangType, cursor); } #endif return makeType(clangType, cursor); } template = dummy> AbstractType *createType(CXCursor) { auto t = new DelayedType; static const IndexedTypeIdentifier id(QStringLiteral("Label")); t->setIdentifier(id); return t; } template = dummy> AbstractType *createType(CXCursor cursor) { auto clangType = clang_getCursorType(cursor); return makeType(clangType, cursor); } #if CINDEX_VERSION_MINOR >= 32 template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto deducedType = clang_getCanonicalType(type); bool isDeduced = deducedType.kind != CXType_Invalid && deducedType.kind != CXType_Auto; return !isDeduced ? createDelayedType(type) : makeType(deducedType, parent); } #endif #if CINDEX_VERSION_MINOR >= 34 template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto underyingType = clang_Type_getNamedType(type); return makeType(underyingType, parent); } #endif /// @param declaration an optional declaration that will be associated with created type AbstractType* createClassTemplateSpecializationType(CXType type, const DeclarationPointer& declaration = {}) { auto numTA = clang_Type_getNumTemplateArguments(type); Q_ASSERT(numTA != -1); auto typeDecl = clang_getTypeDeclaration(type); if (!declaration && typeDecl.kind == CXCursor_NoDeclFound) { // clang_getTypeDeclaration doesn't handle all types, fall back to delayed type... return createDelayedType(type); } QStringList typesStr; QString tStr = ClangString(clang_getTypeSpelling(type)).toString(); ParamIterator iter(QStringLiteral("<>"), tStr); while (iter) { typesStr.append(*iter); ++iter; } auto cst = new ClassSpecializationType; for (int i = 0; i < numTA; i++) { auto argumentType = clang_Type_getTemplateArgumentAsType(type, i); AbstractType::Ptr currentType; if (argumentType.kind == CXType_Invalid) { if(i >= typesStr.size()){ currentType = createDelayedType(argumentType); } else { auto t = new DelayedType; t->setIdentifier(IndexedTypeIdentifier(typesStr[i])); currentType = t; } } else { currentType = makeType(argumentType, typeDecl); } if (currentType) { cst->addParameter(currentType->indexed()); } } auto decl = declaration ? declaration : findDeclaration(typeDecl); DUChainReadLocker lock; if (decl) { cst->setDeclaration(decl.data()); } else { // fallback, at least give the spelling to the user Identifier id(tStr); id.clearTemplateIdentifiers(); cst->setDeclarationId(DeclarationId(IndexedQualifiedIdentifier(QualifiedIdentifier(id)))); } return cst; } //END create* //BEGIN setDeclData template void setDeclData(CXCursor cursor, Declaration *decl, bool setComment = true) const; template void setDeclData(CXCursor cursor, MacroDefinition* decl) const; template void setDeclData(CXCursor cursor, ClassMemberDeclaration *decl) const; template = dummy> void setDeclData(CXCursor cursor, ClassDeclaration* decl) const; template = dummy> void setDeclData(CXCursor cursor, ClassDeclaration* decl) const; template void setDeclData(CXCursor cursor, AbstractFunctionDeclaration* decl) const; template void setDeclData(CXCursor cursor, ClassFunctionDeclaration* decl) const; template void setDeclData(CXCursor cursor, FunctionDeclaration *decl, bool setComment = true) const; template void setDeclData(CXCursor cursor, FunctionDefinition *decl) const; template void setDeclData(CXCursor cursor, NamespaceAliasDeclaration *decl) const; //END setDeclData //BEGIN setDeclInCtxtData template void setDeclInCtxtData(CXCursor, Declaration*) { //No-op } template void setDeclInCtxtData(CXCursor cursor, ClassFunctionDeclaration *decl) { // HACK to retrieve function-constness // This looks like a bug in Clang -- In theory setTypeModifiers should take care of setting the const modifier // however, clang_isConstQualifiedType() for TK == CXType_FunctionProto always returns false // TODO: Debug further auto type = decl->abstractType(); if (type) { if (clang_CXXMethod_isConst(cursor)) { type->setModifiers(type->modifiers() | AbstractType::ConstModifier); decl->setAbstractType(type); } } } template void setDeclInCtxtData(CXCursor cursor, FunctionDefinition *def) { setDeclInCtxtData(cursor, static_cast(def)); const CXCursor canon = clang_getCanonicalCursor(cursor); if (auto decl = findDeclaration(canon)) { def->setDeclaration(decl.data()); } } //END setDeclInCtxtData //BEGIN setDeclType template void setDeclType(Declaration *decl, typename IdType::Type *type) { setDeclType(decl, static_cast(type)); setDeclType(decl, static_cast(type)); } template void setDeclType(Declaration *decl, IdentifiedType *type) { type->setDeclaration(decl); } template void setDeclType(Declaration *decl, AbstractType *type) { decl->setAbstractType(AbstractType::Ptr(type)); } //END setDeclType template void setTypeModifiers(CXType type, AbstractType* kdevType) const; const CXFile m_file; const IncludeFileContexts &m_includes; DeclarationPointer findDeclaration(CXCursor cursor) const; void setIdTypeDecl(CXCursor typeCursor, IdentifiedType* idType) const; std::unordered_map> m_uses; /// At these location offsets (cf. @ref clang_getExpansionLocation) we encountered macro expansions QSet m_macroExpansionLocations; mutable QHash m_cursorToDeclarationCache; CurrentContext *m_parentContext; const bool m_update; }; //BEGIN setTypeModifiers template void Visitor::setTypeModifiers(CXType type, AbstractType* kdevType) const { quint64 modifiers = 0; if (clang_isConstQualifiedType(type)) { modifiers |= AbstractType::ConstModifier; } if (clang_isVolatileQualifiedType(type)) { modifiers |= AbstractType::VolatileModifier; } if (TK == CXType_Short || TK == CXType_UShort) { modifiers |= AbstractType::ShortModifier; } if (TK == CXType_Long || TK == CXType_LongDouble || TK == CXType_ULong) { modifiers |= AbstractType::LongModifier; } if (TK == CXType_LongLong || TK == CXType_ULongLong) { modifiers |= AbstractType::LongLongModifier; } if (TK == CXType_SChar) { modifiers |= AbstractType::SignedModifier; } if (TK == CXType_UChar || TK == CXType_UInt || TK == CXType_UShort || TK == CXType_UInt128 || TK == CXType_ULong || TK == CXType_ULongLong) { modifiers |= AbstractType::UnsignedModifier; } kdevType->setModifiers(modifiers); } //END setTypeModifiers //BEGIN dispatchCursor template> CXChildVisitResult Visitor::dispatchCursor(CXCursor cursor, CXCursor parent) { const bool decision = CursorKindTraits::isClass(clang_getCursorKind(parent)); return decision ? dispatchCursor(cursor, parent) : dispatchCursor(cursor, parent); } template> CXChildVisitResult Visitor::dispatchCursor(CXCursor cursor, CXCursor parent) { IF_DEBUG(clangDebug() << "IsInClass:" << IsInClass << "- isDefinition:" << IsDefinition;) const bool isDefinition = clang_isCursorDefinition(cursor); return isDefinition ? dispatchCursor(cursor, parent) : dispatchCursor(cursor, parent); } template> CXChildVisitResult Visitor::dispatchCursor(CXCursor cursor, CXCursor parent) { IF_DEBUG(clangDebug() << "IsInClass:" << IsInClass << "- isDefinition:" << IsDefinition;) // We may end up visiting the same cursor twice in some cases // see discussion on https://git.reviewboard.kde.org/r/119526/ // TODO: Investigate why this is happening in libclang if ((CursorKindTraits::isClass(CK) || CK == CXCursor_EnumDecl) && clang_getCursorKind(parent) == CXCursor_VarDecl) { return CXChildVisit_Continue; } constexpr bool isClassMember = IsInClass == Decision::True; constexpr bool isDefinition = IsDefinition == Decision::True; // always build a context for class templates and functions, otherwise we "leak" // the function/template parameter declarations into the surrounding context, // which can lead to interesting bugs, like https://bugs.kde.org/show_bug.cgi?id=368067 constexpr bool hasContext = isDefinition || CursorKindTraits::isFunction(CK) || CursorKindTraits::isClassTemplate(CK); return buildDeclaration::Type, hasContext>(cursor); } //END dispatchCursor //BEGIN setDeclData template void Visitor::setDeclData(CXCursor cursor, Declaration *decl, bool setComment) const { if (setComment) #if CINDEX_VERSION_MINOR < 100 // FIXME https://bugs.llvm.org/show_bug.cgi?id=35333 decl->setComment(KDevelop::formatComment(QByteArray(clang_getCString(clang_Cursor_getRawCommentText(cursor))))); #else decl->setComment(makeComment(clang_Cursor_getParsedComment(cursor))); #endif if (CursorKindTraits::isAliasType(CK)) { decl->setIsTypeAlias(true); } if (CK == CXCursor_Namespace) decl->setKind(Declaration::Namespace); if (CK == CXCursor_EnumDecl || CK == CXCursor_EnumConstantDecl || CursorKindTraits::isClass(CK) || CursorKindTraits::isAliasType(CK)) decl->setKind(Declaration::Type); int isAlwaysDeprecated; clang_getCursorPlatformAvailability(cursor, &isAlwaysDeprecated, nullptr, nullptr, nullptr, nullptr, 0); decl->setDeprecated(isAlwaysDeprecated); } template void Visitor::setDeclData(CXCursor cursor, MacroDefinition* decl) const { setDeclData(cursor, static_cast(decl)); if (m_update) { decl->clearParameters(); } auto unit = clang_Cursor_getTranslationUnit(cursor); auto range = clang_getCursorExtent(cursor); // TODO: Quite lacking API in libclang here. // No way to find out if this macro is function-like or not // cf. http://clang.llvm.org/doxygen/classclang_1_1MacroInfo.html // And no way to get the actual definition text range // Should be quite easy to expose that in libclang, though // Let' still get some basic support for this and parse on our own, it's not that difficult const QString contents = QString::fromUtf8(ClangUtils::getRawContents(unit, range)); const int firstOpeningParen = contents.indexOf(QLatin1Char('(')); const int firstWhitespace = contents.indexOf(QLatin1Char(' ')); const bool isFunctionLike = (firstOpeningParen != -1) && (firstOpeningParen < firstWhitespace); decl->setFunctionLike(isFunctionLike); // now extract the actual definition text int start = -1; if (isFunctionLike) { const int closingParen = findClose(contents, firstOpeningParen); if (closingParen != -1) { start = closingParen + 2; // + ')' + ' ' // extract macro function parameters const QString parameters = contents.mid(firstOpeningParen, closingParen - firstOpeningParen + 1); ParamIterator paramIt(QStringLiteral("():"), parameters, 0); while (paramIt) { decl->addParameter(IndexedString(*paramIt)); ++paramIt; } } } else { start = firstWhitespace + 1; // + ' ' } if (start == -1) { // unlikely: invalid macro definition, insert the complete #define statement decl->setDefinition(IndexedString(QLatin1String("#define ") + contents)); } else if (start < contents.size()) { decl->setDefinition(IndexedString(contents.mid(start))); } // else: macro has no body => leave the definition text empty } template void Visitor::setDeclData(CXCursor cursor, ClassMemberDeclaration *decl) const { setDeclData(cursor, static_cast(decl)); //A CXCursor_VarDecl in a class is static (otherwise it'd be a CXCursor_FieldDecl) if (CK == CXCursor_VarDecl) decl->setStatic(true); decl->setAccessPolicy(CursorKindTraits::kdevAccessPolicy(clang_getCXXAccessSpecifier(cursor))); #if CINDEX_VERSION_MINOR >= 32 decl->setMutable(clang_CXXField_isMutable(cursor)); #endif #if CINDEX_VERSION_MINOR >= 30 if (!jsonTestRun()) { auto offset = clang_Cursor_getOffsetOfField(cursor); if (offset >= 0) { // don't add this info to the json tests, it invalidates the comment structure auto type = clang_getCursorType(cursor); auto sizeOf = clang_Type_getSizeOf(type); auto alignedTo = clang_Type_getAlignOf(type); const auto byteOffset = offset / 8; const auto bitOffset = offset % 8; const QString byteOffsetStr = i18np("1 Byte", "%1 Bytes", byteOffset); const QString bitOffsetStr = bitOffset ? i18np("1 Bit", "%1 Bits", bitOffset) : QString(); const QString offsetStr = bitOffset ? i18nc("%1: bytes, %2: bits", "%1, %2", byteOffsetStr, bitOffsetStr) : byteOffsetStr; decl->setComment(decl->comment() + i18n("

offset in parent: %1; " "size: %2 Bytes; " "aligned to: %3 Bytes

", offsetStr, sizeOf, alignedTo).toUtf8()); } } #endif } template> void Visitor::setDeclData(CXCursor cursor, ClassDeclaration* decl) const { CXCursorKind kind = clang_getTemplateCursorKind(cursor); switch (kind) { case CXCursor_UnionDecl: setDeclData(cursor, decl); break; case CXCursor_StructDecl: setDeclData(cursor, decl); break; case CXCursor_ClassDecl: setDeclData(cursor, decl); break; default: Q_ASSERT(false); break; } } template> void Visitor::setDeclData(CXCursor cursor, ClassDeclaration* decl) const { if (m_update) { decl->clearBaseClasses(); } setDeclData(cursor, static_cast(decl)); if (CK == CXCursor_UnionDecl) decl->setClassType(ClassDeclarationData::Union); if (CK == CXCursor_StructDecl) decl->setClassType(ClassDeclarationData::Struct); if (clang_isCursorDefinition(cursor)) { decl->setDeclarationIsDefinition(true); } if (!jsonTestRun()) { // don't add this info to the json tests, it invalidates the comment structure auto type = clang_getCursorType(cursor); auto sizeOf = clang_Type_getSizeOf(type); auto alignOf = clang_Type_getAlignOf(type); if (sizeOf >= 0 && alignOf >= 0) { decl->setComment(decl->comment() + i18n("

size: %1 Bytes; " "aligned to: %2 Bytes

", sizeOf, alignOf).toUtf8()); } } } template void Visitor::setDeclData(CXCursor cursor, AbstractFunctionDeclaration* decl) const { if (m_update) { decl->clearDefaultParameters(); } // No setDeclData(...) here: AbstractFunctionDeclaration is an interface // TODO: Can we get the default arguments directly from Clang? // also see http://clang-developers.42468.n3.nabble.com/Finding-default-value-for-function-argument-with-clang-c-API-td4036919.html const QVector defaultArgs = ClangUtils::getDefaultArguments(cursor, ClangUtils::MinimumSize); foreach (const QString& defaultArg, defaultArgs) { decl->addDefaultParameter(IndexedString(defaultArg)); } } template void Visitor::setDeclData(CXCursor cursor, ClassFunctionDeclaration* decl) const { setDeclData(cursor, static_cast(decl)); setDeclData(cursor, static_cast(decl)); decl->setIsAbstract(clang_CXXMethod_isPureVirtual(cursor)); decl->setStatic(clang_CXXMethod_isStatic(cursor)); decl->setVirtual(clang_CXXMethod_isVirtual(cursor)); // TODO: Set flags in one go? (needs new API in kdevplatform) const auto attributes = ClangUtils::specialAttributes(cursor); decl->setIsSignal(attributes & FunctionSignalFlag); decl->setIsSlot(attributes & FunctionSlotFlag); decl->setIsFinal(attributes & FinalFunctionFlag); } template void Visitor::setDeclData(CXCursor cursor, FunctionDeclaration *decl, bool setComment) const { setDeclData(cursor, static_cast(decl)); setDeclData(cursor, static_cast(decl), setComment); } template void Visitor::setDeclData(CXCursor cursor, FunctionDefinition *decl) const { bool setComment = clang_equalCursors(clang_getCanonicalCursor(cursor), cursor); setDeclData(cursor, static_cast(decl), setComment); } template void Visitor::setDeclData(CXCursor cursor, NamespaceAliasDeclaration *decl) const { setDeclData(cursor, static_cast(decl)); clang_visitChildren(cursor, [] (CXCursor cursor, CXCursor parent, CXClientData data) -> CXChildVisitResult { if (clang_getCursorKind(cursor) == CXCursor_NamespaceRef) { const auto id = QualifiedIdentifier(ClangString(clang_getCursorSpelling(cursor)).toString()); reinterpret_cast(data)->setImportIdentifier(id); return CXChildVisit_Break; } else { return visitCursor(cursor, parent, data); } }, decl); } //END setDeclData //BEGIN build* template CXChildVisitResult Visitor::buildDeclaration(CXCursor cursor) { auto id = makeId(cursor); if (CK == CXCursor_UnexposedDecl && id.isEmpty()) { // skip unexposed declarations that have no identifier set // this is useful to skip e.g. friend declarations return CXChildVisit_Recurse; } IF_DEBUG(clangDebug() << "id:" << id << "- CK:" << CK << "- DeclType:" << typeid(DeclType).name() << "- hasContext:" << hasContext;) // Code path for class declarations that may be defined "out-of-line", e.g. // "SomeNameSpace::SomeClass {};" QScopedPointer helperContext; if (CursorKindTraits::isClass(CK) || CursorKindTraits::isFunction(CK)) { const auto lexicalParent = clang_getCursorLexicalParent(cursor); const auto semanticParent = clang_getCursorSemanticParent(cursor); const bool isOutOfLine = !clang_equalCursors(lexicalParent, semanticParent); if (isOutOfLine) { const QString scope = ClangUtils::getScope(cursor); auto context = createContext(cursor, QualifiedIdentifier(scope)); helperContext.reset(new CurrentContext(context, m_parentContext->keepAliveContexts)); } } // if helperContext is null, this is a no-op PushValue pushCurrent(m_parentContext, helperContext.isNull() ? m_parentContext : helperContext.data()); if (hasContext) { auto context = createContext(cursor, QualifiedIdentifier(id)); createDeclaration(cursor, id, context); CurrentContext newParent(context, m_parentContext->keepAliveContexts); PushValue pushCurrent(m_parentContext, &newParent); clang_visitChildren(cursor, &visitCursor, this); return CXChildVisit_Continue; } createDeclaration(cursor, id, nullptr); return CXChildVisit_Recurse; } CXChildVisitResult Visitor::buildParmDecl(CXCursor cursor) { return buildDeclaration::Type, false>(cursor); } CXChildVisitResult Visitor::buildUse(CXCursor cursor) { m_uses[m_parentContext->context].push_back(cursor); return cursor.kind == CXCursor_DeclRefExpr || cursor.kind == CXCursor_MemberRefExpr ? CXChildVisit_Recurse : CXChildVisit_Continue; } CXChildVisitResult Visitor::buildMacroExpansion(CXCursor cursor) { buildUse(cursor); // cache that we encountered a macro expansion at this location unsigned int offset; clang_getSpellingLocation(clang_getCursorLocation(cursor), nullptr, nullptr, nullptr, &offset); m_macroExpansionLocations << offset; return CXChildVisit_Recurse; } +template CXChildVisitResult Visitor::buildCompoundStatement(CXCursor cursor) { - if (m_parentContext->context->type() == DUContext::Function) + if (CK == CXCursor_LambdaExpr || m_parentContext->context->type() == DUContext::Function) { - auto context = createContext(cursor); + auto context = createContext(cursor); CurrentContext newParent(context, m_parentContext->keepAliveContexts); PushValue pushCurrent(m_parentContext, &newParent); clang_visitChildren(cursor, &visitCursor, this); return CXChildVisit_Continue; } return CXChildVisit_Recurse; } CXChildVisitResult Visitor::buildCXXBaseSpecifier(CXCursor cursor) { auto currentContext = m_parentContext->context; bool virtualInherited = clang_isVirtualBase(cursor); Declaration::AccessPolicy access = CursorKindTraits::kdevAccessPolicy(clang_getCXXAccessSpecifier(cursor)); auto classDeclCursor = clang_getCursorReferenced(cursor); auto decl = findDeclaration(classDeclCursor); if (!decl) { // this happens for templates with template-dependent base classes e.g. - dunno whether we can/should do more here clangDebug() << "failed to find declaration for base specifier:" << clang_getCursorDisplayName(cursor); return CXChildVisit_Recurse; } DUChainWriteLocker lock; contextImportDecl(currentContext, decl); auto classDecl = dynamic_cast(currentContext->owner()); Q_ASSERT(classDecl); classDecl->addBaseClass({decl->indexedType(), access, virtualInherited}); return CXChildVisit_Recurse; } template CXChildVisitResult Visitor::buildTypeAliasTemplateDecl(CXCursor cursor) { auto aliasDecl = findEmbeddedTypeAlias(cursor); // NOTE: using aliasDecl here averts having to add a workaround to makeId() auto id = makeId(aliasDecl); // create template context to prevent leaking child template params auto context = createContext(cursor, QualifiedIdentifier(id)); using DeclType = typename DeclType::Type; createDeclaration(aliasDecl, id, context); CurrentContext newParent(context, m_parentContext->keepAliveContexts); PushValue pushCurrent(m_parentContext, &newParent); clang_visitChildren(cursor, [] (CXCursor cursor, CXCursor parent, CXClientData data) { // NOTE: immediately recurse into embedded alias decl return clang_getCursorKind(cursor) == CXCursor_TypeAliasDecl ? CXChildVisit_Recurse : visitCursor(cursor, parent, data); }, this); return CXChildVisit_Continue; } //END build* DeclarationPointer Visitor::findDeclaration(CXCursor cursor) const { const auto it = m_cursorToDeclarationCache.constFind(cursor); if (it != m_cursorToDeclarationCache.constEnd()) { return *it; } // fallback, and cache result auto decl = ClangHelpers::findDeclaration(cursor, m_includes); m_cursorToDeclarationCache.insert(cursor, decl); return decl; } void Visitor::setIdTypeDecl(CXCursor typeCursor, IdentifiedType* idType) const { DeclarationPointer decl = findDeclaration(typeCursor); DUChainReadLocker lock; if (decl) { idType->setDeclaration(decl.data()); } } AbstractType *Visitor::makeType(CXType type, CXCursor parent) { #define UseKind(TypeKind) case TypeKind: return dispatchType(type, parent) switch (type.kind) { UseKind(CXType_Void); UseKind(CXType_Bool); UseKind(CXType_Short); UseKind(CXType_UShort); UseKind(CXType_Int); UseKind(CXType_UInt); UseKind(CXType_Long); UseKind(CXType_ULong); UseKind(CXType_LongLong); UseKind(CXType_ULongLong); UseKind(CXType_Float); UseKind(CXType_LongDouble); UseKind(CXType_Double); UseKind(CXType_Char_U); UseKind(CXType_Char_S); UseKind(CXType_UChar); UseKind(CXType_SChar); UseKind(CXType_Char16); UseKind(CXType_Char32); UseKind(CXType_Pointer); UseKind(CXType_BlockPointer); UseKind(CXType_MemberPointer); UseKind(CXType_ObjCObjectPointer); UseKind(CXType_ConstantArray); UseKind(CXType_VariableArray); UseKind(CXType_IncompleteArray); UseKind(CXType_DependentSizedArray); UseKind(CXType_LValueReference); UseKind(CXType_RValueReference); UseKind(CXType_FunctionNoProto); UseKind(CXType_FunctionProto); UseKind(CXType_Record); UseKind(CXType_Enum); UseKind(CXType_Typedef); UseKind(CXType_Int128); UseKind(CXType_UInt128); UseKind(CXType_Vector); UseKind(CXType_Unexposed); UseKind(CXType_WChar); UseKind(CXType_ObjCInterface); UseKind(CXType_ObjCId); UseKind(CXType_ObjCClass); UseKind(CXType_ObjCSel); UseKind(CXType_NullPtr); #if CINDEX_VERSION_MINOR >= 32 UseKind(CXType_Auto); #endif #if CINDEX_VERSION_MINOR >= 34 UseKind(CXType_Elaborated); #endif #if CINDEX_VERSION_MINOR >= 38 UseKind(CXType_Float128); #endif UseKind(CXType_Complex); case CXType_Invalid: return nullptr; default: qCWarning(KDEV_CLANG) << "Unhandled type:" << type.kind << clang_getTypeSpelling(type); return nullptr; } #undef UseKind } RangeInRevision rangeInRevisionForUse(CXCursor cursor, CXCursorKind referencedCursorKind, CXSourceRange useRange, const QSet& macroExpansionLocations) { auto range = ClangRange(useRange).toRangeInRevision(); //TODO: Fix in clang, happens for operator<<, operator<, probably more if (clang_Range_isNull(useRange)) { useRange = clang_getCursorExtent(cursor); range = ClangRange(useRange).toRangeInRevision(); } if (referencedCursorKind == CXCursor_ConversionFunction) { range.end = range.start; range.start.column--; } // For uses inside macro expansions, create an empty use range at the spelling location // the empty range is required in order to not "overlap" the macro expansion range // and to allow proper navigation for the macro expansion // also see JSON test 'macros.cpp' if (clang_getCursorKind(cursor) != CXCursor_MacroExpansion) { unsigned int expansionLocOffset; const auto spellingLocation = clang_getRangeStart(useRange); clang_getExpansionLocation(spellingLocation, nullptr, nullptr, nullptr, &expansionLocOffset); if (macroExpansionLocations.contains(expansionLocOffset)) { unsigned int spellingLocOffset; clang_getSpellingLocation(spellingLocation, nullptr, nullptr, nullptr, &spellingLocOffset); if (spellingLocOffset == expansionLocOffset) { range.end = range.start; } } } else { // Workaround for wrong use range returned by clang for macro expansions const auto contents = ClangUtils::getRawContents(clang_Cursor_getTranslationUnit(cursor), useRange); const int firstOpeningParen = contents.indexOf('('); if (firstOpeningParen != -1) { range.end.column = range.start.column + firstOpeningParen; range.end.line = range.start.line; } } return range; } Visitor::Visitor(CXTranslationUnit tu, CXFile file, const IncludeFileContexts& includes, const bool update) : m_file(file) , m_includes(includes) , m_parentContext(nullptr) , m_update(update) { CXCursor tuCursor = clang_getTranslationUnitCursor(tu); auto top = includes[file]; // when updating, this contains child contexts that should be kept alive // even when they are not part of the AST anymore // this is required for some assistants, such as the signature assistant QSet keepAliveContexts; { DUChainReadLocker lock; foreach (const auto& problem, top->problems()) { const auto& desc = problem->description(); if (desc.startsWith(QLatin1String("Return type of out-of-line definition of '")) && desc.endsWith(QLatin1String("' differs from that in the declaration"))) { auto ctx = top->findContextAt(problem->range().start); // keep the context and its parents alive // this also keeps declarations in this context alive while (ctx) { keepAliveContexts << ctx; ctx = ctx->parentContext(); } } } } CurrentContext parent(top, keepAliveContexts); m_parentContext = &parent; clang_visitChildren(tuCursor, &visitCursor, this); if (m_update) { DUChainWriteLocker lock; top->deleteUsesRecursively(); } for (const auto &contextUses : m_uses) { for (const auto &cursor : contextUses.second) { auto referenced = referencedCursor(cursor); if (clang_Cursor_isNull(referenced)) { continue; } // first, try the canonical referenced cursor // this is important to get the correct function declaration e.g. auto canonicalReferenced = clang_getCanonicalCursor(referenced); auto used = findDeclaration(canonicalReferenced); if (!used) { // if the above failed, try the non-canonicalized version as a fallback // this is required for friend declarations that occur before // the real declaration. there, the canonical cursor points to // the friend declaration which is not what we are looking for used = findDeclaration(referenced); } if (!used) { // as a last resort, try to resolve the forward declaration DUChainReadLocker lock; DeclarationPointer decl = ClangHelpers::findForwardDeclaration(clang_getCursorType(referenced), contextUses.first, referenced); used = decl; if (!used) { continue; } } #if CINDEX_VERSION_MINOR >= 29 if (clang_Cursor_getNumTemplateArguments(referenced) >= 0) { // Ideally, we don't need this, but for function templates clang_getCanonicalCursor returns a function definition // See also the testUsesCreatedForDeclarations test DUChainReadLocker lock; used = DUChainUtils::declarationForDefinition(used.data()); } #endif const auto useRange = clang_getCursorReferenceNameRange(cursor, 0, 0); const auto range = rangeInRevisionForUse(cursor, referenced.kind, useRange, m_macroExpansionLocations); DUChainWriteLocker lock; auto usedIndex = top->indexForUsedDeclaration(used.data()); contextUses.first->createUse(usedIndex, range); } } } //END Visitor CXChildVisitResult visitCursor(CXCursor cursor, CXCursor parent, CXClientData data) { Visitor *visitor = static_cast(data); const auto kind = clang_getCursorKind(cursor); auto location = clang_getCursorLocation(cursor); CXFile file; clang_getFileLocation(location, &file, nullptr, nullptr, nullptr); // don't skip MemberRefExpr with invalid location, see also: // http://lists.cs.uiuc.edu/pipermail/cfe-dev/2015-May/043114.html if (!ClangUtils::isFileEqual(file, visitor->m_file) && (file || kind != CXCursor_MemberRefExpr)) { return CXChildVisit_Continue; } #define UseCursorKind(CursorKind, ...) case CursorKind: return visitor->dispatchCursor(__VA_ARGS__); switch (kind) { UseCursorKind(CXCursor_UnexposedDecl, cursor, parent); UseCursorKind(CXCursor_StructDecl, cursor, parent); UseCursorKind(CXCursor_UnionDecl, cursor, parent); UseCursorKind(CXCursor_ClassDecl, cursor, parent); UseCursorKind(CXCursor_EnumDecl, cursor, parent); UseCursorKind(CXCursor_FieldDecl, cursor, parent); UseCursorKind(CXCursor_EnumConstantDecl, cursor, parent); UseCursorKind(CXCursor_FunctionDecl, cursor, parent); UseCursorKind(CXCursor_VarDecl, cursor, parent); UseCursorKind(CXCursor_TypeAliasDecl, cursor, parent); UseCursorKind(CXCursor_TypedefDecl, cursor, parent); UseCursorKind(CXCursor_CXXMethod, cursor, parent); UseCursorKind(CXCursor_Namespace, cursor, parent); UseCursorKind(CXCursor_NamespaceAlias, cursor, parent); UseCursorKind(CXCursor_Constructor, cursor, parent); UseCursorKind(CXCursor_Destructor, cursor, parent); UseCursorKind(CXCursor_ConversionFunction, cursor, parent); UseCursorKind(CXCursor_TemplateTypeParameter, cursor, parent); UseCursorKind(CXCursor_NonTypeTemplateParameter, cursor, parent); UseCursorKind(CXCursor_TemplateTemplateParameter, cursor, parent); UseCursorKind(CXCursor_FunctionTemplate, cursor, parent); UseCursorKind(CXCursor_ClassTemplate, cursor, parent); UseCursorKind(CXCursor_ClassTemplatePartialSpecialization, cursor, parent); UseCursorKind(CXCursor_ObjCInterfaceDecl, cursor, parent); UseCursorKind(CXCursor_ObjCCategoryDecl, cursor, parent); UseCursorKind(CXCursor_ObjCProtocolDecl, cursor, parent); UseCursorKind(CXCursor_ObjCPropertyDecl, cursor, parent); UseCursorKind(CXCursor_ObjCIvarDecl, cursor, parent); UseCursorKind(CXCursor_ObjCInstanceMethodDecl, cursor, parent); UseCursorKind(CXCursor_ObjCClassMethodDecl, cursor, parent); UseCursorKind(CXCursor_ObjCImplementationDecl, cursor, parent); UseCursorKind(CXCursor_ObjCCategoryImplDecl, cursor, parent); UseCursorKind(CXCursor_MacroDefinition, cursor, parent); UseCursorKind(CXCursor_LabelStmt, cursor, parent); case CXCursor_TypeRef: case CXCursor_TemplateRef: case CXCursor_NamespaceRef: case CXCursor_MemberRef: case CXCursor_LabelRef: case CXCursor_OverloadedDeclRef: case CXCursor_VariableRef: case CXCursor_DeclRefExpr: case CXCursor_MemberRefExpr: case CXCursor_ObjCClassRef: return visitor->buildUse(cursor); case CXCursor_MacroExpansion: return visitor->buildMacroExpansion(cursor); case CXCursor_CompoundStmt: - return visitor->buildCompoundStatement(cursor); + return visitor->buildCompoundStatement(cursor); + case CXCursor_LambdaExpr: + return visitor->buildCompoundStatement(cursor); case CXCursor_CXXBaseSpecifier: return visitor->buildCXXBaseSpecifier(cursor); case CXCursor_ParmDecl: return visitor->buildParmDecl(cursor); // TODO: fix upstream and then just adapt this to UseCursorKind() case CXCursor_TypeAliasTemplateDecl: return visitor->dispatchTypeAliasTemplate(cursor, parent); default: return CXChildVisit_Recurse; } } } namespace Builder { void visit(CXTranslationUnit tu, CXFile file, const IncludeFileContexts& includes, const bool update) { Visitor visitor(tu, file, includes, update); } } diff --git a/plugins/clang/tests/test_duchain.cpp b/plugins/clang/tests/test_duchain.cpp index f302a29070..efb8a6e238 100644 --- a/plugins/clang/tests/test_duchain.cpp +++ b/plugins/clang/tests/test_duchain.cpp @@ -1,2071 +1,2092 @@ /* * 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 #include "duchain/clangparsingenvironmentfile.h" #include "duchain/clangparsingenvironment.h" #include "duchain/parsesession.h" #include #include #include #include #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; } Path::List frameworkDirectoriesInBackground(const QString&) const override { return {}; } 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_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, QStringLiteral("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(QStringLiteral("int foo() { return 42; }\n"), QStringLiteral("h")); // NOTE: header is _not_ explicitly being parsed, instead the impl job does that TestFile impl("#include \"" + header.url().str() + "\"\n" "int main() { return foo(); }", QStringLiteral("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, QStringLiteral("h")); TestFile impl("#include \"" + header.url().str() + "\"\n", QStringLiteral("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), QStringLiteral("h")); TestFile header2(createCode("Header2", 1000), QStringLiteral("h")); TestFile header3(createCode("Header3", 1000), QStringLiteral("h")); ICore::self()->languageController()->backgroundParser()->setThreadCount(3); TestFile impl1("#include \"" + header1.url().str() + "\"\n" "#include \"" + header2.url().str() + "\"\n" "#include \"" + header3.url().str() + "\"\n" "int main() { return 0; }", QStringLiteral("cpp")); TestFile impl2("#include \"" + header2.url().str() + "\"\n" "#include \"" + header1.url().str() + "\"\n" "#include \"" + header3.url().str() + "\"\n" "int main() { return 0; }", QStringLiteral("cpp")); TestFile impl3("#include \"" + header3.url().str() + "\"\n" "#include \"" + header1.url().str() + "\"\n" "#include \"" + header2.url().str() + "\"\n" "int main() { return 0; }", QStringLiteral("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(QStringLiteral("int main() { int i = 42; return i; }"), QStringLiteral("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(QStringLiteral("int main()\n{\nfloat i = 13; return i - 5;\n}\n")); } file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); } } void TestDUChain::testReparseError() { TestFile file(QStringLiteral("int i = 1 / 0;\n"), QStringLiteral("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(QStringLiteral("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", QStringLiteral("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< T >")).size(), 1); QCOMPARE(file.topContext()->findDeclarations(QualifiedIdentifier("foo< T >::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", QStringLiteral("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(QStringLiteral(R"( const volatile auto foo = 5; template struct myTemplate {}; myTemplate& > templRefParam; auto autoTemplRefParam = templRefParam; )"), QStringLiteral("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(QStringLiteral("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(QStringLiteral("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(QStringLiteral("template struct foo { T member; } foo f; auto i = f.member;"), QStringLiteral("cpp")); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; DUContext* ctx = file.topContext().data(); QVERIFY(ctx); QCOMPARE(ctx->localDeclarations().size(), 3); Declaration* decl = nullptr; // check 'foo' declaration decl = ctx->localDeclarations()[0]; QVERIFY(decl); QCOMPARE(decl->identifier(), Identifier("foo< T >")); // 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(QStringLiteral("struct S {}; struct A { virtual S* ret(); }; struct B : public A { virtual S* ret(); };"), QStringLiteral("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(QStringLiteral("class Base {}; class Inherited : public Base {};"), QStringLiteral("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(QStringLiteral("struct a{}; struct b : a {};\n"), QStringLiteral("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(QStringLiteral("template struct a{}; struct b : a {};\n"), QStringLiteral("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, QStringLiteral("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(QStringLiteral("void foo(int arg1, char arg2);\n"), QStringLiteral("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(QStringLiteral("void func(); void func() {}\n"), QStringLiteral("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(QStringLiteral("struct SomeStruct {} s;\n"), QStringLiteral("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(QStringLiteral("SomeStruct"))); QCOMPARE(candidates.size(), 1); } void TestDUChain::testParsingEnvironment() { const TopDUContext::Features features = TopDUContext::AllDeclarationsContextsAndUses; IndexedTopDUContext indexed; ClangParsingEnvironment lastEnv; { TestFile file(QStringLiteral("int main() {}\n"), QStringLiteral("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(QStringLiteral("/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(QStringLiteral("/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(QStringLiteral("int main() {}\n"), QStringLiteral("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(QStringLiteral("int main() {}\n"), QStringLiteral("cpp")); TestFile file2(QStringLiteral("int main() {}\n"), QStringLiteral("cpp")); TestFile file3(QStringLiteral("int main() {}\n"), QStringLiteral("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", QStringLiteral("cpp")); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(60000)); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); } } void TestDUChain::testReparseWithAllDeclarationsContextsAndUses() { TestFile file(QStringLiteral("int foo() { return 0; } int main() { return foo(); }"), QStringLiteral("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(QStringLiteral("int foo() { return 0; } int main() { return foo(); }"), QStringLiteral("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(QStringLiteral("int foo() { return 42; }\n"), QStringLiteral("h")); TestFile impl("#include \"" + header.url().str() + "\"\n" "int main() { return foo(); }", QStringLiteral("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(QStringLiteral("int foo() { return 42; }\n"), QStringLiteral("h")); TestFile impl("#include \"" + header.url().str() + "\"\n" "int main() { return foo(); }", QStringLiteral("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(QStringLiteral("foooooooo"), QStringLiteral("baaar!")); } else if (i == 1) { // 2) change includes m_provider->includes.append(Path(QStringLiteral("/foo/bar/asdf/lalala"))); } // 3) stop } } void TestDUChain::testMacroDependentHeader() { TestFile header(QStringLiteral("struct MY_CLASS { class Q{Q(); int m;}; int m; };\n"), QStringLiteral("h")); TestFile impl("#define MY_CLASS A\n" "#include \"" + header.url().str() + "\"\n" "#undef MY_CLASS\n" "#define MY_CLASS B\n" "#include \"" + header.url().str() + "\"\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" , QStringLiteral("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(); qDebug() << "CTX RANGE:" << top2->localDeclarations()[0]->internalContext()->range().castToSimpleRange(); // 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(QStringLiteral("typedef const A B;\n"), QStringLiteral("h")); TestFile impl("template class A{};\n" "#include \"" + header.url().str() + "\"\n" "B c;", QStringLiteral("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(QStringLiteral("template class A{};\n"), QStringLiteral("h")); TestFile header2(QStringLiteral("typedef const A B;\n"), QStringLiteral("h")); TestFile impl("#include \"" + header.url().str() + "\"\n" "#include \"" + header2.url().str() + "\"\n" "B c;", QStringLiteral("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(QStringLiteral("#define FUNC_MACROS(x) struct str##x{};\nFUNC_MACROS(x);"), QStringLiteral("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::testMacroUses() { TestFile file(QStringLiteral("#define USER(x) x\n#define USED\nUSER(USED)"), QStringLiteral("cpp")); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto macroDefinition1 = file.topContext()->localDeclarations()[0]; auto macroDefinition2 = file.topContext()->localDeclarations()[1]; QCOMPARE(macroDefinition1->uses().size(), 1); QCOMPARE(macroDefinition1->uses().begin()->first(), RangeInRevision(2,0,2,4)); #if CINDEX_VERSION_MINOR < 32 QEXPECT_FAIL("", "This appears to be a clang bug, the AST doesn't contain the macro use", Continue); #endif QCOMPARE(macroDefinition2->uses().size(), 1); if (macroDefinition2->uses().size()) { QCOMPARE(macroDefinition2->uses().begin()->first(), RangeInRevision(2,5,2,9)); } } void TestDUChain::testMultiLineMacroRanges() { TestFile file(QStringLiteral("#define FUNC_MACROS(x) struct str##x{};\nFUNC_MACROS(x\n);"), QStringLiteral("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(QStringLiteral("#define INNER int var; var = 0;\n#define MACRO() INNER\nint main(){MACRO(\n);}"), QStringLiteral("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(QStringLiteral("#pragma once\nint B();\n"), QStringLiteral("h")); TestFile C("#pragma once\n#include \"" + B.url().str() + "\"\nint C();\n", QStringLiteral("h")); TestFile A("#include \"" + B.url().str() + "\"\n" + "#include \"" + C.url().str() + "\"\nint A();\n", QStringLiteral("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(QStringLiteral("int main();\n"), QStringLiteral("cpp")); m_provider->includes.clear(); m_provider->includes.append(Path(QStringLiteral("/path1"))); m_provider->includes.append(Path(QStringLiteral("/path2"))); m_provider->defines.clear(); m_provider->defines.insert(QStringLiteral("key1"), QStringLiteral("value1")); m_provider->defines.insert(QStringLiteral("key2"), QStringLiteral("value2")); m_provider->defines.insert(QStringLiteral("key3"), QStringLiteral("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(QStringLiteral("key3"), QStringLiteral("value3")); m_provider->defines.insert(QStringLiteral("key1"), QStringLiteral("value1")); m_provider->defines.insert(QStringLiteral("key2"), QStringLiteral("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(QStringLiteral("key2"), QStringLiteral("value2")); m_provider->defines.insert(QStringLiteral("key3"), QStringLiteral("value3")); m_provider->defines.insert(QStringLiteral("key3"), QStringLiteral("value3")); m_provider->defines.insert(QStringLiteral("key1"), QStringLiteral("value1")); } else if (i == 2) { //OTOH order of includes should change hash of the environment. m_provider->includes.clear(); m_provider->includes.append(Path(QStringLiteral("/path2"))); m_provider->includes.append(Path(QStringLiteral("/path1"))); } } } void TestDUChain::testReparseMacro() { TestFile file(QStringLiteral("#define DECLARE(a) typedef struct a##_ {} *a;\nDECLARE(D);\nD d;"), QStringLiteral("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(QStringLiteral("int main() {\ngoto label;\ngoto label;\nlabel: return 0;}"), QStringLiteral("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(QStringLiteral("class Test{public: Test& operator++(int);};\n#define MACRO(var) var++;\nint main(){\nTest tst; MACRO(tst)}"), QStringLiteral("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, QStringLiteral("cpp")); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed()); DUChainReadLocker lock; QVERIFY(file.topContext()); auto functionTemplate = file.topContext()->findDeclarations(QualifiedIdentifier(QStringLiteral("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(QStringLiteral("function"))); QVERIFY(!function.isEmpty()); auto functionDeclaration = DUChainUtils::declarationForDefinition(function.first()); QVERIFY(!functionDeclaration->isDefinition()); QCOMPARE(functionDeclaration->uses().count(), 1); } void TestDUChain::testReparseIncludeGuard() { TestFile header(QStringLiteral("#ifndef GUARD\n#define GUARD\nint something;\n#endif\n"), QStringLiteral("h")); TestFile impl("#include \"" + header.url().str() + "\"\n", QStringLiteral("cpp"), &header); QVERIFY(impl.parseAndWait(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::AST ))); { DUChainReadLocker lock; QCOMPARE(static_cast(impl.topContext()-> importedParentContexts().first().context(impl.topContext()))->problems().size(), 0); } QVERIFY(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, QStringLiteral("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, QStringLiteral("h")); TestFile impl(implCode.arg(header.url().str()), QStringLiteral("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(QStringLiteral("template using Alias = T; using Foo = Alias;"), QStringLiteral("cpp")); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto templateAlias = file.topContext()->localDeclarations().first(); QVERIFY(templateAlias); #if CINDEX_VERSION_MINOR < 31 QEXPECT_FAIL("", "TypeAliasTemplate is not exposed via LibClang", Abort); #endif QVERIFY(templateAlias->isTypeAlias()); QVERIFY(templateAlias->abstractType()); QCOMPARE(templateAlias->abstractType()->toString(), QStringLiteral("Alias")); QCOMPARE(templateAlias->uses().size(), 1); QCOMPARE(templateAlias->uses().first().size(), 1); QCOMPARE(templateAlias->uses().first().first(), RangeInRevision(0, 51, 0, 56)); } void TestDUChain::testDeclarationsInsideMacroExpansion() { TestFile header(QStringLiteral("#define DECLARE(a) typedef struct a##__ {int var;} *a\nDECLARE(D);\n"), QStringLiteral("h")); TestFile file("#include \"" + header.url().str() + "\"\nint main(){\nD d; d->var;}\n", QStringLiteral("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); 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})); } // see also: https://bugs.kde.org/show_bug.cgi?id=368067 void TestDUChain::testForwardTemplateTypeParameterContext() { TestFile file(QStringLiteral(R"( template class Foo; class MatchingName { void bar(); }; void MatchingName::bar() { } )"), QStringLiteral("cpp")); file.parse(); QVERIFY(file.waitForParsed(500)); DUChainReadLocker lock; const auto top = file.topContext(); QVERIFY(top); DUChainDumper dumper(DUChainDumper::Features(DUChainDumper::DumpContext | DUChainDumper::DumpProblems)); dumper.dump(top); auto declarations = top->localDeclarations(); QCOMPARE(declarations.size(), 2); } // see also: https://bugs.kde.org/show_bug.cgi?id=368460 void TestDUChain::testTemplateFunctionParameterName() { TestFile file(QStringLiteral(R"( template void foo(int name); void bar(int name); )"), QStringLiteral("cpp")); file.parse(); QVERIFY(file.waitForParsed(500)); DUChainReadLocker lock; const auto top = file.topContext(); QVERIFY(top); DUChainDumper dumper(DUChainDumper::Features(DUChainDumper::DumpContext | DUChainDumper::DumpProblems)); dumper.dump(top); auto declarations = top->localDeclarations(); QCOMPARE(declarations.size(), 2); for (auto decl : declarations) { auto ctx = DUChainUtils::getArgumentContext(decl); QVERIFY(ctx); auto args = ctx->localDeclarations(); if (decl == declarations.first()) QEXPECT_FAIL("", "We get two declarations, for both template and args :(", Continue); QCOMPARE(args.size(), 1); if (decl == declarations.first()) QEXPECT_FAIL("", "see above, this then triggers T T here", Continue); QCOMPARE(args.first()->toString(), QStringLiteral("int name")); } } 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(QLatin1String("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(QLatin1String("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::testFriendDeclaration() { TestFile file(QStringLiteral(R"( struct FriendFoo { friend class FriendBar; }; class FriendBar{}; FriendBar friendBar; )"), QStringLiteral("cpp")); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(1000)); { DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 3); auto friendBar = file.topContext()->localDeclarations()[1]; if (CINDEX_VERSION_MINOR < 37) { QEXPECT_FAIL("", "Your clang version is too old", Abort); } QCOMPARE(friendBar->uses().size(), 1); QCOMPARE(friendBar->uses().begin()->first(), RangeInRevision(3,25,3,34)); QCOMPARE(friendBar->uses().begin()->last(), RangeInRevision(8,8,8,17)); } } void TestDUChain::testVariadicTemplateArguments() { TestFile file(QStringLiteral(R"( template class VariadicTemplate {}; VariadicTemplate variadic; )"), QStringLiteral("cpp")); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(1000)); { DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto decl = file.topContext()->localDeclarations()[1]; QVERIFY(decl); if (CINDEX_VERSION_MINOR < 37) { QEXPECT_FAIL("", "Your clang version is too old", Abort); } QCOMPARE(decl->toString(), QStringLiteral("VariadicTemplate< int, double, bool > variadic")); QVERIFY(decl->abstractType()); QCOMPARE(decl->abstractType()->toString(), QStringLiteral("VariadicTemplate< int, double, bool >")); } } 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(QStringLiteral(R"( #include int main() { return 0; } )"), QStringLiteral("cpp"), project, dir.path()); file.parse(); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QSet checked; verifyNoErrors(file.topContext(), checked); } m_projectController->closeAllProjects(); } +void TestDUChain::testLambda() +{ + TestFile file(QStringLiteral("auto lambda = [](int p1, int p2, int p3) { int var1, var2; };"), QStringLiteral("cpp")); + QVERIFY(file.parseAndWait()); + { + DUChainReadLocker lock; + QVERIFY(file.topContext()); + QCOMPARE(file.topContext()->localDeclarations().size(), 1); + QCOMPARE(file.topContext()->childContexts().size(), 1); + + auto lambdaContext = file.topContext()->childContexts().first(); + + QCOMPARE(lambdaContext->type(), DUContext::Function); + + QCOMPARE(lambdaContext->localDeclarations().size(), 3); + QCOMPARE(lambdaContext->childContexts().size(), 1); + QCOMPARE(lambdaContext->childContexts().first()->type(), DUContext::Other); + QCOMPARE(lambdaContext->childContexts().first()->localDeclarations().size(), 2); + } +} + void TestDUChain::testQtIntegration() { QTemporaryDir includeDir; { QDir dir(includeDir.path()); dir.mkdir(QStringLiteral("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(QStringLiteral(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(); }; )"), QStringLiteral("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(); } void TestDUChain::testHasInclude() { TestFile header(QStringLiteral(R"( #pragma once #if __has_include_next() // good #else #error broken c++11 setup (__has_include_next) #endif )"), QStringLiteral("h")); // NOTE: header is _not_ explicitly being parsed, instead the impl job does that TestFile file(QStringLiteral(R"( #if __has_include() // good #else #error broken c++11 setup (__has_include) #endif #include "%1" )").arg(header.url().str()), QStringLiteral("cpp")); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(1000)); { DUChainDumper dumper{DUChainDumper::DumpProblems}; DUChainReadLocker lock; QVERIFY(file.topContext()); dumper.dump(file.topContext()); QVERIFY(file.topContext()->problems().isEmpty()); auto headerCtx = DUChain::self()->chainForDocument(header.url()); QVERIFY(headerCtx); dumper.dump(headerCtx); QVERIFY(headerCtx->problems().count() <= 1); for (const auto& problem : headerCtx->problems()) { // ignore the following error: "#include_next with absolute path [-Winclude-next-absolute-path]" "" [ (2, 12) -> (2, 30) ] QVERIFY(problem->description().contains(QLatin1String("-Winclude-next-absolute-path"))); } } } diff --git a/plugins/clang/tests/test_duchain.h b/plugins/clang/tests/test_duchain.h index 805ed5f9fa..306424759b 100644 --- a/plugins/clang/tests/test_duchain.h +++ b/plugins/clang/tests/test_duchain.h @@ -1,110 +1,111 @@ /* * 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 Q_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 testMacroUses(); 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 testLambda(); void testReparseUnchanged_data(); void testReparseUnchanged(); void testTypeAliasTemplate(); void testDeclarationsInsideMacroExpansion(); void testForwardTemplateTypeParameterContext(); void testTemplateFunctionParameterName(); void testFriendDeclaration(); void testVariadicTemplateArguments(); void benchDUChainBuilder(); void testGccCompatibility(); void testQtIntegration(); void testHasInclude(); private: QScopedPointer m_provider; KDevelop::TestProjectController* m_projectController; }; #endif // DUCHAINTEST_H