Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Regression in passing buffers through void * with Python 3.13 #272

Open
guitargeek opened this issue Nov 4, 2024 · 9 comments
Open

Regression in passing buffers through void * with Python 3.13 #272

guitargeek opened this issue Nov 4, 2024 · 9 comments

Comments

@guitargeek
Copy link
Contributor

Reproducer:

import array
import cppyy

cppyy.cppdef("""

double* pass_arr(double* a) {
   return a;
}
double* pass_void_arr(void* a) {
   return (double*)a;
}

""");

b = array.array('d', range(6))

# Works!
ca = cppyy.gbl.pass_arr(b)
for i in range(len(b)):
    print(ca[i])

# Crashes!
ca = cppyy.gbl.pass_void_arr(b)
for i in range(len(b)):
    print(ca[i])

I investigated the problem a bit, and it turns out the IsCTypesArrayOrPointer() is giving a false positive, resulting in the wrong code path being taken:
https://github.com/wlav/CPyCppyy/blob/master/src/Converters.cxx#L1510

@wlav, do you have any idea on how to improve this function? I don't understand its implementation, unfortunately. How does relate comparing the type of the extended dictionary (Py_TYPE(pytype->tp_dict)) into figuring out whether it's a ctype or not? Is the PyTYPE of a dictionary not always a dictionary anyway, therefore resulting in the false positive?

guitargeek added a commit to guitargeek/root that referenced this issue Nov 4, 2024
The `IsCTypesArrayOrPointer` gives false positives in Python 3.13,
resulting the void pointer converter to take the wrong code path and
crash. See:
wlav/cppyy#272

This code path is used for implicit conversion from other `ctypes`
pointer types to `void*`, which is not strictly required. One can
always do an explicit cast:  `ctypes.cast(my_ptr, ctypes.c_void_p )`.

Given that this a niche feature that broke Python 3.13 support for
functions taking `void*`, which is quite common, it can be argued that
it's better to remove this implicit conversion.

This commit fixes the following tests under Python 3.13:

```
roottest-python-basic-datatype
roottest-python-cpp-cpp
```

This reverts the following commit from upstream:
wlav/CPyCppyy@80a0205
guitargeek added a commit to guitargeek/root that referenced this issue Nov 5, 2024
The `IsCTypesArrayOrPointer` gives false positives in Python 3.13,
resulting the void pointer converter to take the wrong code path and
crash. See:
wlav/cppyy#272

This code path is used for implicit conversion from other `ctypes`
pointer types to `void*`, which is not strictly required. One can
always do an explicit cast:  `ctypes.cast(my_ptr, ctypes.c_void_p )`.

Given that this a niche feature that broke Python 3.13 support for
functions taking `void*`, which is quite common, it can be argued that
it's better to remove this implicit conversion.

This commit fixes the following tests under Python 3.13:

```
roottest-python-basic-datatype
roottest-python-cpp-cpp
```

This reverts the following commit from upstream:
wlav/CPyCppyy@80a0205
guitargeek added a commit to guitargeek/root that referenced this issue Nov 5, 2024
The `IsCTypesArrayOrPointer` gives false positives in Python 3.13,
resulting the void pointer converter to take the wrong code path and
crash. See:
wlav/cppyy#272

This code path is used for implicit conversion from other `ctypes`
pointer types to `void*`, which is not strictly required. One can
always do an explicit cast:  `ctypes.cast(my_ptr, ctypes.c_void_p )`.

Given that this a niche feature that broke Python 3.13 support for
functions taking `void*`, which is quite common, it can be argued that
it's better to remove this implicit conversion.

This commit fixes the following tests under Python 3.13:

```
roottest-python-basic-datatype
roottest-python-cpp-cpp
```

This reverts the following commit from upstream:
wlav/CPyCppyy@80a0205
guitargeek added a commit to root-project/root that referenced this issue Nov 5, 2024
The `IsCTypesArrayOrPointer` gives false positives in Python 3.13,
resulting the void pointer converter to take the wrong code path and
crash. See:
wlav/cppyy#272

This code path is used for implicit conversion from other `ctypes`
pointer types to `void*`, which is not strictly required. One can
always do an explicit cast:  `ctypes.cast(my_ptr, ctypes.c_void_p )`.

Given that this a niche feature that broke Python 3.13 support for
functions taking `void*`, which is quite common, it can be argued that
it's better to remove this implicit conversion.

This commit fixes the following tests under Python 3.13:

```
roottest-python-basic-datatype
roottest-python-cpp-cpp
```

This reverts the following commit from upstream:
wlav/CPyCppyy@80a0205
guitargeek added a commit to guitargeek/root that referenced this issue Nov 5, 2024
The `IsCTypesArrayOrPointer` gives false positives in Python 3.13,
resulting the void pointer converter to take the wrong code path and
crash. See:
wlav/cppyy#272

This code path is used for implicit conversion from other `ctypes`
pointer types to `void*`, which is not strictly required. One can
always do an explicit cast:  `ctypes.cast(my_ptr, ctypes.c_void_p )`.

Given that this a niche feature that broke Python 3.13 support for
functions taking `void*`, which is quite common, it can be argued that
it's better to remove this implicit conversion.

This commit fixes the following tests under Python 3.13:

```
roottest-python-basic-datatype
roottest-python-cpp-cpp
```

This reverts the following commit from upstream:
wlav/CPyCppyy@80a0205
guitargeek added a commit to root-project/root that referenced this issue Nov 5, 2024
The `IsCTypesArrayOrPointer` gives false positives in Python 3.13,
resulting the void pointer converter to take the wrong code path and
crash. See:
wlav/cppyy#272

This code path is used for implicit conversion from other `ctypes`
pointer types to `void*`, which is not strictly required. One can
always do an explicit cast:  `ctypes.cast(my_ptr, ctypes.c_void_p )`.

Given that this a niche feature that broke Python 3.13 support for
functions taking `void*`, which is quite common, it can be argued that
it's better to remove this implicit conversion.

This commit fixes the following tests under Python 3.13:

```
roottest-python-basic-datatype
roottest-python-cpp-cpp
```

This reverts the following commit from upstream:
wlav/CPyCppyy@80a0205
@wlav
Copy link
Owner

wlav commented Nov 5, 2024

The reason why it worked was that ctypes' types have had a custom dictionary type (StgDictObject) since their introduction in py2.5 and ctypes code has been pretty much left untouched until, apparently, now in py3.13. (Looks like the custom info is now stored directly in the type object.) Checking the custom dictionary provided a single test for all ctypes types that was convenient and fast.

From the py3.13 code, the proper way of checking would now be a PyObject_IsInstance for the (newly introduced) PyCType_Type common type base class.

@guitargeek
Copy link
Contributor Author

Ah ok I suspected something like that! Great that you found the PyCType_Type, I missed that when looking around the Python docs yesterday.

@wlav
Copy link
Owner

wlav commented Nov 5, 2024

It's not in the docs (I'm sure; haven't actually looked) b/c ctypes does not have a public C API.

@wlav
Copy link
Owner

wlav commented Nov 5, 2024

It's actually not used as a type base class, but as a meta type and their doesn't seem to be any hierarchy of meta types. Ie., a simple test w/o needing to copy/expose any functions from ctypes.h would be to compare the type of the type of any ctypes type (no isinstance() needed).

@wlav
Copy link
Owner

wlav commented Nov 5, 2024

Looks like there was more to it than just convenience: the dict-checking trick also covered pointer types which are created at runtime and which are not part (as such) of ctypes. The metaclass-checking as proposed does not cover them.

@guitargeek guitargeek changed the title Regressoin in passing buffers through void * with Python 3.13 Regression in passing buffers through void * with Python 3.13 Nov 7, 2024
@wlav
Copy link
Owner

wlav commented Nov 7, 2024

Should work now. I found no better way than to copy the state definition. I'd rather pick the metatype from one of the existing types (as that would not involve code copying), but the modules (and types) associated with pointers are those where and from what they are created at run-time, making that unreliable. (Note that even for getting the state, the relevant module is _ctypes, not ctypes.)

@guitargeek
Copy link
Contributor Author

Thank you so much for you updates!

With my previous hotfixes, we could already add Fedora 41 with Python 3.13 in the CI. I'm checking now if everything works with your solution:

@wlav
Copy link
Owner

wlav commented Nov 7, 2024

I'm down to 2 leak checks failing on py3.13 that don't under other pythons; then 4 more tests that are failing b/c of Clang 16 (3 related to not keeping int8_t as a typedef and 1 to lambdas; both work only on my end due to changes in ROOT/meta, so probably never worked for ROOT main anyway).

@guitargeek
Copy link
Contributor Author

Good to hear! On our side, I don't see any regressions anymore due to Python 3.13 for whatever test coverage we have :) So we're shifting the effort again towards general PyROOT improvements. Especially for the memory regulator, that regular comes to bite us (see root-project/root#16837). We also discussed with Vassil at the last Tuesday meeting that we have to do a general revamp of how ROOT is initialized, since it's too implicit.

guitargeek added a commit to root-project/root that referenced this issue Nov 8, 2024
guitargeek added a commit to guitargeek/root that referenced this issue Nov 8, 2024
guitargeek added a commit to root-project/root that referenced this issue Nov 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants