Skip to content

Conversation

homm
Copy link
Member

@homm homm commented Sep 1, 2024

Partially suppress #8340

The non-primary extensions in Pillow don't have direct access to Python's _imaging types, such as Imaging_Type and ImagingObject. However, they often still need to interact with objects passed from Python code. Currently, the raw memory pointer to an Imaging object (ImageCore.id) is passed as an integer and then cast to a memory pointer:

static PyObject *
cms_transform_apply(CmsTransformObject *self, PyObject *args) {
    Py_ssize_t idIn, idOut;
    Imaging im, imOut;

    if (!PyArg_ParseTuple(args, "nn:apply", &idIn, &idOut)) {
        return NULL;
    }

    im = (Imaging)idIn;
    imOut = (Imaging)idOut;

    return Py_BuildValue("i", pyCMSdoTransform(im, imOut, self->transform));
}

It's not safe and can lead to segmentation faults. Python has Capsule objects, which provide a safer way to pass pointers to C code. These are exposed through ImageCore.ptr.

This PR deprecates the raw pointer properties ImageCore.id and ImageCore.unsafe_ptrs, and migrates non-primary extensions to interact with Capsule objects.

@homm homm force-pushed the use-ptr branch 5 times, most recently from 1fddcce to 9bd6434 Compare September 2, 2024 01:00
homm added 2 commits September 2, 2024 05:14
PytestUnraisableExceptionWarning: Exception ignored in: <function PhotoImage.__del__>
AttributeError: 'PhotoImage' object has no attribute '_PhotoImage__photo'
@homm homm force-pushed the use-ptr branch 5 times, most recently from 830a245 to 491e637 Compare September 2, 2024 09:33
@homm
Copy link
Member Author

homm commented Sep 2, 2024

@python-pillow/pillow-team I need help here)

Could anyone debug pypy3.10 on windows? I can't get what's wrong, since as I can see, the capsule object is correctly restored from the pointer and the capsule object itself is alive.

https://github.com/python-pillow/Pillow/actions/runs/10664609198/job/29556218383?pr=8341#step:27:3342

@homm homm force-pushed the use-ptr branch 3 times, most recently from 10db853 to bc83e33 Compare September 2, 2024 10:53
@homm
Copy link
Member Author

homm commented Sep 2, 2024

Get it! In pypy repr(capsule) holds the capsuled pointer itself, not the pointer to the PyObject. And even hex(id(capsule)) is not the pointer to PyObject. Lucky, we can reliably distinguish pypy capsule from cpython capsule by absence of trailing "<>".

https://github.com/pypy/pypy/blob/branches/py3.10/pypy/objspace/std/capsuleobject.py#L39

@radarhere
Copy link
Member

So is the motivation here just to simplify the code?

Also, we don't necessarily need the deprecations - #4532 (comment)

Pillow makes a commitment to stable public interfaces, which are defined at the Python layer. The C interfaces are explicitly internal, and no effort is made to keep them stable even between minor releases, and no support or warning is given when they change. (IIRC, there are/were people using the internal interfaces, but they've always been explicitly on their own with that)

@homm
Copy link
Member Author

homm commented Sep 3, 2024

So is the motivation here just to simplify the code?

Added description to PR.

Also, we don't necessarily need the deprecations

While ImageCore is internal Pillow object, it's still Python API. I believe ImageCore.id could be used by third-party extensions (Github search) and can't be removed in a one day.

@homm
Copy link
Member Author

homm commented Sep 22, 2024

What's the advantage of using repr(capsule) in the first place? Why not just pass the capsule itself all the time?

I'm not aware of TK API, but it seems it converts any arguments to strings, at least PyImagingPhotoPut receives arguments as const char **argv. So looks like there is no way to pass the capsule object itself, instead repr(capsule) will be passed any way. I just made this conversion more explicit.

@radarhere
Copy link
Member

radarhere commented Sep 30, 2024

it seems it converts any arguments to strings, at least PyImagingPhotoPut receives arguments as const char **argv. So looks like there is no way to pass the capsule object itself, instead repr(capsule) will be passed any way. I just made this conversion more explicit.

In CPython, sure, but from your comments, not in PyPy.

It seems simpler to note that CPython converts the capsule to a string,
rather than converting to a string and noting that PyPy doesn't convert it?

Depends on whether your thinking was already aligned with how Tk works, I guess. I've created uploadcare#148, but feel free to say you prefer the current version.

@homm
Copy link
Member Author

homm commented Oct 1, 2024

but from your comments, not in PyPy.

That isn't in my comments. In both implementations, the Capsule is converted to a string. In the case of CPython, it's a string like <capsule object "Pillow Imaging" at 0xffff>, while in PyPy, it's a string like capsule object "Pillow Imaging" at 0xeeee. Where 0xffff is PyCapsule object address, and 0xeeee is the pointer stored in the capsule itself.

@radarhere
Copy link
Member

// Special case for PyPy, where the string representation of a Capsule
// refers directly to the pointer itself, not to the PyCapsule object.

Oh, I see, I very much misunderstood this comment.

@radarhere
Copy link
Member

radarhere commented Oct 3, 2024

Get it! In pypy repr(capsule) holds the capsuled pointer itself, not the pointer to the PyObject.

Interestingly, I guess this is what is preventing you from checking the capsule name against the magic with PyCapsule_IsValid() in ImagingFind().

if (strncmp(name, expected, strlen(expected))) { is has already performed the essence of that check by that point though, right?

@homm
Copy link
Member Author

homm commented Oct 3, 2024

This check is the best we can do here. It’s not as secure as PyCapsule_IsValid(), since it only verifies that the passed string is a correctly formatted string representation of the Capsule object. It’s still possible to craft a well-formatted string with the wrong pointer, which could result in a segmentation fault when calling a Tk operation. However, it’s still a significant improvement over the previous implementation, which simply obtained some number and assumed it was a pointer.

@radarhere
Copy link
Member

For the sake of interest, unsafe_ptrs was originally added in #476 for PyAccess (which was removed in #8182). id has been there since PIL.

I'm not aware of TK API, but it seems it converts any arguments to strings, at least PyImagingPhotoPut receives arguments as const char **argv.

I looked, and this would be mirroring https://www.tcl.tk/man/tcl8.6/TclLib/CrtCommand.htm

@hugovk hugovk merged commit 535bf23 into python-pillow:main Oct 7, 2024
2 of 3 checks passed
@homm homm deleted the use-ptr branch October 7, 2024 11:50
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

Successfully merging this pull request may close these issues.

3 participants