From 6d5d6aed2e38e1abc625f29c0b3e97fc8c60ae3b Mon Sep 17 00:00:00 2001
From: Stefan Behnel <stefan_ml@behnel.de>
Date: Wed, 28 Aug 2024 11:16:55 +0200
Subject: [PATCH] Avoid custom "tp_new()" call and add a safe-guard that
 element lookups actually return a type.

---
 src/lxml/etree.pyx             | 12 +++++-------
 src/lxml/includes/etree_defs.h |  8 ++------
 src/lxml/python.pxd            |  1 -
 3 files changed, 7 insertions(+), 14 deletions(-)

diff --git a/src/lxml/etree.pyx b/src/lxml/etree.pyx
index c21d1343..90579af9 100644
--- a/src/lxml/etree.pyx
+++ b/src/lxml/etree.pyx
@@ -1636,11 +1636,6 @@ cdef public class _Element [ type LxmlElementType, object LxmlElement ]:
         return CSSSelector(expr, translator=translator)(self)
 
 
-cdef extern from "includes/etree_defs.h":
-    # macro call to 't->tp_new()' for fast instantiation
-    cdef object NEW_ELEMENT "PY_NEW" (object t)
-
-
 @cython.linetrace(False)
 cdef _Element _elementFactory(_Document doc, xmlNode* c_node):
     cdef _Element result
@@ -1650,12 +1645,15 @@ cdef _Element _elementFactory(_Document doc, xmlNode* c_node):
     if c_node is NULL:
         return None
 
-    element_class = LOOKUP_ELEMENT_CLASS(
+    element_class = <type> LOOKUP_ELEMENT_CLASS(
         ELEMENT_CLASS_LOOKUP_STATE, doc, c_node)
+    if type(element_class) is not type:
+        if not isinstance(element_class, type):
+            raise TypeError(f"Element class is not a type, got {type(element_class)}")
     if hasProxy(c_node):
         # prevent re-entry race condition - we just called into Python
         return getProxy(c_node)
-    result = NEW_ELEMENT(element_class)
+    result = element_class.__new__(element_class)
     if hasProxy(c_node):
         # prevent re-entry race condition - we just called into Python
         result._c_node = NULL
diff --git a/src/lxml/includes/etree_defs.h b/src/lxml/includes/etree_defs.h
index 17d470d0..8645869f 100644
--- a/src/lxml/includes/etree_defs.h
+++ b/src/lxml/includes/etree_defs.h
@@ -177,7 +177,7 @@ long _ftol2( double dblSource ) { return _ftol( dblSource ); }
 
 #ifdef __GNUC__
 /* Test for GCC > 2.95 */
-#if __GNUC__ > 2 || (__GNUC__ == 2 && (__GNUC_MINOR__ > 95)) 
+#if __GNUC__ > 2 || (__GNUC__ == 2 && (__GNUC_MINOR__ > 95))
 #define unlikely_condition(x) __builtin_expect((x), 0)
 #else /* __GNUC__ > 2 ... */
 #define unlikely_condition(x) (x)
@@ -190,10 +190,6 @@ long _ftol2( double dblSource ) { return _ftol( dblSource ); }
   #define Py_TYPE(ob)   (((PyObject*)(ob))->ob_type)
 #endif
 
-#define PY_NEW(T) \
-     (((PyTypeObject*)(T))->tp_new( \
-             (PyTypeObject*)(T), __pyx_empty_tuple, NULL))
-
 #define _fqtypename(o)  ((Py_TYPE(o))->tp_name)
 
 #define lxml_malloc(count, item_size) \
@@ -268,7 +264,7 @@ static void* lxml_unpack_xmldoc_capsule(PyObject* capsule, int* is_owned) {
  * 'inclusive' is 1).  The _ELEMENT_ variants will only stop on nodes
  * that match _isElement(), the normal variant will stop on every node
  * except text nodes.
- * 
+ *
  * To traverse the node and all of its children and siblings in Pyrex, call
  *    cdef xmlNode* some_node
  *    BEGIN_FOR_EACH_ELEMENT_FROM(some_node.parent, some_node, 1)
diff --git a/src/lxml/python.pxd b/src/lxml/python.pxd
index d0877355..e0ec762e 100644
--- a/src/lxml/python.pxd
+++ b/src/lxml/python.pxd
@@ -131,7 +131,6 @@ cdef extern from "includes/etree_defs.h": # redefines some functions as macros
     cdef void* lxml_unpack_xmldoc_capsule(object capsule, bint* is_owned) except? NULL
     cdef bint _isString(object obj)
     cdef const_char* _fqtypename(object t)
-    cdef object PY_NEW(object t)
     cdef bint IS_PYPY
     cdef object PyOS_FSPath(object obj)
 
