-
Notifications
You must be signed in to change notification settings - Fork 12.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[libclang] Add API to query more information about base classes. #120300
Conversation
@llvm/pr-subscribers-clang Author: Eli Friedman (efriedma-quic) ChangesThe 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. Full diff: https://github.com/llvm/llvm-project/pull/120300.diff 6 Files Affected:
diff --git a/clang/bindings/python/clang/cindex.py b/clang/bindings/python/clang/cindex.py
index f8a20a1e224724..6e26eca9ce0a01 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(field, children):
+ assert field != conf.lib.clang_getNullCursor()
+
+ # Create reference to TU so it isn't GC'd before Cursor.
+ field._tu = self._tu
+ fields.append(field)
+ return 1 # continue
+
+ fields: list[Cursor] = []
+ conf.lib.clang_visitCXXBaseClasses(self, fields_visit_callback(visitor), fields)
+ return iter(fields)
+
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..76497bdcf9f43f 100644
--- a/clang/bindings/python/tests/cindex/test_type.py
+++ b/clang/bindings/python/tests/cindex/test_type.py
@@ -1,4 +1,5 @@
import os
+import clang.cindex
from clang.cindex import Config, CursorKind, RefQualifierKind, TranslationUnit, TypeKind
@@ -514,3 +515,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/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..09e6a5f8d3a33b 100644
--- a/clang/tools/libclang/libclang.map
+++ b/clang/tools/libclang/libclang.map
@@ -437,6 +437,8 @@ LLVM_19 {
LLVM_20 {
global:
clang_isBeforeInTranslationUnit;
+ clang_getOffsetOfBase;
+ clang_visitCXXBaseClasses;
};
# Example of how to add a new symbol version entry. If you do add a new symbol
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm only familiar with the Python-side of the bindings, so only reviewed those. Minor comments, otherwise LGTM!
dd61e9f
to
4485378
Compare
Just referencing #121502 since this PR seem somewhat related (making it easier to iterate over things and pointed out it doesn't work for template instantiations, which might be the underlying problem I encountered). I'd like to keep using libclang (and its Python bindings), so if there is a new API that could be added to libclang or a modification somewhere that is obvious, let me know and I'll see if I can make an attempt at it. |
For what it's worth, to get around my issue in #121502 I followed your same pattern here and added a Looking at CXXRecordDecl, perhaps the following might all be useful to add to the libclang API (including Python):
For my purposes of parsing C++ headers and generating Python bindings via pybind11, this bases PR plus adding the Adding I'll open a PR for the |
I think the relevant code that prevents visiting template instantiations is CursorVisitor::VisitClassTemplateSpecializationDecl. It intentionally skips implicit template instantiations, I guess because of the origin of the API, which was focused around code written by the user.
It might be a little weird to see uninstantiated methods in some cases... but this probably makes sense.
get_fields() already exists. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No idea what is going on with the python, but only 1 possible nit in the C++
clang/tools/libclang/CIndexCXX.cpp
Outdated
if (clang_isInvalid(PC.kind)) | ||
return false; | ||
const CXXRecordDecl *RD = | ||
dyn_cast_or_null<CXXRecordDecl>(cxcursor::getCursorDecl(PC)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
are we supposed to be using the if_present
versions or not?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, the or_null
variants are deprecated in favor of the is_present
ones.
clang/tools/libclang/CIndexCXX.cpp
Outdated
if (clang_isInvalid(PC.kind)) | ||
return false; | ||
const CXXRecordDecl *RD = | ||
dyn_cast_or_null<CXXRecordDecl>(cxcursor::getCursorDecl(PC)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, the or_null
variants are deprecated in favor of the is_present
ones.
0d4bcf6
to
ff0f4b5
Compare
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.
ff0f4b5
to
12ad982
Compare
Addressed review comments |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re Python: LGTM!
On a sidenote just fyi, there's no need to amend & forcepush when you adress review comments. I know the contributor guide says something about how you should have only a single commit when submitting a patch, but that goes only for the initial thing (I got confused by this at the start). We squash on merge anyways.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
LLVM Buildbot has detected a new failure on builder Full details are available at: https://lab.llvm.org/buildbot/#/builders/154/builds/10273 Here is the relevant piece of the build log for the reference
|
LLVM Buildbot has detected a new failure on builder Full details are available at: https://lab.llvm.org/buildbot/#/builders/73/builds/11714 Here is the relevant piece of the build log for the reference
|
The 32-bit Arm failures are fixed by 7aec7ca . The other failures appear to be unrelated. |
Inspired by #120300, add a new API `clang_visitCXXMethods` to libclang (and the Python bindings) which allows iterating over the class methods of a type. --------- Co-authored-by: Vlad Serebrennikov <serebrennikov.vladislav@gmail.com> Co-authored-by: Aaron Ballman <aaron@aaronballman.com>
…3539) Inspired by llvm/llvm-project#120300, add a new API `clang_visitCXXMethods` to libclang (and the Python bindings) which allows iterating over the class methods of a type. --------- Co-authored-by: Vlad Serebrennikov <serebrennikov.vladislav@gmail.com> Co-authored-by: Aaron Ballman <aaron@aaronballman.com>
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.