https://github.com/efriedma-quic updated https://github.com/llvm/llvm-project/pull/120300
>From 12ad982b984a4e533db4220332f6edbf4b4903a2 Mon Sep 17 00:00:00 2001 From: Eli Friedman <efrie...@quicinc.com> Date: Tue, 10 Dec 2024 15:23:55 -0800 Subject: [PATCH] [cindex] Add API to query more information about base classes. The first API is clang_visitCXXBaseClasses: this allows visiting the base classes without going through the generic child visitor (which is awkward, and doesn't work for template instantiations). The second API is clang_getOffsetOfBase; this allows computing the offset of a base in the class layout, the same way clang_Cursor_getOffsetOfField compues the offset of a field. Also, add a Python binding for the existing function clang_isVirtualBase. --- clang/bindings/python/clang/cindex.py | 25 +++++++++++++ .../bindings/python/tests/cindex/test_type.py | 25 +++++++++++++ clang/docs/ReleaseNotes.rst | 10 ++++++ clang/include/clang-c/Index.h | 36 +++++++++++++++++-- clang/tools/libclang/CIndexCXX.cpp | 27 ++++++++++++++ clang/tools/libclang/CXType.cpp | 34 ++++++++++++++++++ clang/tools/libclang/libclang.map | 2 ++ 7 files changed, 157 insertions(+), 2 deletions(-) diff --git a/clang/bindings/python/clang/cindex.py b/clang/bindings/python/clang/cindex.py index 710259de855f9b..806e1b40f3c9e1 100644 --- a/clang/bindings/python/clang/cindex.py +++ b/clang/bindings/python/clang/cindex.py @@ -2133,6 +2133,14 @@ def get_field_offsetof(self): """Returns the offsetof the FIELD_DECL pointed by this Cursor.""" return conf.lib.clang_Cursor_getOffsetOfField(self) # type: ignore [no-any-return] + def get_base_offsetof(self, parent): + """Returns the offsetof the CXX_BASE_SPECIFIER pointed by this Cursor.""" + return conf.lib.clang_getOffsetOfBase(parent, self) # type: ignore [no-any-return] + + def is_virtual_base(self): + """Returns whether the CXX_BASE_SPECIFIER pointed by this Cursor is virtual.""" + return conf.lib.clang_isVirtualBase(self) # type: ignore [no-any-return] + def is_anonymous(self): """ Check whether this is a record type without a name, or a field where @@ -2687,6 +2695,21 @@ def visitor(field, children): conf.lib.clang_Type_visitFields(self, fields_visit_callback(visitor), fields) return iter(fields) + def get_bases(self): + """Return an iterator for accessing the base classes of this type.""" + + def visitor(base, children): + assert base != conf.lib.clang_getNullCursor() + + # Create reference to TU so it isn't GC'd before Cursor. + base._tu = self._tu + bases.append(base) + return 1 # continue + + bases: list[Cursor] = [] + conf.lib.clang_visitCXXBaseClasses(self, fields_visit_callback(visitor), bases) + return iter(bases) + def get_exception_specification_kind(self): """ Return the kind of the exception specification; a value from @@ -3940,6 +3963,7 @@ def set_property(self, property, value): ("clang_getNumDiagnosticsInSet", [c_object_p], c_uint), ("clang_getNumElements", [Type], c_longlong), ("clang_getNumOverloadedDecls", [Cursor], c_uint), + ("clang_getOffsetOfBase", [Cursor, Cursor], c_longlong), ("clang_getOverloadedDecl", [Cursor, c_uint], Cursor), ("clang_getPointeeType", [Type], Type), ("clang_getRange", [SourceLocation, SourceLocation], SourceRange), @@ -3992,6 +4016,7 @@ def set_property(self, property, value): [TranslationUnit, SourceRange, POINTER(POINTER(Token)), POINTER(c_uint)], ), ("clang_visitChildren", [Cursor, cursor_visit_callback, py_object], c_uint), + ("clang_visitCXXBaseClasses", [Type, fields_visit_callback, py_object], c_uint), ("clang_Cursor_getNumArguments", [Cursor], c_int), ("clang_Cursor_getArgument", [Cursor, c_uint], Cursor), ("clang_Cursor_getNumTemplateArguments", [Cursor], c_int), diff --git a/clang/bindings/python/tests/cindex/test_type.py b/clang/bindings/python/tests/cindex/test_type.py index f39da8b5faf297..db7dc6458581e6 100644 --- a/clang/bindings/python/tests/cindex/test_type.py +++ b/clang/bindings/python/tests/cindex/test_type.py @@ -534,3 +534,28 @@ def test_pretty(self): self.assertEqual(f.type.get_canonical().pretty_printed(pp), "X") pp.set_property(PrintingPolicyProperty.SuppressTagKeyword, False) self.assertEqual(f.type.get_canonical().pretty_printed(pp), "struct X") + + def test_base_classes(self): + source = """ + class A { int a; }; + class B { int b; }; + class C { int c; }; + template <typename T> + class Template : public A, public B, virtual C { + }; + Template<int> instance; + int bar; + """ + tu = get_tu(source, lang="cpp") + cursor = get_cursor(tu, "instance") + cursor_type = cursor.type + cursor_type_decl = cursor_type.get_declaration() + self.assertEqual(cursor.kind, CursorKind.VAR_DECL) + bases = list(cursor_type.get_bases()) + self.assertEqual(len(bases), 3) + self.assertFalse(bases[0].is_virtual_base()) + self.assertEqual(bases[0].get_base_offsetof(cursor_type_decl), 64) + self.assertFalse(bases[1].is_virtual_base()) + self.assertEqual(bases[1].get_base_offsetof(cursor_type_decl), 96) + self.assertTrue(bases[2].is_virtual_base()) + self.assertEqual(bases[2].get_base_offsetof(cursor_type_decl), 128) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 190843f2aa6c96..b56fd8d757629d 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -1184,6 +1184,10 @@ libclang whether the first one comes strictly before the second in the source code. - Add ``clang_getTypePrettyPrinted``. It allows controlling the PrintingPolicy used to pretty-print a type. +- Added ``clang_visitCXXBaseClasses``, which allows visiting the base classes + of a class. +- Added ``clang_getOffsetOfBase``, which allows computing the offset of a base + class in a class's layout. Static Analyzer --------------- @@ -1331,6 +1335,12 @@ Python Binding Changes declaration is an anonymous union or anonymous struct. - Added ``Type.pretty_printed`, a binding for ``clang_getTypePrettyPrinted``, which allows changing the formatting of pretty-printed types. +- Added ``Cursor.is_virtual_base``, a binding for ``clang_isVirtualBase``, + which checks whether a base class is virtual. +- Added ``Type.get_bases``, a binding for ``clang_visitCXXBaseClasses``, which + allows visiting the base classes of a class. +- Added ``Cursor.get_base_offsetof``, a binding for ``clang_getOffsetOfBase``, + which allows computing the offset of a base class in a class's layout. OpenMP Support -------------- diff --git a/clang/include/clang-c/Index.h b/clang/include/clang-c/Index.h index ad64497ceb8025..aac5d1fa8aa2e0 100644 --- a/clang/include/clang-c/Index.h +++ b/clang/include/clang-c/Index.h @@ -3605,8 +3605,8 @@ CINDEX_LINKAGE enum CXTypeNullabilityKind clang_Type_getNullability(CXType T); /** * List the possible error codes for \c clang_Type_getSizeOf, - * \c clang_Type_getAlignOf, \c clang_Type_getOffsetOf and - * \c clang_Cursor_getOffsetOf. + * \c clang_Type_getAlignOf, \c clang_Type_getOffsetOf, + * \c clang_Cursor_getOffsetOf, and \c clang_getOffsetOfBase. * * A value of this enumeration type can be returned if the target type is not * a valid argument to sizeof, alignof or offsetof. @@ -3771,6 +3771,15 @@ CINDEX_LINKAGE enum CXRefQualifierKind clang_Type_getCXXRefQualifier(CXType T); */ CINDEX_LINKAGE unsigned clang_isVirtualBase(CXCursor); +/** + * Returns the offset in bits of a CX_CXXBaseSpecifier relative to the parent + * class. + * + * Returns a small negative number if the offset cannot be computed. See + * CXTypeLayoutError for error codes. + */ +CINDEX_LINKAGE long long clang_getOffsetOfBase(CXCursor Parent, CXCursor Base); + /** * Represents the C++ access control level to a base class for a * cursor with kind CX_CXXBaseSpecifier. @@ -6648,6 +6657,29 @@ typedef enum CXVisitorResult (*CXFieldVisitor)(CXCursor C, CINDEX_LINKAGE unsigned clang_Type_visitFields(CXType T, CXFieldVisitor visitor, CXClientData client_data); +/** + * Visit the base classes of a type. + * + * This function visits all the direct base classes of a the given cursor, + * invoking the given \p visitor function with the cursors of each + * visited base. The traversal may be ended prematurely, if + * the visitor returns \c CXFieldVisit_Break. + * + * \param T the record type whose field may be visited. + * + * \param visitor the visitor function that will be invoked for each + * field of \p T. + * + * \param client_data pointer data supplied by the client, which will + * be passed to the visitor each time it is invoked. + * + * \returns a non-zero value if the traversal was terminated + * prematurely by the visitor returning \c CXFieldVisit_Break. + */ +CINDEX_LINKAGE unsigned clang_visitCXXBaseClasses(CXType T, + CXFieldVisitor visitor, + CXClientData client_data); + /** * Describes the kind of binary operators. */ diff --git a/clang/tools/libclang/CIndexCXX.cpp b/clang/tools/libclang/CIndexCXX.cpp index a1be70dde9f670..8b84fdc22ecff1 100644 --- a/clang/tools/libclang/CIndexCXX.cpp +++ b/clang/tools/libclang/CIndexCXX.cpp @@ -27,6 +27,33 @@ unsigned clang_isVirtualBase(CXCursor C) { return B->isVirtual(); } +unsigned clang_visitCXXBaseClasses(CXType PT, CXFieldVisitor visitor, + CXClientData client_data) { + CXCursor PC = clang_getTypeDeclaration(PT); + if (clang_isInvalid(PC.kind)) + return false; + const CXXRecordDecl *RD = + dyn_cast_if_present<CXXRecordDecl>(cxcursor::getCursorDecl(PC)); + if (!RD || RD->isInvalidDecl()) + return false; + RD = RD->getDefinition(); + if (!RD || RD->isInvalidDecl()) + return false; + + for (auto &Base : RD->bases()) { + // Callback to the client. + switch ( + visitor(cxcursor::MakeCursorCXXBaseSpecifier(&Base, getCursorTU(PC)), + client_data)) { + case CXVisit_Break: + return true; + case CXVisit_Continue: + break; + } + } + return true; +} + enum CX_CXXAccessSpecifier clang_getCXXAccessSpecifier(CXCursor C) { AccessSpecifier spec = AS_none; diff --git a/clang/tools/libclang/CXType.cpp b/clang/tools/libclang/CXType.cpp index f97023c429bfae..034562c661622e 100644 --- a/clang/tools/libclang/CXType.cpp +++ b/clang/tools/libclang/CXType.cpp @@ -19,6 +19,7 @@ #include "clang/AST/DeclObjC.h" #include "clang/AST/DeclTemplate.h" #include "clang/AST/Expr.h" +#include "clang/AST/RecordLayout.h" #include "clang/AST/Type.h" #include "clang/Basic/AddressSpaces.h" #include "clang/Frontend/ASTUnit.h" @@ -1108,6 +1109,39 @@ long long clang_Cursor_getOffsetOfField(CXCursor C) { return -1; } +long long clang_getOffsetOfBase(CXCursor Parent, CXCursor Base) { + if (Base.kind != CXCursor_CXXBaseSpecifier) + return -1; + + if (!clang_isDeclaration(Parent.kind)) + return -1; + + // we need to validate the parent type + CXType PT = clang_getCursorType(Parent); + long long Error = validateFieldParentType(Parent, PT); + if (Error < 0) + return Error; + + const CXXRecordDecl *ParentRD = + dyn_cast<CXXRecordDecl>(cxcursor::getCursorDecl(Parent)); + if (!ParentRD) + return -1; + + ASTContext &Ctx = cxcursor::getCursorContext(Base); + const CXXBaseSpecifier *B = cxcursor::getCursorCXXBaseSpecifier(Base); + if (ParentRD->bases_begin() > B || ParentRD->bases_end() <= B) + return -1; + + const CXXRecordDecl *BaseRD = B->getType()->getAsCXXRecordDecl(); + if (!BaseRD) + return -1; + + const ASTRecordLayout &Layout = Ctx.getASTRecordLayout(ParentRD); + if (B->isVirtual()) + return Ctx.toBits(Layout.getVBaseClassOffset(BaseRD)); + return Ctx.toBits(Layout.getBaseClassOffset(BaseRD)); +} + enum CXRefQualifierKind clang_Type_getCXXRefQualifier(CXType T) { QualType QT = GetQualType(T); if (QT.isNull()) diff --git a/clang/tools/libclang/libclang.map b/clang/tools/libclang/libclang.map index 00ba56ab3c79d5..8ca8a58b76d9e0 100644 --- a/clang/tools/libclang/libclang.map +++ b/clang/tools/libclang/libclang.map @@ -436,8 +436,10 @@ LLVM_19 { LLVM_20 { global: + clang_getOffsetOfBase; clang_getTypePrettyPrinted; clang_isBeforeInTranslationUnit; + clang_visitCXXBaseClasses; }; # Example of how to add a new symbol version entry. If you do add a new symbol _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits