[clang] [libclang] Add API to query more information about base classes. (PR #120300)
Eli Friedman via cfe-commits
cfe-commits at lists.llvm.org
Thu Dec 19 13:21:11 PST 2024
https://github.com/efriedma-quic updated https://github.com/llvm/llvm-project/pull/120300
>From 448537876f4377686847120a923dd9d443f7d556 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 | 5 +++
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, 147 insertions(+)
diff --git a/clang/bindings/python/clang/cindex.py b/clang/bindings/python/clang/cindex.py
index f8a20a1e224724..a790a09f0a05c6 100644
--- a/clang/bindings/python/clang/cindex.py
+++ b/clang/bindings/python/clang/cindex.py
@@ -2123,6 +2123,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 if the record is anonymous.
@@ -2663,6 +2671,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
@@ -3844,6 +3867,7 @@ def write_main_file_to_stdout(self):
("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),
@@ -3895,6 +3919,7 @@ def write_main_file_to_stdout(self):
[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 ce05fdb1a1ebc0..d0319d89f012cc 100644
--- a/clang/bindings/python/tests/cindex/test_type.py
+++ b/clang/bindings/python/tests/cindex/test_type.py
@@ -514,3 +514,28 @@ class Template {
# Variable without a template argument.
cursor = get_cursor(tu, "bar")
self.assertEqual(cursor.get_num_template_arguments(), -1)
+
+ 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 b2231bb4584aae..f5f867974b4951 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -784,6 +784,10 @@ libclang
--------
- Add ``clang_isBeforeInTranslationUnit``. Given two source locations, it determines
whether the first one comes strictly before the second in the source code.
+- 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
---------------
@@ -883,6 +887,7 @@ Sanitizers
Python Binding Changes
----------------------
- Fixed an issue that led to crashes when calling ``Type.get_exception_specification_kind``.
+- Added binding for ``clang_isVirtualBase``, which checks whether a base class is virtual.
OpenMP Support
--------------
diff --git a/clang/include/clang-c/Index.h b/clang/include/clang-c/Index.h
index 0c5ac80772e2b9..6ee4043efc9112 100644
--- a/clang/include/clang-c/Index.h
+++ b/clang/include/clang-c/Index.h
@@ -3731,6 +3731,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.
@@ -6600,6 +6606,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 ea6f97d39644e1..16f82c9bdb29a5 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_or_null<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 b4df12405cf356..da04e041bee455 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"
@@ -1094,6 +1095,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 25d8ba57d32514..6696c67bd262a7 100644
--- a/clang/tools/libclang/libclang.map
+++ b/clang/tools/libclang/libclang.map
@@ -436,7 +436,9 @@ LLVM_19 {
LLVM_20 {
global:
+ clang_getOffsetOfBase;
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