Repository: cython/cython

Issue:
[BUG] null-deref inside _PyObject_GC_UNTRACK from extention class dealloc method. 
### Describe the bug

```
SCARINESS: 10 (null-deref)
    #0 0x53c9a5 in _PyObject_GC_UNTRACK(_object*) (.__uniq.71160431842585912442991593982683040670) python/3.10/Include/internal/pycore_object.h:123
    #1 0x53c9a5 in BaseException_dealloc(PyBaseExceptionObject*) (.__uniq.71160431842585912442991593982683040670) python/3.10/Objects/exceptions.c:92
    #2 0x7fb1f0bcfea7 in __pyx_tp_dealloc_6thrift_3py3_10exceptions_GeneratedError(_object*) exceptions.cpp:20592 *cython*
    #3 0x7fb1f4369b4f in __pyx_tp_dealloc_7testing_5types_UnusedError(_object*) types.cpp:165249  *cython* 
    #4 0x3d3658 in _Py_Dealloc python/3.10/Objects/object.c:2301
```

I believe this is related to this commit https://github.com/cython/cython/commit/663c924462adec202c9f469fa346bda1b2264dfa from #5432 

The generated .cpp code between the old version of cython and 3.0.8 is missing these lines
```
#if CYTHON_USE_TYPE_SLOTS
if (PyType_IS_GC(Py_TYPE(o)->tp_base))
#endif
PyObject_GC_Track(o);
 ```
 
 The missing GC_Track would explain why GC is exploding  

### Code to reproduce the behaviour:

```cython
# We have no custom __dealloc__ or __del__
# Exception is the builtin python Exception
# inside the exceptions.pxd we have this def for Exception
cdef extern from "Python.h":
    ctypedef extern class builtins.Exception[object PyBaseExceptionObject]:
        pass


# inside the exceptions.pyx
cdef class Error(Exception):
   # Has no slots
    ...

cdef class GeneratedError(Error):
  # has some attribute slots
   ...
```





### Expected behaviour

no crash

### OS

Linux

### Python version

3.10.9

### Cython version

3.0.8

### Additional context

_No response_


Discussion:

Resolving patch:
diff --git a/Cython/Compiler/ModuleNode.py b/Cython/Compiler/ModuleNode.py
--- a/Cython/Compiler/ModuleNode.py
+++ b/Cython/Compiler/ModuleNode.py
@@ -1761,22 +1761,24 @@ def generate_dealloc_function(self, scope, code):
 
         if base_type:
             base_cname = base_type.typeptr_cname
-            if needs_gc:
-                # The base class deallocator probably expects this to be tracked,
-                # so undo the untracking above.
-                if base_type.scope:
-                    # Assume that we know whether the base class uses GC or not.
-                    if base_type.scope.needs_gc():
-                        code.putln("PyObject_GC_Track(o);")
-                else:
-                    code.putln("if (PyType_IS_GC(%s)) PyObject_GC_Track(o);" % base_cname)
-
             tp_dealloc = TypeSlots.get_base_slot_function(scope, tp_slot)
             if tp_dealloc is not None:
+                if needs_gc and base_type.scope and base_type.scope.needs_gc():
+                    # We know that the base class uses GC, so probably expects it to be tracked.
+                    # Undo the untracking above.
+                    code.putln("PyObject_GC_Track(o);")
                 code.putln("%s(o);" % tp_dealloc)
             elif base_type.is_builtin_type:
+                if needs_gc and base_type.scope and base_type.scope.needs_gc():
+                    # We know that the base class uses GC, so probably expects it to be tracked.
+                    # Undo the untracking above.
+                    code.putln("PyObject_GC_Track(o);")
                 code.putln("__Pyx_PyType_GetSlot(%s, tp_dealloc, destructor)(o);" % base_cname)
             else:
+                if needs_gc:
+                    # We don't know if the base class uses GC or not, so must find out at runtime
+                    # whether we should undo the untracking above or not.
+                    code.putln("if (PyType_IS_GC(%s)) PyObject_GC_Track(o);" % base_cname)
                 # This is an externally defined type.  Calling through the
                 # cimported base type pointer directly interacts badly with
                 # the module cleanup, which may already have cleared it.


Test patch:
diff --git a/tests/run/exttype_gc.pyx b/tests/run/exttype_gc.pyx
--- a/tests/run/exttype_gc.pyx
+++ b/tests/run/exttype_gc.pyx
@@ -36,3 +36,26 @@ cdef class ExtTypeNoGC(BaseTypeNoGC):
     >>> create_obj(PyExtTypeNoGC)
     """
     cdef int x
+
+
+cdef extern from "Python.h":
+    ctypedef extern class builtins.Exception[object PyBaseExceptionObject]:
+        pass
+
+
+cdef class Error(Exception):
+    pass
+
+
+cdef class GeneratedError(Error):
+    """
+    >>> create_obj(GeneratedError)                
+    >>> create_obj(GeneratedError)                
+    >>> create_obj(GeneratedError)                
+                                               
+    >>> class PyGeneratedError(GeneratedError): pass 
+    >>> create_obj(PyGeneratedError)              
+    >>> create_obj(PyGeneratedError)              
+    >>> create_obj(PyGeneratedError)              
+    """
+    cdef object attr