Skip to content

Commit

Permalink
[libclang] Add API to query more information about base classes. (#12…
Browse files Browse the repository at this point in the history
…0300)

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.
  • Loading branch information
efriedma-quic authored Jan 14, 2025
1 parent 6a214ec commit 1682dee
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 2 deletions.
25 changes: 25 additions & 0 deletions clang/bindings/python/clang/cindex.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down
25 changes: 25 additions & 0 deletions clang/bindings/python/tests/cindex/test_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
10 changes: 10 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1224,6 +1224,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
---------------
Expand Down Expand Up @@ -1371,6 +1375,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
--------------
Expand Down
36 changes: 34 additions & 2 deletions clang/include/clang-c/Index.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
*/
Expand Down
27 changes: 27 additions & 0 deletions clang/tools/libclang/CIndexCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
34 changes: 34 additions & 0 deletions clang/tools/libclang/CXType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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())
Expand Down
2 changes: 2 additions & 0 deletions clang/tools/libclang/libclang.map
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 1682dee

Please sign in to comment.