From c7701fe91022a116e0a37f410e70fe8906f56441 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johann=20Kl=C3=A4hn?= <johann.klaehn@kip.uni-heidelberg.de> Date: Mon, 17 Jul 2017 12:25:49 +0200 Subject: [PATCH 11/12] [libclang] Allow visiting of implicit declarations and template instantiations (WIP!) --- bindings/python/clang/cindex.py | 45 +++++++++-- bindings/python/tests/cindex/test_cursor.py | 33 ++++++++ include/clang-c/Index.h | 31 ++++++++ tools/libclang/CIndex.cpp | 112 ++++++++++++++++++++++++++-- tools/libclang/CursorVisitor.h | 12 ++- tools/libclang/libclang.exports | 2 + 6 files changed, 219 insertions(+), 16 deletions(-) diff --git a/tools/clang/bindings/python/clang/cindex.py b/tools/clang/bindings/python/clang/cindex.py index b21f2b75f2..2416fd1803 100644 --- a/tools/clang/bindings/python/clang/cindex.py +++ b/tools/clang/bindings/python/clang/cindex.py @@ -1407,6 +1407,15 @@ class Cursor(Structure): """ _fields_ = [("_kind_id", c_int), ("xdata", c_int), ("data", c_void_p * 3)] + # Default behavior. + GET_CHILDREN_NONE = 0 + + # Used to indicate that implicit cursors should be visited. + GET_CHILDREN_WITH_IMPLICIT = 1 + + # Used to indicate that template instantiations should be visited. + GET_CHILDREN_WITH_TEMPLATE_INSTANTIATIONS = 2 + @staticmethod def from_location(tu, location): # We store a reference to the TU in the instance so the TU won't get @@ -1496,6 +1505,10 @@ class Cursor(Structure): """ return conf.lib.clang_EnumDecl_isScoped(self) + def is_implicit(self): + """Test whether the cursor refers to an implicit declaration.""" + return conf.lib.clang_isImplicit(self) + def get_definition(self): """ If the cursor is a reference to a declaration or a declaration of @@ -1790,8 +1803,12 @@ class Cursor(Structure): """Returns the value of the indicated arg as an unsigned 64b integer.""" return conf.lib.clang_Cursor_getTemplateArgumentUnsignedValue(self, num) - def get_children(self): - """Return an iterator for accessing the children of this cursor.""" + def get_children(self, with_implicit=False, with_template_instantiations=False): + """Return an iterator for accessing the children of this cursor. + + By default, cursors representing implicit declarations or template instantiations + will be skipped. + """ # FIXME: Expose iteration from CIndex, PR6125. def visitor(child, parent, children): @@ -1804,18 +1821,24 @@ class Cursor(Structure): children.append(child) return 1 # continue children = [] - conf.lib.clang_visitChildren(self, callbacks['cursor_visit'](visitor), - children) + dispatch = conf.lib.clang_visitChildren + options = Cursor.GET_CHILDREN_NONE + if with_implicit: + options |= Cursor.GET_CHILDREN_WITH_IMPLICIT + if with_template_instantiations: + options |= Cursor.GET_CHILDREN_WITH_TEMPLATE_INSTANTIATIONS + conf.lib.clang_visitChildrenWithOptions( + self, callbacks['cursor_visit'](visitor), children, options) return iter(children) - def walk_preorder(self): + def walk_preorder(self, **kwargs): """Depth-first preorder walk over the cursor and its descendants. Yields cursors. """ yield self - for child in self.get_children(): - for descendant in child.walk_preorder(): + for child in self.get_children(**kwargs): + for descendant in child.walk_preorder(**kwargs): yield descendant def get_tokens(self, options=0): @@ -3840,6 +3863,10 @@ functionList = [ [Type], bool), + ("clang_isImplicit", + [Cursor], + bool), + ("clang_isInvalid", [CursorKind], bool), @@ -3903,6 +3930,10 @@ functionList = [ [Cursor, callbacks['cursor_visit'], py_object], c_uint), + ("clang_visitChildrenWithOptions", + [Cursor, callbacks['cursor_visit'], py_object, c_uint], + c_uint), + ("clang_Cursor_getNumArguments", [Cursor], c_int), diff --git a/tools/clang/bindings/python/tests/cindex/test_cursor.py b/tools/clang/bindings/python/tests/cindex/test_cursor.py index 6c20577302..43606b605c 100644 --- a/tools/clang/bindings/python/tests/cindex/test_cursor.py +++ b/tools/clang/bindings/python/tests/cindex/test_cursor.py @@ -70,6 +70,39 @@ def test_get_children(): assert tu_nodes[2].displayname == 'f0(int, int)' assert tu_nodes[2].is_definition() == True +def test_get_children_with_implicit(): + tu = get_tu('struct X {}; X x;', lang='cpp') + cursor = get_cursor(tu, 'X') + + children = list(cursor.get_children()) + assert len(children) == 0 + + children = list(cursor.get_children(with_implicit=True)) + assert len(children) > 0 + for child in children: + assert child.is_implicit() + assert child.spelling == "X" + assert child.kind in [CursorKind.CONSTRUCTOR, CursorKind.STRUCT_DECL] + +def test_get_children_with_template_instantiations(): + tu = get_tu( + 'template <typename T> T frobnicate(T val);' + 'extern template int frobnicate<int>(int);', + lang='cpp') + cursor = get_cursor(tu, 'frobnicate') + assert cursor.kind == CursorKind.FUNCTION_TEMPLATE + + for child in cursor.get_children(): + # should not return an instantiation: + assert child.kind != CursorKind.FUNCTION_DECL + + for child in cursor.get_children(with_template_instantiations=True): + if child.kind == CursorKind.FUNCTION_DECL: + assert child.spelling == 'frobnicate' + break + else: + assert False, "Couldn't find template instantiation" + def test_references(): """Ensure that references to TranslationUnit are kept.""" tu = get_tu('int x;') diff --git a/tools/clang/include/clang-c/Index.h b/tools/clang/include/clang-c/Index.h index 7fd17366ee..abe70e9566 100644 --- a/tools/clang/include/clang-c/Index.h +++ b/tools/clang/include/clang-c/Index.h @@ -2670,6 +2670,11 @@ CINDEX_LINKAGE unsigned clang_isPreprocessing(enum CXCursorKind); */ CINDEX_LINKAGE unsigned clang_isUnexposed(enum CXCursorKind); +/*** + * \brief Determine whether the given cursor represents an implicit declaration. + */ +CINDEX_LINKAGE unsigned clang_isImplicit(CXCursor); + /** * \brief Describe the linkage of the entity referred to by a cursor. */ @@ -3961,6 +3966,32 @@ CINDEX_LINKAGE unsigned clang_visitChildrenWithBlock(CXCursor parent, # endif #endif +typedef enum { + /** + * \brief Default behavior. + */ + CXVisitChildren_None = 0x0, + + /** + * \brief Used to indicate that implicit cursors should be visited. + */ + CXVisitChildren_WithImplicit = 0x1, + + /** + * \brief Used to indicate that template instantiations should be visited. + */ + CXVisitChildren_WithTemplateInstantiations = 0x2 +} CXVisitChildren_Flags; + +/** + * \brief Visits the children of a cursor, allowing to pass extra options. + * Behaves identically to clang_visitChildren() in all other respects. + */ +CINDEX_LINKAGE unsigned clang_visitChildrenWithOptions(CXCursor parent, + CXCursorVisitor visitor, + CXClientData client_data, + unsigned options); + /** * @} */ diff --git a/tools/clang/tools/libclang/CIndex.cpp b/tools/clang/tools/libclang/CIndex.cpp index 27f74b2aa2..f32611b8d7 100644 --- a/tools/clang/tools/libclang/CIndex.cpp +++ b/tools/clang/tools/libclang/CIndex.cpp @@ -192,10 +192,11 @@ bool CursorVisitor::Visit(CXCursor Cursor, bool CheckedRegionOfInterest) { assert(0 && "Invalid declaration cursor"); return true; // abort. } - - // Ignore implicit declarations, unless it's an objc method because - // currently we should report implicit methods for properties when indexing. - if (D->isImplicit() && !isa<ObjCMethodDecl>(D)) + + // Unless instructed otherwise we ignore implicit declarations. + // ObjC methods are currently visited in any case, because implicit methods + // for properties should be reported when indexing. + if (!VisitImplicitDeclarations && D->isImplicit() && !isa<ObjCMethodDecl>(D)) return false; } @@ -700,10 +701,13 @@ bool CursorVisitor::VisitTagDecl(TagDecl *D) { bool CursorVisitor::VisitClassTemplateSpecializationDecl( ClassTemplateSpecializationDecl *D) { - bool ShouldVisitBody = false; + bool ShouldVisitBody = VisitTemplateInstantiations; switch (D->getSpecializationKind()) { - case TSK_Undeclared: case TSK_ImplicitInstantiation: + if (VisitTemplateInstantiations && VisitImplicitDeclarations) { + break; + } + case TSK_Undeclared: // Nothing to visit return false; @@ -712,6 +716,7 @@ bool CursorVisitor::VisitClassTemplateSpecializationDecl( break; case TSK_ExplicitSpecialization: + // Always visit body of explicit specializations ShouldVisitBody = true; break; } @@ -908,7 +913,31 @@ bool CursorVisitor::VisitFunctionTemplateDecl(FunctionTemplateDecl *D) { return true; auto* FD = D->getTemplatedDecl(); - return VisitAttributes(FD) || VisitFunctionDecl(FD); + if (VisitAttributes(FD) || VisitFunctionDecl(FD)) + return true; + + if (VisitTemplateInstantiations && D == D->getCanonicalDecl()) { + for (auto *FD : D->specializations()) { + for (auto *RD : FD->redecls()) { + switch (RD->getTemplateSpecializationKind()) { + case TSK_Undeclared: + case TSK_ImplicitInstantiation: + case TSK_ExplicitInstantiationDeclaration: + case TSK_ExplicitInstantiationDefinition: { + const Optional<bool> V = handleDeclForVisitation(RD); + if (!V.hasValue()) + continue; + return V.getValue(); + } + + case TSK_ExplicitSpecialization: + break; + } + } + } + } + + return false; } bool CursorVisitor::VisitClassTemplateDecl(ClassTemplateDecl *D) { @@ -918,7 +947,40 @@ bool CursorVisitor::VisitClassTemplateDecl(ClassTemplateDecl *D) { return true; auto* CD = D->getTemplatedDecl(); - return VisitAttributes(CD) || VisitCXXRecordDecl(CD); + if (VisitAttributes(CD) || VisitCXXRecordDecl(CD)) + return true; + + if (VisitTemplateInstantiations && D == D->getCanonicalDecl()) { + for (auto *SD : D->specializations()) { + for (auto *RD : SD->redecls()) { + // We don't want to visit injected-class-names in this traversal. + if (cast<CXXRecordDecl>(RD)->isInjectedClassName()) + continue; + + switch ( + cast<ClassTemplateSpecializationDecl>(RD)->getSpecializationKind()) { + // Visit the implicit instantiations with the requested pattern. + case TSK_Undeclared: + case TSK_ImplicitInstantiation: { + const Optional<bool> V = handleDeclForVisitation(RD); + if (!V.hasValue()) + continue; + return V.getValue(); + } + + // We don't need to do anything on an explicit instantiation + // or explicit specialization because there will be an explicit + // node for it elsewhere. + case TSK_ExplicitInstantiationDeclaration: + case TSK_ExplicitInstantiationDefinition: + case TSK_ExplicitSpecialization: + break; + } + } + } + } + + return false; } bool CursorVisitor::VisitTemplateTemplateParmDecl(TemplateTemplateParmDecl *D) { @@ -4314,6 +4376,24 @@ unsigned clang_visitChildrenWithBlock(CXCursor parent, return clang_visitChildren(parent, visitWithBlock, block); } +unsigned clang_visitChildrenWithOptions(CXCursor parent, + CXCursorVisitor visitor, + CXClientData client_data, + unsigned options) { + CursorVisitor CursorVis( + getCursorTU(parent), visitor, client_data, + /*VisitPreprocessorLast=*/false, + /*VisitIncludedPreprocessingEntries=*/false, + /*RegionOfInterest=*/SourceRange(), + /*VisitDeclsOnly=*/false, + /*PostChildrenVisitor=*/nullptr, + /*VisitImplicitDeclarations=*/(options & CXVisitChildren_WithImplicit), + /*VisitTemplateInstantiations=*/ + (options & CXVisitChildren_WithTemplateInstantiations)); + + return CursorVis.VisitChildren(parent); +} + static CXString getDeclSpelling(const Decl *D) { if (!D) return cxstring::createEmpty(); @@ -5402,6 +5482,22 @@ unsigned clang_isUnexposed(enum CXCursorKind K) { } } +unsigned clang_isImplicit(CXCursor Cursor) { + if (clang_isInvalid(Cursor.kind)) + return false; + + if (!clang_isDeclaration(Cursor.kind)) + return false; + + const Decl *D = getCursorDecl(Cursor); + if (!D) { + assert(0 && "Invalid declaration cursor"); + return true; // abort. + } + + return D->isImplicit(); +} + CXCursorKind clang_getCursorKind(CXCursor C) { return C.kind; } diff --git a/tools/clang/tools/libclang/CursorVisitor.h b/tools/clang/tools/libclang/CursorVisitor.h index 82f251a348..c659e866ef 100644 --- a/tools/clang/tools/libclang/CursorVisitor.h +++ b/tools/clang/tools/libclang/CursorVisitor.h @@ -96,6 +96,12 @@ private: /// record entries. bool VisitDeclsOnly; + /// \brief Whether we should visit implicit declarations. + bool VisitImplicitDeclarations; + + /// \brief Whether we should recurse into template instantiations. + bool VisitTemplateInstantiations; + // FIXME: Eventually remove. This part of a hack to support proper // iteration over all Decls contained lexically within an ObjC container. DeclContext::decl_iterator *DI_current; @@ -147,7 +153,9 @@ public: bool VisitIncludedPreprocessingEntries = false, SourceRange RegionOfInterest = SourceRange(), bool VisitDeclsOnly = false, - PostChildrenVisitorTy PostChildrenVisitor = nullptr) + PostChildrenVisitorTy PostChildrenVisitor = nullptr, + bool VisitImplicitDeclarations = false, + bool VisitTemplateInstantiations = false) : TU(TU), AU(cxtu::getASTUnit(TU)), Visitor(Visitor), PostChildrenVisitor(PostChildrenVisitor), ClientData(ClientData), @@ -155,6 +163,8 @@ public: VisitIncludedEntities(VisitIncludedPreprocessingEntries), RegionOfInterest(RegionOfInterest), VisitDeclsOnly(VisitDeclsOnly), + VisitImplicitDeclarations(VisitImplicitDeclarations), + VisitTemplateInstantiations(VisitTemplateInstantiations), DI_current(nullptr), FileDI_current(nullptr) { Parent.kind = CXCursor_NoDeclFound; diff --git a/tools/clang/tools/libclang/libclang.exports b/tools/clang/tools/libclang/libclang.exports index b8e3df23ef..59c46ae09e 100644 --- a/tools/clang/tools/libclang/libclang.exports +++ b/tools/clang/tools/libclang/libclang.exports @@ -291,6 +291,7 @@ clang_isDeclaration clang_isExpression clang_isFileMultipleIncludeGuarded clang_isFunctionTypeVariadic +clang_isImplicit clang_isInvalid clang_isPODType clang_isPreprocessing @@ -332,6 +333,7 @@ clang_CompileCommand_getNumArgs clang_CompileCommand_getArg clang_visitChildren clang_visitChildrenWithBlock +clang_visitChildrenWithOptions clang_ModuleMapDescriptor_create clang_ModuleMapDescriptor_dispose clang_ModuleMapDescriptor_setFrameworkModuleName -- 2.13.0