Skip to content

Commit 1682dee

Browse files
[libclang] Add API to query more information about base classes. (#120300)
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 computes the offset of a field. Also, add a Python binding for the existing function clang_isVirtualBase.
1 parent 6a214ec commit 1682dee

File tree

7 files changed

+157
-2
lines changed

7 files changed

+157
-2
lines changed

clang/bindings/python/clang/cindex.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2133,6 +2133,14 @@ def get_field_offsetof(self):
21332133
"""Returns the offsetof the FIELD_DECL pointed by this Cursor."""
21342134
return conf.lib.clang_Cursor_getOffsetOfField(self) # type: ignore [no-any-return]
21352135

2136+
def get_base_offsetof(self, parent):
2137+
"""Returns the offsetof the CXX_BASE_SPECIFIER pointed by this Cursor."""
2138+
return conf.lib.clang_getOffsetOfBase(parent, self) # type: ignore [no-any-return]
2139+
2140+
def is_virtual_base(self):
2141+
"""Returns whether the CXX_BASE_SPECIFIER pointed by this Cursor is virtual."""
2142+
return conf.lib.clang_isVirtualBase(self) # type: ignore [no-any-return]
2143+
21362144
def is_anonymous(self):
21372145
"""
21382146
Check whether this is a record type without a name, or a field where
@@ -2687,6 +2695,21 @@ def visitor(field, children):
26872695
conf.lib.clang_Type_visitFields(self, fields_visit_callback(visitor), fields)
26882696
return iter(fields)
26892697

2698+
def get_bases(self):
2699+
"""Return an iterator for accessing the base classes of this type."""
2700+
2701+
def visitor(base, children):
2702+
assert base != conf.lib.clang_getNullCursor()
2703+
2704+
# Create reference to TU so it isn't GC'd before Cursor.
2705+
base._tu = self._tu
2706+
bases.append(base)
2707+
return 1 # continue
2708+
2709+
bases: list[Cursor] = []
2710+
conf.lib.clang_visitCXXBaseClasses(self, fields_visit_callback(visitor), bases)
2711+
return iter(bases)
2712+
26902713
def get_exception_specification_kind(self):
26912714
"""
26922715
Return the kind of the exception specification; a value from
@@ -3940,6 +3963,7 @@ def set_property(self, property, value):
39403963
("clang_getNumDiagnosticsInSet", [c_object_p], c_uint),
39413964
("clang_getNumElements", [Type], c_longlong),
39423965
("clang_getNumOverloadedDecls", [Cursor], c_uint),
3966+
("clang_getOffsetOfBase", [Cursor, Cursor], c_longlong),
39433967
("clang_getOverloadedDecl", [Cursor, c_uint], Cursor),
39443968
("clang_getPointeeType", [Type], Type),
39453969
("clang_getRange", [SourceLocation, SourceLocation], SourceRange),
@@ -3992,6 +4016,7 @@ def set_property(self, property, value):
39924016
[TranslationUnit, SourceRange, POINTER(POINTER(Token)), POINTER(c_uint)],
39934017
),
39944018
("clang_visitChildren", [Cursor, cursor_visit_callback, py_object], c_uint),
4019+
("clang_visitCXXBaseClasses", [Type, fields_visit_callback, py_object], c_uint),
39954020
("clang_Cursor_getNumArguments", [Cursor], c_int),
39964021
("clang_Cursor_getArgument", [Cursor, c_uint], Cursor),
39974022
("clang_Cursor_getNumTemplateArguments", [Cursor], c_int),

clang/bindings/python/tests/cindex/test_type.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,3 +534,28 @@ def test_pretty(self):
534534
self.assertEqual(f.type.get_canonical().pretty_printed(pp), "X")
535535
pp.set_property(PrintingPolicyProperty.SuppressTagKeyword, False)
536536
self.assertEqual(f.type.get_canonical().pretty_printed(pp), "struct X")
537+
538+
def test_base_classes(self):
539+
source = """
540+
class A { int a; };
541+
class B { int b; };
542+
class C { int c; };
543+
template <typename T>
544+
class Template : public A, public B, virtual C {
545+
};
546+
Template<int> instance;
547+
int bar;
548+
"""
549+
tu = get_tu(source, lang="cpp")
550+
cursor = get_cursor(tu, "instance")
551+
cursor_type = cursor.type
552+
cursor_type_decl = cursor_type.get_declaration()
553+
self.assertEqual(cursor.kind, CursorKind.VAR_DECL)
554+
bases = list(cursor_type.get_bases())
555+
self.assertEqual(len(bases), 3)
556+
self.assertFalse(bases[0].is_virtual_base())
557+
self.assertEqual(bases[0].get_base_offsetof(cursor_type_decl), 64)
558+
self.assertFalse(bases[1].is_virtual_base())
559+
self.assertEqual(bases[1].get_base_offsetof(cursor_type_decl), 96)
560+
self.assertTrue(bases[2].is_virtual_base())
561+
self.assertEqual(bases[2].get_base_offsetof(cursor_type_decl), 128)

clang/docs/ReleaseNotes.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1224,6 +1224,10 @@ libclang
12241224
whether the first one comes strictly before the second in the source code.
12251225
- Add ``clang_getTypePrettyPrinted``. It allows controlling the PrintingPolicy used
12261226
to pretty-print a type.
1227+
- Added ``clang_visitCXXBaseClasses``, which allows visiting the base classes
1228+
of a class.
1229+
- Added ``clang_getOffsetOfBase``, which allows computing the offset of a base
1230+
class in a class's layout.
12271231

12281232
Static Analyzer
12291233
---------------
@@ -1371,6 +1375,12 @@ Python Binding Changes
13711375
declaration is an anonymous union or anonymous struct.
13721376
- Added ``Type.pretty_printed`, a binding for ``clang_getTypePrettyPrinted``,
13731377
which allows changing the formatting of pretty-printed types.
1378+
- Added ``Cursor.is_virtual_base``, a binding for ``clang_isVirtualBase``,
1379+
which checks whether a base class is virtual.
1380+
- Added ``Type.get_bases``, a binding for ``clang_visitCXXBaseClasses``, which
1381+
allows visiting the base classes of a class.
1382+
- Added ``Cursor.get_base_offsetof``, a binding for ``clang_getOffsetOfBase``,
1383+
which allows computing the offset of a base class in a class's layout.
13741384

13751385
OpenMP Support
13761386
--------------

clang/include/clang-c/Index.h

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3605,8 +3605,8 @@ CINDEX_LINKAGE enum CXTypeNullabilityKind clang_Type_getNullability(CXType T);
36053605

36063606
/**
36073607
* List the possible error codes for \c clang_Type_getSizeOf,
3608-
* \c clang_Type_getAlignOf, \c clang_Type_getOffsetOf and
3609-
* \c clang_Cursor_getOffsetOf.
3608+
* \c clang_Type_getAlignOf, \c clang_Type_getOffsetOf,
3609+
* \c clang_Cursor_getOffsetOf, and \c clang_getOffsetOfBase.
36103610
*
36113611
* A value of this enumeration type can be returned if the target type is not
36123612
* a valid argument to sizeof, alignof or offsetof.
@@ -3771,6 +3771,15 @@ CINDEX_LINKAGE enum CXRefQualifierKind clang_Type_getCXXRefQualifier(CXType T);
37713771
*/
37723772
CINDEX_LINKAGE unsigned clang_isVirtualBase(CXCursor);
37733773

3774+
/**
3775+
* Returns the offset in bits of a CX_CXXBaseSpecifier relative to the parent
3776+
* class.
3777+
*
3778+
* Returns a small negative number if the offset cannot be computed. See
3779+
* CXTypeLayoutError for error codes.
3780+
*/
3781+
CINDEX_LINKAGE long long clang_getOffsetOfBase(CXCursor Parent, CXCursor Base);
3782+
37743783
/**
37753784
* Represents the C++ access control level to a base class for a
37763785
* cursor with kind CX_CXXBaseSpecifier.
@@ -6648,6 +6657,29 @@ typedef enum CXVisitorResult (*CXFieldVisitor)(CXCursor C,
66486657
CINDEX_LINKAGE unsigned clang_Type_visitFields(CXType T, CXFieldVisitor visitor,
66496658
CXClientData client_data);
66506659

6660+
/**
6661+
* Visit the base classes of a type.
6662+
*
6663+
* This function visits all the direct base classes of a the given cursor,
6664+
* invoking the given \p visitor function with the cursors of each
6665+
* visited base. The traversal may be ended prematurely, if
6666+
* the visitor returns \c CXFieldVisit_Break.
6667+
*
6668+
* \param T the record type whose field may be visited.
6669+
*
6670+
* \param visitor the visitor function that will be invoked for each
6671+
* field of \p T.
6672+
*
6673+
* \param client_data pointer data supplied by the client, which will
6674+
* be passed to the visitor each time it is invoked.
6675+
*
6676+
* \returns a non-zero value if the traversal was terminated
6677+
* prematurely by the visitor returning \c CXFieldVisit_Break.
6678+
*/
6679+
CINDEX_LINKAGE unsigned clang_visitCXXBaseClasses(CXType T,
6680+
CXFieldVisitor visitor,
6681+
CXClientData client_data);
6682+
66516683
/**
66526684
* Describes the kind of binary operators.
66536685
*/

clang/tools/libclang/CIndexCXX.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,33 @@ unsigned clang_isVirtualBase(CXCursor C) {
2727
return B->isVirtual();
2828
}
2929

30+
unsigned clang_visitCXXBaseClasses(CXType PT, CXFieldVisitor visitor,
31+
CXClientData client_data) {
32+
CXCursor PC = clang_getTypeDeclaration(PT);
33+
if (clang_isInvalid(PC.kind))
34+
return false;
35+
const CXXRecordDecl *RD =
36+
dyn_cast_if_present<CXXRecordDecl>(cxcursor::getCursorDecl(PC));
37+
if (!RD || RD->isInvalidDecl())
38+
return false;
39+
RD = RD->getDefinition();
40+
if (!RD || RD->isInvalidDecl())
41+
return false;
42+
43+
for (auto &Base : RD->bases()) {
44+
// Callback to the client.
45+
switch (
46+
visitor(cxcursor::MakeCursorCXXBaseSpecifier(&Base, getCursorTU(PC)),
47+
client_data)) {
48+
case CXVisit_Break:
49+
return true;
50+
case CXVisit_Continue:
51+
break;
52+
}
53+
}
54+
return true;
55+
}
56+
3057
enum CX_CXXAccessSpecifier clang_getCXXAccessSpecifier(CXCursor C) {
3158
AccessSpecifier spec = AS_none;
3259

clang/tools/libclang/CXType.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "clang/AST/DeclObjC.h"
2020
#include "clang/AST/DeclTemplate.h"
2121
#include "clang/AST/Expr.h"
22+
#include "clang/AST/RecordLayout.h"
2223
#include "clang/AST/Type.h"
2324
#include "clang/Basic/AddressSpaces.h"
2425
#include "clang/Frontend/ASTUnit.h"
@@ -1108,6 +1109,39 @@ long long clang_Cursor_getOffsetOfField(CXCursor C) {
11081109
return -1;
11091110
}
11101111

1112+
long long clang_getOffsetOfBase(CXCursor Parent, CXCursor Base) {
1113+
if (Base.kind != CXCursor_CXXBaseSpecifier)
1114+
return -1;
1115+
1116+
if (!clang_isDeclaration(Parent.kind))
1117+
return -1;
1118+
1119+
// we need to validate the parent type
1120+
CXType PT = clang_getCursorType(Parent);
1121+
long long Error = validateFieldParentType(Parent, PT);
1122+
if (Error < 0)
1123+
return Error;
1124+
1125+
const CXXRecordDecl *ParentRD =
1126+
dyn_cast<CXXRecordDecl>(cxcursor::getCursorDecl(Parent));
1127+
if (!ParentRD)
1128+
return -1;
1129+
1130+
ASTContext &Ctx = cxcursor::getCursorContext(Base);
1131+
const CXXBaseSpecifier *B = cxcursor::getCursorCXXBaseSpecifier(Base);
1132+
if (ParentRD->bases_begin() > B || ParentRD->bases_end() <= B)
1133+
return -1;
1134+
1135+
const CXXRecordDecl *BaseRD = B->getType()->getAsCXXRecordDecl();
1136+
if (!BaseRD)
1137+
return -1;
1138+
1139+
const ASTRecordLayout &Layout = Ctx.getASTRecordLayout(ParentRD);
1140+
if (B->isVirtual())
1141+
return Ctx.toBits(Layout.getVBaseClassOffset(BaseRD));
1142+
return Ctx.toBits(Layout.getBaseClassOffset(BaseRD));
1143+
}
1144+
11111145
enum CXRefQualifierKind clang_Type_getCXXRefQualifier(CXType T) {
11121146
QualType QT = GetQualType(T);
11131147
if (QT.isNull())

clang/tools/libclang/libclang.map

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,8 +436,10 @@ LLVM_19 {
436436

437437
LLVM_20 {
438438
global:
439+
clang_getOffsetOfBase;
439440
clang_getTypePrettyPrinted;
440441
clang_isBeforeInTranslationUnit;
442+
clang_visitCXXBaseClasses;
441443
};
442444

443445
# Example of how to add a new symbol version entry. If you do add a new symbol

0 commit comments

Comments
 (0)