diff --git a/plugins/clang/duchain/builder.cpp b/plugins/clang/duchain/builder.cpp index eeb0d9239a..aa7df1e11f 100644 --- a/plugins/clang/duchain/builder.cpp +++ b/plugins/clang/duchain/builder.cpp @@ -1,1605 +1,1605 @@ /* * 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) using namespace KDevelop; namespace { #if CINDEX_VERSION_MINOR >= 100 // TODO: this is ugly, can we find a better alternative? bool jsonTestRun() { static bool runningTest = qEnvironmentVariableIsSet("KDEV_CLANG_JSON_TEST_RUN"); return runningTest; } #endif //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()); } auto name = ClangString(clang_getCursorSpelling(cursor)).toIndexed(); if (name.isEmpty() && CursorKindTraits::isClass(cursor.kind)) { // try to use the type name for typedef'ed anon structs etc. as a fallback auto type = ClangString(clang_getTypeSpelling(clang_getCursorType(cursor))).toString(); // but don't associate a super long name for anon structs without a typedef if (!type.startsWith(QLatin1String("(anonymous "))) { name = IndexedString(type); } } return Identifier(name); } #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> { using Type = StructureType; }; template struct IdType::type> { using Type = TypeAliasType; }; template struct IdType::type> { using Type = TypeAliasType; }; template struct IdType::type> { using Type = EnumerationType; }; template struct IdType::type> { using Type = EnumeratorType; }; //END IdType //BEGIN DeclType template struct DeclType; template struct DeclType::type> { using Type = Declaration; }; template struct DeclType::type> { using Type = MacroDefinition; }; template struct DeclType::type> { using Type = ForwardDeclaration; }; template struct DeclType::type> { using Type = ClassDeclaration; }; template struct DeclType::type> { using Type = ClassFunctionDeclaration; }; template struct DeclType::type> { using Type = FunctionDeclaration; }; template struct DeclType::type> { using Type = FunctionDefinition; }; template struct DeclType::type> { using Type = NamespaceAliasDeclaration; }; template struct DeclType::type> { using Type = ClassMemberDeclaration; }; //END DeclType //BEGIN CurrentContext struct CurrentContext { CurrentContext(DUContext* context, const QSet& keepAliveContexts) : context(context) , keepAliveContexts(keepAliveContexts) { DUChainReadLocker lock; previousChildContexts = context->childContexts(); previousChildDeclarations = context->localDeclarations(); } ~CurrentContext() { DUChainWriteLocker lock; for (auto* childContext : qAsConst(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); #if CINDEX_VERSION_MINOR >= 32 decl->setExplicitlyTyped(clang_getCursorType(cursor).kind != CXType_Auto); #endif 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 https://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(ClangString(clang_Cursor_getRawCommentText(cursor)).toByteArray())); #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. https://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 QString contents = 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 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 alignOf = clang_Type_getAlignOf(type); if (sizeOf >= 0) decl->setSizeOf(sizeOf); if (offset >= 0) decl->setBitOffsetOf(offset); if (alignOf >= 0) decl->setAlignOf(alignOf); } #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 CINDEX_VERSION_MINOR >= 30 auto type = clang_getCursorType(cursor); auto sizeOf = clang_Type_getSizeOf(type); auto alignOf = clang_Type_getAlignOf(type); if (sizeOf >= 0) decl->setSizeOf(sizeOf); if (alignOf >= 0) decl->setAlignOf(alignOf); #endif } 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); for (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 (CK == CXCursor_LambdaExpr || m_parentContext->context->type() == DUContext::Function) { 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('('); + const int firstOpeningParen = contents.indexOf(QChar::fromLatin1('(')); 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; const auto problems = top->problems(); for (const auto& problem : 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) { auto *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); 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/duchain/clangproblem.cpp b/plugins/clang/duchain/clangproblem.cpp index 26bcb12498..954e2a9d55 100644 --- a/plugins/clang/duchain/clangproblem.cpp +++ b/plugins/clang/duchain/clangproblem.cpp @@ -1,290 +1,290 @@ /* * Copyright 2014 Kevin Funk * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "clangproblem.h" #include #include #include "util/clangtypes.h" #include "util/clangdebug.h" #include "util/clangutils.h" #include #include #include using namespace KDevelop; namespace { IProblem::Severity diagnosticSeverityToSeverity(CXDiagnosticSeverity severity, const QString& optionName) { switch (severity) { case CXDiagnostic_Fatal: case CXDiagnostic_Error: return IProblem::Error; case CXDiagnostic_Warning: if (optionName.startsWith(QLatin1String("-Wunused-"))) { return IProblem::Hint; } return IProblem::Warning; default: return IProblem::Hint; } } /** * Clang diagnostic messages always start with a lowercase character * * @return Prettified version, starting with uppercase character */ inline QString prettyDiagnosticSpelling(const QString& str) { QString ret = str; if (ret.isEmpty()) { return {}; } ret[0] = ret[0].toUpper(); return ret; } ClangFixits fixitsForDiagnostic(CXDiagnostic diagnostic, CXTranslationUnit unit) { ClangFixits fixits; auto numFixits = clang_getDiagnosticNumFixIts(diagnostic); fixits.reserve(numFixits); for (uint i = 0; i < numFixits; ++i) { CXSourceRange range; const QString replacementText = ClangString(clang_getDiagnosticFixIt(diagnostic, i, &range)).toString(); - QByteArray original = ClangUtils::getRawContents(unit, range); - fixits << ClangFixit{replacementText, ClangRange(range).toDocumentRange(), QString(), QString::fromLocal8Bit(original)}; + const auto original = ClangUtils::getRawContents(unit, range); + fixits << ClangFixit{replacementText, ClangRange(range).toDocumentRange(), QString(), original}; } return fixits; } } QDebug operator<<(QDebug debug, const ClangFixit& fixit) { debug.nospace() << "ClangFixit[" << "replacementText=" << fixit.replacementText << ", range=" << fixit.range << ", description=" << fixit.description << "]"; return debug; } ClangProblem::ClangProblem() = default; ClangProblem::ClangProblem(const ClangProblem& other) : Problem(), m_fixits(other.m_fixits) { setSource(other.source()); setFinalLocation(other.finalLocation()); setFinalLocationMode(other.finalLocationMode()); setDescription(other.description()); setExplanation(other.explanation()); setSeverity(other.severity()); auto diagnostics = other.diagnostics(); for (auto& diagnostic : diagnostics) { auto* clangDiagnostic = dynamic_cast(diagnostic.data()); Q_ASSERT(clangDiagnostic); diagnostic = ClangProblem::Ptr(new ClangProblem(*clangDiagnostic)); } setDiagnostics(diagnostics); } ClangProblem::ClangProblem(CXDiagnostic diagnostic, CXTranslationUnit unit) { const QString diagnosticOption = ClangString(clang_getDiagnosticOption(diagnostic, nullptr)).toString(); auto severity = diagnosticSeverityToSeverity(clang_getDiagnosticSeverity(diagnostic), diagnosticOption); setSeverity(severity); QString description = ClangString(clang_getDiagnosticSpelling(diagnostic)).toString(); if (!diagnosticOption.isEmpty()) { description.append(QLatin1String(" [") + diagnosticOption + QLatin1Char(']')); } setDescription(prettyDiagnosticSpelling(description)); ClangLocation location(clang_getDiagnosticLocation(diagnostic)); CXFile diagnosticFile; clang_getFileLocation(location, &diagnosticFile, nullptr, nullptr, nullptr); const ClangString fileName(clang_getFileName(diagnosticFile)); DocumentRange docRange(IndexedString(QUrl::fromLocalFile(fileName.toString()).adjusted(QUrl::NormalizePathSegments)), KTextEditor::Range(location, location)); const uint numRanges = clang_getDiagnosticNumRanges(diagnostic); for (uint i = 0; i < numRanges; ++i) { auto range = ClangRange(clang_getDiagnosticRange(diagnostic, i)).toRange(); // Note that the second condition is a workaround for seemingly wrong ranges that // were observed sometimes. In principle, such a range should be valid. if(!range.isValid() || (range.isEmpty() && range.start().line() == 0 && range.start().column() == 0)){ continue; } if (range.start() < docRange.start()) { docRange.setStart(range.start()); } if (range.end() > docRange.end()) { docRange.setEnd(range.end()); } } if (docRange.isEmpty()) { // try to find a bigger range for the given location by using the token at the given location CXFile file = nullptr; unsigned line = 0; unsigned column = 0; clang_getExpansionLocation(location, &file, &line, &column, nullptr); // just skip ahead some characters, hoping that it's sufficient to encompass // a token we can use for building the range auto nextLocation = clang_getLocation(unit, file, line, column + 100); auto rangeToTokenize = clang_getRange(location, nextLocation); const ClangTokens tokens(unit, rangeToTokenize); if (tokens.size()) { docRange.setRange(ClangRange(clang_getTokenExtent(unit, tokens.at(0))).toRange()); } } setFixits(fixitsForDiagnostic(diagnostic, unit)); setFinalLocation(docRange); setSource(IProblem::SemanticAnalysis); QVector diagnostics; auto childDiagnostics = clang_getChildDiagnostics(diagnostic); auto numChildDiagnostics = clang_getNumDiagnosticsInSet(childDiagnostics); diagnostics.reserve(numChildDiagnostics); for (uint j = 0; j < numChildDiagnostics; ++j) { auto childDiagnostic = clang_getDiagnosticInSet(childDiagnostics, j); ClangProblem::Ptr problem(new ClangProblem(childDiagnostic, unit)); diagnostics << ProblemPointer(problem.data()); } setDiagnostics(diagnostics); } IAssistant::Ptr ClangProblem::solutionAssistant() const { if (allFixits().isEmpty()) { return {}; } return IAssistant::Ptr(new ClangFixitAssistant(allFixits())); } ClangFixits ClangProblem::fixits() const { return m_fixits; } void ClangProblem::setFixits(const ClangFixits& fixits) { m_fixits = fixits; } ClangFixits ClangProblem::allFixits() const { ClangFixits result; result << m_fixits; const auto& diagnostics = this->diagnostics(); for (const IProblem::Ptr& diagnostic : diagnostics) { const Ptr problem(dynamic_cast(diagnostic.data())); Q_ASSERT(problem); result << problem->allFixits(); } return result; } ClangFixitAssistant::ClangFixitAssistant(const ClangFixits& fixits) : m_title(i18n("Fix-it Hints")) , m_fixits(fixits) { } ClangFixitAssistant::ClangFixitAssistant(const QString& title, const ClangFixits& fixits) : m_title(title) , m_fixits(fixits) { } QString ClangFixitAssistant::title() const { return m_title; } void ClangFixitAssistant::createActions() { KDevelop::IAssistant::createActions(); for (const ClangFixit& fixit : qAsConst(m_fixits)) { addAction(IAssistantAction::Ptr(new ClangFixitAction(fixit))); } } ClangFixits ClangFixitAssistant::fixits() const { return m_fixits; } ClangFixitAction::ClangFixitAction(const ClangFixit& fixit) : m_fixit(fixit) { } QString ClangFixitAction::description() const { if (!m_fixit.description.isEmpty()) return m_fixit.description; const auto range = m_fixit.range; if (range.start() == range.end()) { return i18n("Insert \"%1\" at line: %2, column: %3", m_fixit.replacementText, range.start().line()+1, range.start().column()+1); } else if (range.start().line() == range.end().line()) { if (m_fixit.currentText.isEmpty()) { return i18n("Replace text at line: %1, column: %2 with: \"%3\"", range.start().line()+1, range.start().column()+1, m_fixit.replacementText); } else return i18n("Replace \"%1\" with: \"%2\"", m_fixit.currentText, m_fixit.replacementText); } else { return i18n("Replace multiple lines starting at line: %1, column: %2 with: \"%3\"", range.start().line()+1, range.start().column()+1, m_fixit.replacementText); } } void ClangFixitAction::execute() { DocumentChangeSet changes; { DUChainReadLocker lock; DocumentChange change(m_fixit.range.document, m_fixit.range, m_fixit.currentText, m_fixit.replacementText); change.m_ignoreOldText = !m_fixit.currentText.isEmpty(); changes.addChange(change); } changes.setReplacementPolicy(DocumentChangeSet::WarnOnFailedChange); changes.applyAllChanges(); emit executed(this); } diff --git a/plugins/clang/tests/test_clangutils.cpp b/plugins/clang/tests/test_clangutils.cpp index cb72136195..1547fc6336 100644 --- a/plugins/clang/tests/test_clangutils.cpp +++ b/plugins/clang/tests/test_clangutils.cpp @@ -1,279 +1,279 @@ /* * Copyright 2014 Kevin Funk * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "test_clangutils.h" #include "../util/clangutils.h" #include "../util/clangtypes.h" #include "../util/clangdebug.h" #include #include #include #include #include #include #include #include #include QTEST_MAIN(TestClangUtils) using namespace KDevelop; namespace { struct CursorCollectorVisitor { QVector cursors; static CXChildVisitResult visit(CXCursor cursor, CXCursor /*parent*/, CXClientData d) { auto* thisPtr = static_cast(d); thisPtr->cursors << cursor; return CXChildVisit_Recurse; }; }; CXSourceRange toCXRange(CXTranslationUnit unit, const DocumentRange& range) { auto file = clang_getFile(unit, QString::fromUtf8(range.document.c_str(), range.document.length()).toUtf8()); auto begin = clang_getLocation(unit, file, range.start().line()+1, range.start().column()+1); auto end = clang_getLocation(unit, file, range.end().line()+1, range.end().column()+1); return clang_getRange(begin, end); } void parse(const QByteArray& code, CXTranslationUnit* unit, QString* fileName = nullptr) { Q_ASSERT(unit); QTemporaryFile tempFile; QVERIFY(tempFile.open()); tempFile.write(code); tempFile.flush(); if (fileName) { *fileName = tempFile.fileName(); } std::unique_ptr index(clang_createIndex(1, 1), clang_disposeIndex); const QVector args = {"-std=c++11", "-xc++", "-Wall", "-nostdinc", "-nostdinc++"}; *unit = clang_parseTranslationUnit( index.get(), qPrintable(tempFile.fileName()), args.data(), args.size(), nullptr, 0, CXTranslationUnit_None ); QVERIFY(*unit); } template void runVisitor(const QByteArray& code, Visitor& visitor) { runVisitor(code, &Visitor::visit, &visitor); } void runVisitor(const QByteArray& code, CXCursorVisitor visitor, CXClientData data) { CXTranslationUnit unit; parse(code, &unit); const auto startCursor = clang_getTranslationUnitCursor(unit); QVERIFY(!clang_Cursor_isNull(startCursor)); QVERIFY(clang_visitChildren(startCursor, visitor, data) == 0); } } void TestClangUtils::initTestCase() { QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\nkdevelop.plugins.clang.debug=true\n")); AutoTestShell::init(); TestCore::initialize(Core::NoUi); } void TestClangUtils::cleanupTestCase() { TestCore::shutdown(); } void TestClangUtils::testGetScope() { QFETCH(QByteArray, code); QFETCH(int, cursorIndex); QFETCH(QString, expectedScope); CursorCollectorVisitor visitor; runVisitor(code, visitor); QVERIFY(cursorIndex < visitor.cursors.size()); const auto cursor = visitor.cursors[cursorIndex]; clangDebug() << "Found decl:" << clang_getCursorSpelling(cursor) << "| range:" << ClangRange(clang_getCursorExtent(cursor)).toRange(); const QString scope = ClangUtils::getScope(cursor); QCOMPARE(scope, expectedScope); QVERIFY(!visitor.cursors.isEmpty()); } void TestClangUtils::testGetScope_data() { QTest::addColumn("code"); QTest::addColumn("cursorIndex"); QTest::addColumn("expectedScope"); QTest::newRow("func-decl-inside-ns") << QByteArray("namespace ns1 { void foo(); } ns1::foo() {}") << 2 << "ns1"; QTest::newRow("fwd-decl-inside-ns") << QByteArray("namespace ns1 { struct foo; } struct ns1::foo {};") << 2 << "ns1"; QTest::newRow("fwd-decl-inside-ns-inside-ns") << QByteArray("namespace ns1 { namespace ns2 { struct foo; } } struct ns1::ns2::foo {};") << 3 << "ns1::ns2"; QTest::newRow("fwd-decl-inside-ns-inside-ns") << QByteArray("namespace ns1 { namespace ns2 { struct foo; } } struct ns1::ns2::foo {};") << 3 << "ns1::ns2"; QTest::newRow("using-namespace") << QByteArray("namespace ns1 { struct klass { struct foo; }; } using namespace ns1; struct klass::foo {};") << 5 << "ns1::klass"; QTest::newRow("fwd-decl-def-inside-namespace") << QByteArray("namespace ns1 { struct klass { struct foo; }; } namespace ns1 { struct klass::foo {}; }") << 4 << "klass"; } void TestClangUtils::testTemplateArgumentTypes() { QFETCH(QByteArray, code); QFETCH(int, cursorIndex); QFETCH(QStringList, expectedTypes); CursorCollectorVisitor visitor; runVisitor(code, visitor); QVERIFY(cursorIndex < visitor.cursors.size()); const auto cursor = visitor.cursors[cursorIndex]; const QStringList types = ClangUtils::templateArgumentTypes(cursor); QCOMPARE(types, expectedTypes); } void TestClangUtils::testTemplateArgumentTypes_data() { QTest::addColumn("code"); QTest::addColumn("cursorIndex"); QTest::addColumn("expectedTypes"); QTest::newRow("template-spec") << QByteArray( "template\n" "struct is_void\n" "{ };\n" "template<>\n" "struct is_void\n" "{ };\n") << 2 << QStringList({"void"}); // Partially specialize one QTest::newRow("template-partial-spec") << QByteArray( "template\n" "struct is_void\n" "{ };\n" "template\n" "struct is_void\n" "{ };\n") << 3 << QStringList({"type-parameter-0-0", ""}); // Fully specialize 3 args QTest::newRow("template-full-spec") << QByteArray( "template\n" "struct is_void\n" "{ };\n" "template<>\n" "struct is_void\n" "{ };\n") << 4 << QStringList({"unsigned int", "void", ""}); } void TestClangUtils::testGetRawContents() { QFETCH(QByteArray, code); QFETCH(KTextEditor::Range, range); QFETCH(QString, expectedContents); CXTranslationUnit unit; QString fileName; parse(code, &unit, &fileName); DocumentRange documentRange{IndexedString(fileName), range}; auto cxRange = toCXRange(unit, documentRange); - const QString contents = QString::fromLatin1(ClangUtils::getRawContents(unit, cxRange)); + const QString contents = ClangUtils::getRawContents(unit, cxRange); QCOMPARE(contents, expectedContents); } void TestClangUtils::testGetRawContents_data() { QTest::addColumn("code"); QTest::addColumn("range"); QTest::addColumn("expectedContents"); QTest::newRow("complete") << QByteArray("void; int foo = 42; void;") << KTextEditor::Range(0, 6, 0, 19) << "int foo = 42;"; QTest::newRow("cut-off-at-start") << QByteArray("void; int foo = 42; void;") << KTextEditor::Range(0, 7, 0, 19) << "nt foo = 42;"; QTest::newRow("cut-off-at-end") << QByteArray("void; int foo = 42; void;") << KTextEditor::Range(0, 6, 0, 17) << "int foo = 4"; QTest::newRow("whitespace") << QByteArray("void; int ; void;") << KTextEditor::Range(0, 5, 0, 18) << " int "; QTest::newRow("empty-range") << QByteArray("void;") << KTextEditor::Range(0, 0, 0, 0) << ""; } void TestClangUtils::testRangeForIncludePathSpec() { QCOMPARE(ClangUtils::rangeForIncludePathSpec("#include "), KTextEditor::Range(0, 10, 0, 16)); QCOMPARE(ClangUtils::rangeForIncludePathSpec("# include "), KTextEditor::Range(0, 11, 0, 17)); QCOMPARE(ClangUtils::rangeForIncludePathSpec("#\t include "), KTextEditor::Range(0, 12, 0, 18)); QCOMPARE(ClangUtils::rangeForIncludePathSpec("#include "), KTextEditor::Range(0, 10, 0, 17)); QCOMPARE(ClangUtils::rangeForIncludePathSpec("#include \"foo\\\".h\""), KTextEditor::Range(0, 10, 0, 17)); QCOMPARE(ClangUtils::rangeForIncludePathSpec("#include \"foo<>.h\""), KTextEditor::Range(0, 10, 0, 17)); } diff --git a/plugins/clang/util/clangutils.cpp b/plugins/clang/util/clangutils.cpp index c594e6eb1b..3a65497652 100644 --- a/plugins/clang/util/clangutils.cpp +++ b/plugins/clang/util/clangutils.cpp @@ -1,468 +1,504 @@ /* * Copyright 2014 Kevin Funk * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "clangutils.h" #include "../util/clangdebug.h" #include "../util/clangtypes.h" #include "../duchain/cursorkindtraits.h" #include "../duchain/documentfinderhelpers.h" #include #include #include #include #include #include #include #include using namespace KDevelop; CXCursor ClangUtils::getCXCursor(int line, int column, const CXTranslationUnit& unit, const CXFile& file) { if (!file) { clangDebug() << "getCXCursor couldn't find file: " << clang_getFileName(file); return clang_getNullCursor(); } CXSourceLocation location = clang_getLocation(unit, file, line + 1, column + 1); if (clang_equalLocations(clang_getNullLocation(), location)) { clangDebug() << "getCXCursor given invalid position " << line << ", " << column << " for file " << clang_getFileName(file); return clang_getNullCursor(); } return clang_getCursor(unit, location); } QVector ClangUtils::unsavedFiles() { QVector ret; const auto documents = ICore::self()->documentController()->openDocuments(); for (auto* document : documents) { auto textDocument = document->textDocument(); // TODO: Introduce a cache so we don't have to re-read all the open documents // which were not changed since the last run if (!textDocument || !textDocument->url().isLocalFile() || !DocumentFinderHelpers::mimeTypesList().contains(textDocument->mimeType())) { continue; } if (!textDocument->isModified()) { continue; } ret << UnsavedFile(textDocument->url().toLocalFile(), textDocument->textLines(textDocument->documentRange())); } return ret; } KTextEditor::Range ClangUtils::rangeForIncludePathSpec(const QString& line, const KTextEditor::Range& originalRange) { static const QRegularExpression pattern(QStringLiteral("^\\s*(#\\s*include|#\\s*import)")); if (!line.contains(pattern)) { return KTextEditor::Range::invalid(); } KTextEditor::Range range = originalRange; int pos = 0; char term_char = 0; for (; pos < line.size(); ++pos) { if (line[pos] == QLatin1Char('"') || line[pos] == QLatin1Char('<')) { term_char = line[pos] == QLatin1Char('"') ? '"' : '>'; range.setStart({ range.start().line(), ++pos }); break; } } for (; pos < line.size(); ++pos) { if (line[pos] == QLatin1Char('\\')) { ++pos; continue; } else if(line[pos] == QLatin1Char(term_char)) { range.setEnd({ range.start().line(), pos }); break; } } return range; } namespace { struct FunctionInfo { KTextEditor::Range range; QString fileName; CXTranslationUnit unit; QStringList stringParts; }; CXChildVisitResult paramVisitor(CXCursor cursor, CXCursor /*parent*/, CXClientData data) { //Ignore the type of the parameter CXCursorKind kind = clang_getCursorKind(cursor); if (kind == CXCursor_TypeRef || kind == CXCursor_TemplateRef || kind == CXCursor_NamespaceRef) { return CXChildVisit_Continue; } auto *info = static_cast(data); ClangRange range(clang_getCursorExtent(cursor)); CXFile file; clang_getFileLocation(clang_getCursorLocation(cursor),&file,nullptr,nullptr,nullptr); if (!file) { clangDebug() << "Couldn't find file associated with default parameter cursor!"; //We keep going, because getting an error because we accidentally duplicated //a default parameter is better than deleting a default parameter } QString fileName = ClangString(clang_getFileName(file)).toString(); //Clang doesn't make a distinction between the default arguments being in //the declaration or definition, and the default arguments don't have lexical //parents. So this range check is the only thing that really works. if ((info->fileName.isEmpty() || fileName == info->fileName) && info->range.contains(range.toRange())) { const ClangTokens tokens(info->unit, range.range()); info->stringParts.reserve(info->stringParts.size() + tokens.size()); for (CXToken token : tokens) { info->stringParts.append(ClangString(clang_getTokenSpelling(info->unit, token)).toString()); } } return CXChildVisit_Continue; } } QVector ClangUtils::getDefaultArguments(CXCursor cursor, DefaultArgumentsMode mode) { if (!CursorKindTraits::isFunction(clang_getCursorKind(cursor))) { return QVector(); } int numArgs = clang_Cursor_getNumArguments(cursor); QVector arguments(mode == FixedSize ? numArgs : 0); QString fileName; CXFile file; clang_getFileLocation(clang_getCursorLocation(cursor),&file,nullptr,nullptr,nullptr); if (!file) { clangDebug() << "Couldn't find file associated with default parameter cursor!"; //The empty string serves as a wildcard string, because it's better to //duplicate a default parameter than delete one } else { fileName = ClangString(clang_getFileName(file)).toString(); } FunctionInfo info{ClangRange(clang_getCursorExtent(cursor)).toRange(), fileName, clang_Cursor_getTranslationUnit(cursor), QStringList()}; for (int i = 0; i < numArgs; i++) { CXCursor arg = clang_Cursor_getArgument(cursor, i); info.stringParts.clear(); clang_visitChildren(arg, paramVisitor, &info); //Clang includes the equal sign sometimes, but not other times. if (!info.stringParts.isEmpty() && info.stringParts.first() == QLatin1String("=")) { info.stringParts.removeFirst(); } //Clang seems to include the , or ) at the end of the param, so delete that if (!info.stringParts.isEmpty() && ((info.stringParts.last() == QLatin1String(",")) || (info.stringParts.last() == QLatin1String(")") && // assuming otherwise matching "(" & ")" tokens info.stringParts.count(QStringLiteral("(")) != info.stringParts.count(QStringLiteral(")"))))) { info.stringParts.removeLast(); } const QString result = info.stringParts.join(QString()); if (mode == FixedSize) { arguments.replace(i, result); } else if (!result.isEmpty()) { arguments << result; } } return arguments; } bool ClangUtils::isScopeKind(CXCursorKind kind) { return kind == CXCursor_Namespace || kind == CXCursor_StructDecl || kind == CXCursor_UnionDecl || kind == CXCursor_ClassDecl || kind == CXCursor_ClassTemplate || kind == CXCursor_ClassTemplatePartialSpecialization; } QString ClangUtils::getScope(CXCursor cursor, CXCursor context) { QStringList scope; if (clang_Cursor_isNull(context)) { context = clang_getCursorLexicalParent(cursor); } context = clang_getCanonicalCursor(context); CXCursor search = clang_getCursorSemanticParent(cursor); while (isScopeKind(clang_getCursorKind(search)) && !clang_equalCursors(search, context)) { scope.prepend(ClangString(clang_getCursorDisplayName(search)).toString()); search = clang_getCursorSemanticParent(search); } return scope.join(QStringLiteral("::")); } QString ClangUtils::getCursorSignature(CXCursor cursor, const QString& scope, const QVector& defaultArgs) { CXCursorKind kind = clang_getCursorKind(cursor); //Get the return type QString ret; ret.reserve(128); QTextStream stream(&ret); if (kind != CXCursor_Constructor && kind != CXCursor_Destructor) { stream << ClangString(clang_getTypeSpelling(clang_getCursorResultType(cursor))).toString() << ' '; } //Build the function name, with scope and parameters if (!scope.isEmpty()) { stream << scope << "::"; } QString functionName = ClangString(clang_getCursorSpelling(cursor)).toString(); if (functionName.contains(QLatin1Char('<'))) { stream << functionName.left(functionName.indexOf(QLatin1Char('<'))); } else { stream << functionName; } //Add the parameters and such stream << '('; int numArgs ; QVector args; // SEE https://bugs.kde.org/show_bug.cgi?id=368544 // clang_Cursor_getNumArguments returns -1 for FunctionTemplate // clang checks if cursor's Decl is ObjCMethodDecl or FunctionDecl // CXCursor_FunctionTemplate is neither of them instead it has a FunctionTemplateDecl // HACK Get function template arguments by visiting children if (kind == CXCursor_FunctionTemplate) { clang_visitChildren(cursor, [] (CXCursor cursor, CXCursor /*parent*/, CXClientData data) { if (clang_getCursorKind(cursor) == CXCursor_ParmDecl) { (static_cast*>(data))->push_back(cursor); } return CXChildVisit_Continue; }, &args); numArgs = args.size(); } else { numArgs = clang_Cursor_getNumArguments(cursor); args.reserve(numArgs); for (int i = 0; i < numArgs; i++) { CXCursor arg = clang_Cursor_getArgument(cursor, i); args.push_back(arg); } } for (int i = 0; i < numArgs; i++) { CXCursor arg = args[i]; //Clang formats pointer types as "t *x" and reference types as "t &x", while //KDevelop formats them as "t* x" and "t& x". Make that adjustment. const QString type = ClangString(clang_getTypeSpelling(clang_getCursorType(arg))).toString(); if (type.endsWith(QLatin1String(" *")) || type.endsWith(QLatin1String(" &"))) { stream << type.left(type.length() - 2) << type.at(type.length() - 1); } else { stream << type; } const QString id = ClangString(clang_getCursorDisplayName(arg)).toString(); if (!id.isEmpty()) { stream << ' ' << id; } if (i < defaultArgs.count() && !defaultArgs.at(i).isEmpty()) { stream << " = " << defaultArgs.at(i); } if (i < numArgs - 1) { stream << ", "; } } if (clang_Cursor_isVariadic(cursor)) { if (numArgs > 0) { stream << ", "; } stream << "..."; } stream << ')'; if (clang_CXXMethod_isConst(cursor)) { stream << " const"; } return ret; } QStringList ClangUtils::templateArgumentTypes(CXCursor cursor) { CXType typeList = clang_getCursorType(cursor); int templateArgCount = clang_Type_getNumTemplateArguments(typeList); QStringList types; types.reserve(templateArgCount); for (int i = 0; i < templateArgCount; ++i) { ClangString clangString(clang_getTypeSpelling(clang_Type_getTemplateArgumentAsType(typeList, i))); types.append(clangString.toString()); } return types; } -QByteArray ClangUtils::getRawContents(CXTranslationUnit unit, CXSourceRange range) +#if CINDEX_VERSION_MINOR >= 45 +QString ClangUtils::getRawContents(CXTranslationUnit unit, CXSourceRange range) { const auto rangeStart = clang_getRangeStart(range); const auto rangeEnd = clang_getRangeEnd(range); CXFile rangeFile; unsigned int start, end; clang_getFileLocation(rangeStart, &rangeFile, nullptr, nullptr, &start); clang_getFileLocation(rangeEnd, nullptr, nullptr, nullptr, &end); std::size_t fileSize; const char* fileBuffer = clang_getFileContents(unit, rangeFile, &fileSize); if (fileBuffer && start < fileSize && end <= fileSize && start < end) { - return QByteArray(fileBuffer + start, end - start); + return QString::fromUtf8(fileBuffer + start, end - start); } - return QByteArray(); + return QString(); } +#else +QString ClangUtils::getRawContents(CXTranslationUnit unit, CXSourceRange range) +{ + const auto rangeStart = clang_getRangeStart(range); + const auto rangeEnd = clang_getRangeEnd(range); + unsigned int start, end; + clang_getFileLocation(rangeStart, nullptr, nullptr, nullptr, &start); + clang_getFileLocation(rangeEnd, nullptr, nullptr, nullptr, &end); + + QByteArray result; + const ClangTokens tokens(unit, range); + for (CXToken token : tokens) { + const auto location = ClangLocation(clang_getTokenLocation(unit, token)); + unsigned int offset; + clang_getFileLocation(location, nullptr, nullptr, nullptr, &offset); + if (offset < start) // TODO: Sometimes hit, see bug 357585 + return {}; + + const int fillCharacters = offset - start - result.size(); + Q_ASSERT(fillCharacters >= 0); + if (fillCharacters < 0) + return {}; + + result.append(QByteArray(fillCharacters, ' ')); + const auto spelling = clang_getTokenSpelling(unit, token); + result.append(clang_getCString(spelling)); + clang_disposeString(spelling); + } + // Clang always appends the full range of the last token, even if this exceeds the end of the requested range. + // Fix this. + result.chop(result.size() - (end - start)); + + return QString::fromUtf8(result); +} +#endif bool ClangUtils::isExplicitlyDefaultedOrDeleted(CXCursor cursor) { if (clang_getCursorAvailability(cursor) == CXAvailability_NotAvailable) { return true; } #if CINDEX_VERSION_MINOR >= 34 if (clang_CXXMethod_isDefaulted(cursor)) { return true; } #else auto declCursor = clang_getCanonicalCursor(cursor); CXTranslationUnit tu = clang_Cursor_getTranslationUnit(declCursor); ClangTokens tokens(tu, clang_getCursorExtent(declCursor)); bool lastTokenWasDeleteOrDefault = false; for (auto it = tokens.rbegin(), end = tokens.rend(); it != end; ++it) { CXToken token = *it; auto kind = clang_getTokenKind(token); switch (kind) { case CXToken_Comment: break; case CXToken_Identifier: case CXToken_Literal: lastTokenWasDeleteOrDefault = false; break; case CXToken_Punctuation: { ClangString spelling(clang_getTokenSpelling(tu, token)); const char* spellingCStr = spelling.c_str(); if (strcmp(spellingCStr, ")") == 0) { // a closing parent means we have reached the end of the function parameter list // therefore this function can't be explicitly deleted/defaulted return false; } else if (strcmp(spellingCStr, "=") == 0) { if (lastTokenWasDeleteOrDefault) { return true; } #if CINDEX_VERSION_MINOR < 31 // HACK: on old clang versions, we don't get the default/delete // so there, assume the function is defaulted or deleted // when the last token is an equal sign if (it == tokens.rbegin()) { return true; } #endif } lastTokenWasDeleteOrDefault = false; break; } case CXToken_Keyword: { ClangString spelling(clang_getTokenSpelling(tu, token)); const char* spellingCStr = spelling.c_str(); if (strcmp(spellingCStr, "default") == 0 #if CINDEX_VERSION_MINOR < 31 || strcmp(spellingCStr, "delete") == 0 #endif ) { lastTokenWasDeleteOrDefault = true; } else { lastTokenWasDeleteOrDefault = false; } break; } } } #endif return false; } KDevelop::ClassFunctionFlags ClangUtils::specialAttributes(CXCursor cursor) { // check for our injected attributes to detect Qt signals and slots // see also the contents of wrappedQtHeaders/QtCore/qobjectdefs.h ClassFunctionFlags flags = {}; if (cursor.kind == CXCursor_CXXMethod) { clang_visitChildren(cursor, [] (CXCursor cursor, CXCursor /*parent*/, CXClientData data) -> CXChildVisitResult { auto& flags = *static_cast(data); switch (cursor.kind) { case CXCursor_AnnotateAttr: { ClangString attribute(clang_getCursorDisplayName(cursor)); if (attribute.c_str() == QByteArrayLiteral("qt_signal")) { flags |= FunctionSignalFlag; } else if (attribute.c_str() == QByteArrayLiteral("qt_slot")) { flags |= FunctionSlotFlag; } break; } case CXCursor_CXXFinalAttr: flags |= FinalFunctionFlag; break; default: break; } return CXChildVisit_Break; }, &flags); } return flags; } unsigned int ClangUtils::skipTopCommentBlock(CXTranslationUnit unit, CXFile file) { const auto fileRange = clang_getRange(clang_getLocation(unit, file, 1, 1), clang_getLocation(unit, file, std::numeric_limits::max(), 1)); const ClangTokens tokens (unit, fileRange); const auto nonCommentToken = std::find_if(tokens.begin(), tokens.end(), [&](CXToken token) { return clang_getTokenKind(token) != CXToken_Comment; }); // explicitly handle this case, otherwise we skip the preceding whitespace if (nonCommentToken == tokens.begin()) { return 1; } const auto location = (nonCommentToken != tokens.end()) ? clang_getTokenExtent(unit, *nonCommentToken) : fileRange; return KTextEditor::Cursor(ClangRange(location).end()).line() + 1; } diff --git a/plugins/clang/util/clangutils.h b/plugins/clang/util/clangutils.h index 72cf95d493..811f331610 100644 --- a/plugins/clang/util/clangutils.h +++ b/plugins/clang/util/clangutils.h @@ -1,177 +1,173 @@ /* * Copyright 2014 Kevin Funk * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef CLANGUTILS_H #define CLANGUTILS_H #include #include #include "clangprivateexport.h" #include "../duchain/unsavedfile.h" #include namespace ClangUtils { /** * Finds the most specific CXCursor which applies to the specified line and column * in the given translation unit and file. * * @param line The 0-indexed line number at which to search. * @param column The 0-indexed column number at which to search. * @param unit The translation unit to examine. * @param file The file in the translation unit to examine. * * @return The cursor at the specified location */ CXCursor getCXCursor(int line, int column, const CXTranslationUnit& unit, const CXFile& file); enum DefaultArgumentsMode { FixedSize, ///< The vector will have length equal to the number of arguments to the function /// and any arguments without a default parameter will be represented with an empty string. MinimumSize ///< The vector will have a length equal to the number of default values }; /** * Given a cursor representing a function, returns a vector containing the string * representations of the default arguments of the function which are defined at * the occurrence of the cursor. Note that this is not necessarily all of the default * arguments of the function. * * @param cursor The cursor to examine * @return a vector of QStrings representing the default arguments, or an empty * vector if cursor does not represent a function */ QVector getDefaultArguments(CXCursor cursor, DefaultArgumentsMode mode = FixedSize); /** * @return true when the cursor kind references a named scope. */ bool isScopeKind(CXCursorKind kind); /** * @brief Retrieve a list of all unsaved files. * * @note Since this reads text from the editor widget, it must be called from the * GUI thread or with the foreground lock held. * * @return vector of all unsaved files and their current contents */ KDEVCLANGPRIVATE_EXPORT QVector unsavedFiles(); /** * Given a cursor and destination context, returns the string representing the * cursor's scope at its current location. * * @param cursor The cursor to examine * @param context The destination context from which the cursor should be referenced. * By default this will be set to the cursors lexical parent. * @return the cursor's scope as a string */ KDEVCLANGPRIVATE_EXPORT QString getScope(CXCursor cursor, CXCursor context = clang_getNullCursor()); /** * Given a cursor representing some sort of function, returns its signature. The * effect of this function when passed a non-function cursor is undefined. * * @param cursor The cursor to work with * @param scope The scope of the cursor (e.g. "SomeNS::SomeClass") * @return A QString of the function's signature */ QString getCursorSignature(CXCursor cursor, const QString& scope, const QVector& defaultArgs = QVector()); /** * Given a cursor representing the template argument list, return a * list of the argument types. * * @param cursor The cursor to work with * @return A QStringList of the template's arguments */ KDEVCLANGPRIVATE_EXPORT QStringList templateArgumentTypes(CXCursor cursor); /** * Extract the raw contents of the range @p range * * @note This will return the exact textual representation of the code, * no whitespace stripped, etc. * - * TODO: It would better if we'd be able to just memcpy parts of the file buffer - * that's stored inside Clang (cf. llvm::MemoryBuffer for files), but libclang - * doesn't offer API for that. This implementation here is a lot more expensive. - * * @param unit Translation unit this range is part of */ - KDEVCLANGPRIVATE_EXPORT QByteArray getRawContents(CXTranslationUnit unit, CXSourceRange range); + KDEVCLANGPRIVATE_EXPORT QString getRawContents(CXTranslationUnit unit, CXSourceRange range); /** * @brief Return true if file @p file1 and file @p file2 are equal * * @see clang_File_isEqual */ inline bool isFileEqual(CXFile file1, CXFile file2) { #if CINDEX_VERSION_MINOR >= 28 return clang_File_isEqual(file1, file2); #else // note: according to the implementation of clang_File_isEqual, file1 and file2 can still be equal, // regardless of whether file1 == file2 is true or not // however, we didn't see any problems with pure pointer comparisons until now, so fall back to that return file1 == file2; #endif } /** * @brief Return true if the cursor @p cursor refers to an explicitly deleted/defaulted function * such as the default constructor in "struct Foo { Foo() = delete; }" * * TODO: do we need isExplicitlyDefaulted() + isExplicitlyDeleted()? * Currently this is only used by the implements completion to hide deleted+defaulted functions so * we don't need to know the difference. We need to tokenize the source code because there is no * such API in libclang so having one function to check both cases is more efficient (only tokenize once) */ bool isExplicitlyDefaultedOrDeleted(CXCursor cursor); /** * Extract the range of the path-spec inside the include-directive in line @p line * * Example: line = "#include " => returns {0, 10, 0, 16} * * @param originalRange This is the range that the resulting range will be based on * * @return Range pointing to the path-spec of the include or invalid range if there is no #include directive on the line. */ KDEVCLANGPRIVATE_EXPORT KTextEditor::Range rangeForIncludePathSpec(const QString& line, const KTextEditor::Range& originalRange = KTextEditor::Range()); /** * Returns special attributes (isFinal, isQtSlot, ...) given a @p cursor representing a CXXmethod */ KDevelop::ClassFunctionFlags specialAttributes(CXCursor cursor); /** * @return the top most line in a file skipping any comment block */ unsigned int skipTopCommentBlock(CXTranslationUnit unit, CXFile file); } #endif // CLANGUTILS_H