[Mlir-commits] [mlir] WIP: [mlir:python] Fix crash in `from_python` on multiple overloads (PR #191764)

Ingo Müller llvmlistbot at llvm.org
Sun Apr 12 23:49:30 PDT 2026


https://github.com/ingomueller-net created https://github.com/llvm/llvm-project/pull/191764

None

>From 94ee44adaeb60d102caa846a631008223583195b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ingo=20M=C3=BCller?= <ingomueller at google.com>
Date: Mon, 13 Apr 2026 08:45:27 +0200
Subject: [PATCH] WIP: [mlir:python] Fix crash in `from_python` on multiple
 overloads
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Ingo Müller <ingomueller at google.com>
---
 .../mlir/Bindings/Python/NanobindAdaptors.h   | 78 +++++++++++++++----
 ...nobind_adaptors_from_python_dirty_error.py | 36 +++++++++
 .../python/lib/PythonTestModuleNanobind.cpp   | 28 +++++++
 3 files changed, 129 insertions(+), 13 deletions(-)
 create mode 100644 mlir/test/python/ir/nanobind_adaptors_from_python_dirty_error.py

diff --git a/mlir/include/mlir/Bindings/Python/NanobindAdaptors.h b/mlir/include/mlir/Bindings/Python/NanobindAdaptors.h
index 6669433550a00..e51240adb5414 100644
--- a/mlir/include/mlir/Bindings/Python/NanobindAdaptors.h
+++ b/mlir/include/mlir/Bindings/Python/NanobindAdaptors.h
@@ -85,7 +85,11 @@ struct type_caster<MlirAffineMap> {
   bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     if (auto capsule = mlirApiObjectToCapsule(src)) {
       value = mlirPythonCapsuleToAffineMap(capsule->ptr());
-      return !mlirAffineMapIsNull(value);
+      if (mlirAffineMapIsNull(value)) {
+        PyErr_Clear();
+        return false;
+      }
+      return true;
     }
     return false;
   }
@@ -108,7 +112,11 @@ struct type_caster<MlirAttribute> {
   bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     if (auto capsule = mlirApiObjectToCapsule(src)) {
       value = mlirPythonCapsuleToAttribute(capsule->ptr());
-      return !mlirAttributeIsNull(value);
+      if (mlirAttributeIsNull(value)) {
+        PyErr_Clear();
+        return false;
+      }
+      return true;
     }
     return false;
   }
@@ -131,7 +139,11 @@ struct type_caster<MlirBlock> {
   bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     if (auto capsule = mlirApiObjectToCapsule(src)) {
       value = mlirPythonCapsuleToBlock(capsule->ptr());
-      return !mlirBlockIsNull(value);
+      if (mlirBlockIsNull(value)) {
+        PyErr_Clear();
+        return false;
+      }
+      return true;
     }
     return false;
   }
@@ -159,7 +171,11 @@ struct type_caster<MlirContext> {
     }
     if (std::optional<nanobind::object> capsule = mlirApiObjectToCapsule(src)) {
       value = mlirPythonCapsuleToContext(capsule->ptr());
-      return !mlirContextIsNull(value);
+      if (mlirContextIsNull(value)) {
+        PyErr_Clear();
+        return false;
+      }
+      return true;
     }
     return false;
   }
@@ -173,7 +189,11 @@ struct type_caster<MlirDialectRegistry> {
   bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     if (auto capsule = mlirApiObjectToCapsule(src)) {
       value = mlirPythonCapsuleToDialectRegistry(capsule->ptr());
-      return !mlirDialectRegistryIsNull(value);
+      if (mlirDialectRegistryIsNull(value)) {
+        PyErr_Clear();
+        return false;
+      }
+      return true;
     }
     return false;
   }
@@ -200,7 +220,11 @@ struct type_caster<MlirLocation> {
     }
     if (auto capsule = mlirApiObjectToCapsule(src)) {
       value = mlirPythonCapsuleToLocation(capsule->ptr());
-      return !mlirLocationIsNull(value);
+      if (mlirLocationIsNull(value)) {
+        PyErr_Clear();
+        return false;
+      }
+      return true;
     }
     return false;
   }
@@ -222,7 +246,11 @@ struct type_caster<MlirModule> {
   bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     if (auto capsule = mlirApiObjectToCapsule(src)) {
       value = mlirPythonCapsuleToModule(capsule->ptr());
-      return !mlirModuleIsNull(value);
+      if (mlirModuleIsNull(value)) {
+        PyErr_Clear();
+        return false;
+      }
+      return true;
     }
     return false;
   }
@@ -246,7 +274,11 @@ struct type_caster<MlirFrozenRewritePatternSet> {
   bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     if (auto capsule = mlirApiObjectToCapsule(src)) {
       value = mlirPythonCapsuleToFrozenRewritePatternSet(capsule->ptr());
-      return value.ptr != nullptr;
+      if (value.ptr == nullptr) {
+        PyErr_Clear();
+        return false;
+      }
+      return true;
     }
     return false;
   }
@@ -269,7 +301,11 @@ struct type_caster<MlirOperation> {
   bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     if (auto capsule = mlirApiObjectToCapsule(src)) {
       value = mlirPythonCapsuleToOperation(capsule->ptr());
-      return !mlirOperationIsNull(value);
+      if (mlirOperationIsNull(value)) {
+        PyErr_Clear();
+        return false;
+      }
+      return true;
     }
     return false;
   }
@@ -293,7 +329,11 @@ struct type_caster<MlirValue> {
   bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     if (auto capsule = mlirApiObjectToCapsule(src)) {
       value = mlirPythonCapsuleToValue(capsule->ptr());
-      return !mlirValueIsNull(value);
+      if (mlirValueIsNull(value)) {
+        PyErr_Clear();
+        return false;
+      }
+      return true;
     }
     return false;
   }
@@ -319,7 +359,11 @@ struct type_caster<MlirPassManager> {
   bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     if (auto capsule = mlirApiObjectToCapsule(src)) {
       value = mlirPythonCapsuleToPassManager(capsule->ptr());
-      return !mlirPassManagerIsNull(value);
+      if (mlirPassManagerIsNull(value)) {
+        PyErr_Clear();
+        return false;
+      }
+      return true;
     }
     return false;
   }
@@ -332,7 +376,11 @@ struct type_caster<MlirTypeID> {
   bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     if (auto capsule = mlirApiObjectToCapsule(src)) {
       value = mlirPythonCapsuleToTypeID(capsule->ptr());
-      return !mlirTypeIDIsNull(value);
+      if (mlirTypeIDIsNull(value)) {
+        PyErr_Clear();
+        return false;
+      }
+      return true;
     }
     return false;
   }
@@ -356,7 +404,11 @@ struct type_caster<MlirType> {
   bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     if (auto capsule = mlirApiObjectToCapsule(src)) {
       value = mlirPythonCapsuleToType(capsule->ptr());
-      return !mlirTypeIsNull(value);
+      if (mlirTypeIsNull(value)) {
+        PyErr_Clear();
+        return false;
+      }
+      return true;
     }
     return false;
   }
diff --git a/mlir/test/python/ir/nanobind_adaptors_from_python_dirty_error.py b/mlir/test/python/ir/nanobind_adaptors_from_python_dirty_error.py
new file mode 100644
index 0000000000000..19eccd5478b9a
--- /dev/null
+++ b/mlir/test/python/ir/nanobind_adaptors_from_python_dirty_error.py
@@ -0,0 +1,36 @@
+# RUN: %PYTHON %s 2>&1 | FileCheck %s
+#
+# Regression test for the dirty-error-state crash in NanobindAdaptors.h
+# from_python type casters (CPython 3.13).
+#
+# Uses an overloaded function: overload 1 takes MlirOperation, overload 2
+# takes MlirModule.  When called with an ir.Module:
+#
+#   1. nanobind tries overload 1 (MlirOperation).  from_python gets the
+#      Module's _CAPIPtr capsule, then mlirPythonCapsuleToOperation calls
+#      PyCapsule_GetPointer with "mlir.ir.Operation._CAPIPtr" — but the
+#      capsule is named "mlir.ir.Module._CAPIPtr".  PyCapsule_GetPointer
+#      returns NULL and sets PyErr_Occurred().  from_python returns false.
+#
+#   2. nanobind tries overload 2 (MlirModule).  from_python calls
+#      mlirApiObjectToCapsule → nanobind::getattr(obj, "_CAPIPtr") →
+#      _PyType_LookupRef.
+#
+# Without the fix:
+#   _PyType_LookupRef asserts !PyErr_Occurred() → SIGABRT (CPython 3.13).
+#
+# With the fix (PyErr_Clear in from_python after failed capsule conversion):
+#   Overload 2 succeeds and returns "module".
+
+from mlir import ir
+from mlir._mlir_libs import _mlirPythonTestNanobind as test_ext
+
+with ir.Context():
+    module = ir.Module.parse("module {}")
+
+    # CHECK: result = module
+    result = test_ext.take_module_or_operation(module)
+    print(f"result = {result}")
+
+# CHECK: PASS
+print("PASS")
diff --git a/mlir/test/python/lib/PythonTestModuleNanobind.cpp b/mlir/test/python/lib/PythonTestModuleNanobind.cpp
index e9754749352b1..4a794872c9a26 100644
--- a/mlir/test/python/lib/PythonTestModuleNanobind.cpp
+++ b/mlir/test/python/lib/PythonTestModuleNanobind.cpp
@@ -147,6 +147,34 @@ NB_MODULE(_mlirPythonTestNanobind, m) {
       },
       nb::arg("context").none() = nb::none());
 
+  // Reproducer for the CPython 3.13 dirty-error-state crash in
+  // NanobindAdaptors.h::from_python type casters.
+  //
+  // Two overloads of the same function: one takes MlirOperation, the other
+  // takes MlirModule.  When called with an ir.Module:
+  //
+  //   1. nanobind tries overload 1 (MlirOperation).  from_python calls
+  //      mlirApiObjectToCapsule (succeeds — Module has _CAPIPtr), then
+  //      mlirPythonCapsuleToOperation, whose PyCapsule_GetPointer fails on
+  //      the capsule-name mismatch and sets PyErr_Occurred().
+  //      from_python returns false.
+  //
+  //   Without fix: PyErr is still set.
+  //   2. nanobind tries overload 2 (MlirModule).  from_python calls
+  //      mlirApiObjectToCapsule → nanobind::getattr(obj, "_CAPIPtr") →
+  //      CPython's _PyType_LookupRef → assert(!PyErr_Occurred()) → SIGABRT.
+  //
+  //   With fix (PyErr_Clear in from_python after failed capsule conversion):
+  //   2. PyErr is clear → overload 2 succeeds → returns "module".
+  m.def(
+      "take_module_or_operation",
+      [](MlirOperation) { return std::string("operation"); },
+      nb::arg("arg"));
+  m.def(
+      "take_module_or_operation",
+      [](MlirModule) { return std::string("module"); },
+      nb::arg("arg"));
+
   using namespace python_test;
   PyTestAttr::bind(m);
   PyTestType::bind(m);



More information about the Mlir-commits mailing list