Skip to content

Commit bb81d35

Browse files
[PyROOT] Fix hash collision in TemplateProxy dispatch (Issue #20526)
Distinct CPPOverload objects (e.g:- different global functions) previously generated identical hash signatures because HashSignature relied solely on Py_TYPE and Py_REFCNT. This caused the TemplateProxy to retrieve incorrect cached instantiations. This patch adds object identity (pointer address) to the hash calculation for CPPOverload and TemplateProxy types using golden ratio bit mixing. Fixes #20526.
1 parent c591107 commit bb81d35

File tree

1 file changed

+18
-5
lines changed

1 file changed

+18
-5
lines changed

bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.h

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "PyCallable.h"
66

77
// Standard
8+
#include <cstdint>
89
#include <map>
910
#include <string>
1011
#include <utility>
@@ -14,22 +15,34 @@
1415
namespace CPyCppyy {
1516

1617
// signature hashes are also used by TemplateProxy
18+
// Forward declare for use in HashSignature before class definition
19+
CPYCPPYY_IMPORT PyTypeObject CPPOverload_Type;
20+
CPYCPPYY_IMPORT PyTypeObject TemplateProxy_Type;
21+
1722
inline uint64_t HashSignature(CPyCppyy_PyArgs_t args, size_t nargsf)
1823
{
1924
// Build a hash from the types of the given python function arguments.
2025
uint64_t hash = 0;
2126

2227
Py_ssize_t nargs = CPyCppyy_PyArgs_GET_SIZE(args, nargsf);
2328
for (Py_ssize_t i = 0; i < nargs; ++i) {
24-
// TODO: hashing in the ref-count is for moves; resolve this together with the
25-
// improved overloads for implicit conversions
2629
PyObject* pyobj = CPyCppyy_PyArgs_GET_ITEM(args, i);
27-
hash += (uint64_t)Py_TYPE(pyobj);
30+
31+
// For CPPOverload and TemplateProxy, mix in object identity (pointer address)
32+
// to distinguish different C++ callables with same Python type
33+
if (Py_TYPE(pyobj) == &CPPOverload_Type || Py_TYPE(pyobj) == &TemplateProxy_Type) {
34+
// Use golden ratio mixing: shift by 3 (pointers are 8-byte aligned),
35+
// then apply proper bit mixing with golden ratio constant
36+
hash ^= ((uint64_t)(uintptr_t)pyobj >> 3) + 0x9e3779b9ULL + (hash << 6) + (hash >> 2);
37+
} else {
38+
// Standard type-based hashing for other objects
39+
hash += (uint64_t)Py_TYPE(pyobj);
2840
#if PY_VERSION_HEX >= 0x030e0000
29-
hash += (uint64_t)(PyUnstable_Object_IsUniqueReferencedTemporary(pyobj) ? 1 : 0);
41+
hash += (uint64_t)(PyUnstable_Object_IsUniqueReferencedTemporary(pyobj) ? 1 : 0);
3042
#else
31-
hash += (uint64_t)(Py_REFCNT(pyobj) == 1 ? 1 : 0);
43+
hash += (uint64_t)(Py_REFCNT(pyobj) == 1 ? 1 : 0);
3244
#endif
45+
}
3346
hash += (hash << 10); hash ^= (hash >> 6);
3447
}
3548

0 commit comments

Comments
 (0)