[clang] [libclang] Add API to query more information about base classes. (PR #120300)

Eli Friedman via cfe-commits cfe-commits at lists.llvm.org
Fri Jan 10 15:24:03 PST 2025


https://github.com/efriedma-quic updated https://github.com/llvm/llvm-project/pull/120300

>From 0d4bcf69c57457ba79af9069ae60c6de8ee45498 Mon Sep 17 00:00:00 2001
From: Eli Friedman <efriedma at 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                 | 29 ++++++++++++++++
 clang/tools/libclang/CIndexCXX.cpp            | 27 +++++++++++++++
 clang/tools/libclang/CXType.cpp               | 34 +++++++++++++++++++
 clang/tools/libclang/libclang.map             |  2 ++
 7 files changed, 152 insertions(+)

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..8fef22de5667f3 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..776b342c4ddc67 100644
--- a/clang/include/clang-c/Index.h
+++ b/clang/include/clang-c/Index.h
@@ -3771,6 +3771,12 @@ 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.
+ */
+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 +6654,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



More information about the cfe-commits mailing list