From 524ad9a3322a14584b3ddec4d93131d111bb9747 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Fri, 28 Aug 2026 17:56:32 +0200 Subject: [PATCH] [cpyrt] Use GetActualClass in cast_actual cast_actual kept the static class, so comparing two proxies of the same object through different base subobjects compared unequal addresses. Reachable when a proxy is not downcast on creation, e.g. for a pinned type. Also drops a stray '&' from the ostream operand name used for the operator<< lookup, and fixes two comment typos. --- src/cpyrt/CPPInstance.cxx | 7 +++---- src/cpyrt/CPPMethod.cxx | 2 +- test/test_pythonify.py | 29 +++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/cpyrt/CPPInstance.cxx b/src/cpyrt/CPPInstance.cxx index 59cde8a..425c654 100644 --- a/src/cpyrt/CPPInstance.cxx +++ b/src/cpyrt/CPPInstance.cxx @@ -281,7 +281,7 @@ static PyObject* op_destruct(CPPInstance* self) { //= cpyrt object dispatch support ========================================= static PyObject* op_dispatch(PyObject* self, PyObject* args, - PyObject* /* kdws */) { + PyObject* /* kwds */) { // User-side __dispatch__ method to allow selection of a specific overloaded // method. The actual selection is in the __overload__() method of // CPPOverload. @@ -554,8 +554,7 @@ static inline void* cast_actual(void* obj) { return address; interop::TCppScope_t klass = ((CPPClass*)Py_TYPE((PyObject*)obj))->fCppType; - interop::TCppScope_t clActual = - klass /* XXX: interop::GetActualClass(klass, address) */; + interop::TCppScope_t clActual = interop::GetActualClass(klass, address); if (clActual && clActual != klass) { intptr_t offset = interop::GetBaseOffset( clActual, klass, address, -1 /* down-cast */, true /* report errors */); @@ -827,7 +826,7 @@ static PyObject* op_str(CPPInstance* self) { interop::TCppScope_t rnsID = interop::GetScope(TypeManip::extract_namespace(rcname)); PyCallable* pyfunc = - Utility::FindBinaryOperator("std::ostream&", rcname, "<<", rnsID); + Utility::FindBinaryOperator("std::ostream", rcname, "<<", rnsID); if (!pyfunc) continue; diff --git a/src/cpyrt/CPPMethod.cxx b/src/cpyrt/CPPMethod.cxx index f70212f..e8cccc5 100644 --- a/src/cpyrt/CPPMethod.cxx +++ b/src/cpyrt/CPPMethod.cxx @@ -297,7 +297,7 @@ void cpyrt::CPPMethod::SetPyError_(PyObject* msg) { // C++ method to give some context. // 2. A C++ exception has occured: // Augment the exception message with the docstring of this method - // 3. A Python exception has occured with a traceback: + // 3. A Python exception has occurred with a traceback: // Do nothing, Python exceptions are already informative enough // 4. If the Python exception has no traceback hinting to an internally set // error stack, diff --git a/test/test_pythonify.py b/test/test_pythonify.py index 4bdf282..47c9825 100644 --- a/test/test_pythonify.py +++ b/test/test_pythonify.py @@ -617,3 +617,32 @@ def test03_write_access_to_globals(self): assert proxy.__get__(proxy, None) == 3 cppjit.gbl.ns_example01.gMyGlobalInt = oldval + + +class TestPINNEDCOMPARISON: + def test01_pinned_base_compares_equal(self): + """Comparison downcasts to the actual class before comparing addresses""" + + import cppjit + from cppjit._pythonization import pin_type + + cppjit.cppdef("""\ + namespace PinnedCmp { + struct B1 { virtual ~B1() {} int a = 1; }; + struct B2 { virtual ~B2() {} int b = 2; }; + struct D : B1, B2 {}; + D g_d; + D* get_d() { return &g_d; } + B2* get_b2() { return static_cast(&g_d); } + }""") + + ns = cppjit.gbl.PinnedCmp + + # pinning keeps the B2 proxy from being downcast on creation, so the + # B2 subobject address is what reaches the comparison + pin_type(ns.B2) + d, b2 = ns.get_d(), ns.get_b2() + assert type(b2).__cpp_name__ == "PinnedCmp::B2" + + assert d == b2 + assert not (d != b2)