@@ -1555,6 +1555,18 @@ class Cursor(Structure):
15551555
15561556 _tu : TranslationUnit
15571557
1558+ # This ensures that no operations are possible on null cursors
1559+ # by guarding all method calls with a not-null assert
1560+ def __getattribute__ (self , key : str ) -> object :
1561+ value = super ().__getattribute__ (key )
1562+ is_property = isinstance (getattr (Cursor , key , None ), property )
1563+ # Don't guard the is_null method, since it is part of the guard
1564+ # and leads to infinite recursion otherwise
1565+ if is_property or callable (value ):
1566+ if key != "is_null" and self .is_null ():
1567+ raise Exception ("Tried calling method on a null-cursor." )
1568+ return value
1569+
15581570 @staticmethod
15591571 def from_location (tu : TranslationUnit , location : SourceLocation ) -> Cursor :
15601572 # We store a reference to the TU in the instance so the TU won't get
@@ -1575,6 +1587,9 @@ def __ne__(self, other: object) -> bool:
15751587 def __hash__ (self ) -> int :
15761588 return self .hash
15771589
1590+ def is_null (self ) -> bool :
1591+ return self == conf .null_cursor
1592+
15781593 def is_definition (self ) -> bool :
15791594 """
15801595 Returns true if the declaration pointed at by the cursor is also a
@@ -2124,8 +2139,7 @@ def get_children(self) -> Iterator[Cursor]:
21242139 # FIXME: Expose iteration from CIndex, PR6125.
21252140 def visitor (child : Cursor , _ : Cursor , children : list [Cursor ]) -> int :
21262141 # FIXME: Document this assertion in API.
2127- # FIXME: There should just be an isNull method.
2128- assert child != conf .lib .clang_getNullCursor ()
2142+ assert not child .is_null ()
21292143
21302144 # Create reference to TU so it isn't GC'd before Cursor.
21312145 child ._tu = self ._tu
@@ -2210,7 +2224,7 @@ def has_attrs(self) -> bool:
22102224 def from_result (res : Cursor , arg : Cursor | TranslationUnit | Type ) -> Cursor | None :
22112225 assert isinstance (res , Cursor )
22122226 # FIXME: There should just be an isNull method.
2213- if res == conf . lib . clang_getNullCursor ():
2227+ if res . is_null ():
22142228 return None
22152229
22162230 # Store a reference to the TU in the Python object so it won't get GC'd
@@ -2229,7 +2243,7 @@ def from_result(res: Cursor, arg: Cursor | TranslationUnit | Type) -> Cursor | N
22292243 @staticmethod
22302244 def from_cursor_result (res : Cursor , arg : Cursor ) -> Cursor | None :
22312245 assert isinstance (res , Cursor )
2232- if res == conf . lib . clang_getNullCursor ():
2246+ if res . is_null ():
22332247 return None
22342248
22352249 res ._tu = arg ._tu
@@ -2728,7 +2742,7 @@ def get_fields(self):
27282742 """Return an iterator for accessing the fields of this type."""
27292743
27302744 def visitor (field , children ):
2731- assert field != conf . lib . clang_getNullCursor ()
2745+ assert not field . is_null ()
27322746
27332747 # Create reference to TU so it isn't GC'd before Cursor.
27342748 field ._tu = self ._tu
@@ -2743,7 +2757,7 @@ def get_bases(self):
27432757 """Return an iterator for accessing the base classes of this type."""
27442758
27452759 def visitor (base , children ):
2746- assert base != conf . lib . clang_getNullCursor ()
2760+ assert not base . is_null ()
27472761
27482762 # Create reference to TU so it isn't GC'd before Cursor.
27492763 base ._tu = self ._tu
@@ -2758,7 +2772,7 @@ def get_methods(self):
27582772 """Return an iterator for accessing the methods of this type."""
27592773
27602774 def visitor (method , children ):
2761- assert method != conf . lib . clang_getNullCursor ()
2775+ assert not method . is_null ()
27622776
27632777 # Create reference to TU so it isn't GC'd before Cursor.
27642778 method ._tu = self ._tu
@@ -4228,6 +4242,7 @@ def set_compatibility_check(check_status: bool) -> None:
42284242 def lib (self ) -> CDLL :
42294243 lib = self .get_cindex_library ()
42304244 register_functions (lib , not Config .compatibility_check )
4245+ self .null_cursor = lib .clang_getNullCursor ()
42314246 Config .loaded = True
42324247 return lib
42334248
0 commit comments