Inspect arg as Python object, instead of using PyErr_Clear() - #9726
Conversation
| if (!PyArg_ParseTuple(args, "sO", &mode_name, &matrix)) { | ||
| return NULL; | ||
| } | ||
| if (PyTuple_Size(matrix) == 12) { |
There was a problem hiding this comment.
This fails with lists:
from PIL import Image
rgb = Image.new("RGB", (8, 8), (255, 0, 0))
def transparent(t):
rgb.info["transparency"] = t
return rgb.convert("RGBA")
cases = {
"matrix tuple(4)": lambda: rgb.convert("L", matrix=(0.3, 0.59, 0.11, 0.0)),
"matrix list(4)": lambda: rgb.convert("L", matrix=[0.3, 0.59, 0.11, 0.0]),
"matrix tuple(12)": lambda: rgb.convert("RGB", matrix=tuple(range(12))),
"matrix list(12)": lambda: rgb.convert("RGB", matrix=list(range(12))),
"transparency tuple": lambda: transparent((255, 0, 0)),
"transparency list": lambda: transparent([255, 0, 0]),
}
for label, fn in cases.items():
try:
fn()
print(f"OK {label}")
except Exception as e:
print(f"RAISE {label}: {type(e).__name__}: {e}")Before:
OK matrix tuple(4)
OK matrix list(4)
OK matrix tuple(12)
OK matrix list(12)
OK transparency tuple
OK transparency list
After:
OK matrix tuple(4)
RAISE matrix list(4): SystemError: <method 'convert_matrix' of 'ImagingCore' objects> returned a result with an exception set
OK matrix tuple(12)
RAISE matrix list(12): SystemError: /Users/nad/build_macos_installer/installer/variant/binaries/build_source/Objects/tupleobject.c:96: bad argument to internal function
OK transparency tuple
RAISE transparency list: TypeError: 'list' object cannot be interpreted as an integer
Maybe worth adding a test?
| if (PyTuple_Size(matrix) == 12) { | |
| if (PySequence_Size(matrix) == 12) { |
There was a problem hiding this comment.
Should according to type hint and docstring, but not enforced.
There is existing code using lists:
https://github.com/search?q=%2F%5C.convert%5C%28.*%2C+matrix%3D%5C%5B%2F&type=code
Do we want to change the behaviour in this PR?
There was a problem hiding this comment.
Ok, I pushed a commit to allow for lists.
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
Merging this PR will not alter performance
Comparing Footnotes
|
| PyObject *matrix; | ||
| if (!PyArg_ParseTuple(args, "sO", &mode_name, &matrix)) { | ||
| return NULL; | ||
| } | ||
| Py_ssize_t size = PySequence_Size(matrix); |
There was a problem hiding this comment.
Review note to self: it's OK to not decref matrix, since "O" in ParseTuple doesn't create a strong reference.
| } else if (!PyArg_ParseTuple( | ||
| args, "s(ffff)", &mode_name, m + 0, m + 1, m + 2, m + 3 | ||
| )) { | ||
| return NULL; |
There was a problem hiding this comment.
With this, passing in a sequence of any other length than 4 or 12 always yields
argument 2 must be tuple of length 4, not 5
which is a little confusing (since 12 is valid too):
>>> i.convert("L", matrix=(5,5,5,5,5))
Traceback (most recent call last):
File "<python-input-9>", line 1, in <module>
i.convert("L", matrix=(5,5,5,5,5))
~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
File "PIL/Image.py", line 1084, in convert
im = self.im.convert_matrix(mode, matrix)
TypeError: argument 2 must be tuple of length 4, not 5Could just do
if (!(size == 4 || size == 12)) {
return PyErr_Format(PyExc_TypeError, "matrix must be a sequence of 4 or 12 floats");
}above instead of the size == -1 check without much extra ado?
There was a problem hiding this comment.
From what I see, main also behaves that way. Not saying we can't do this idea, just seems to be a bit of scope creep for this PR.
There was a problem hiding this comment.
It does.
The PR description doesn't really tell the reader why the preimage PyErr_Clear() approach was inadvisable, or why this change needs to be done in the first place. In that light, I assume the intent could be "Improve error handling in convert and convert_transparent", in which case improving errors to be more descriptive altogether (in the spirit of #9784) wouldn't be amiss?
| im = hopper("RGB") | ||
| im = hopper() | ||
| assert im.mode == "RGB" |
There was a problem hiding this comment.
Wouldn't im = hopper("RGB") be clear enough about this Hopper's mode?
There was a problem hiding this comment.
Ok, I've pushed a commit.
| if (PySequence_Check(transparency)) { | ||
| if (!PyArg_ParseTuple(args, "s(iii)", &mode_name, &r, &g, &b)) { | ||
| return NULL; | ||
| } | ||
| } else if (!PyArg_ParseTuple(args, "si", &mode_name, &r)) { | ||
| return NULL; | ||
| } | ||
| return NULL; |
There was a problem hiding this comment.
The same comment as above about confusing error messages stands here (the allowed values are either a single integer, or a 3-sequence of integers), but it's much harder to reach from userland, since you'd either need to call i.im.convert_transparent() by hand, or have explicitly set some image's .info["transparency"] to an invalid value. Might not be worth addressing.
>>> i.im.convert_transparent("RGBA", "hello")
Traceback (most recent call last):
File "<python-input-22>", line 1, in <module>
i.im.convert_transparent("RGBA", "hello")
~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
TypeError: argument 2 must be 3-item tuple, not str
>>> i.im.convert_transparent("RGBA", 123)
<ImagingCore object at 0x1034fca50>
>>> i.im.convert_transparent("RGBA", [1,2,4])
<ImagingCore object at 0x1034ffeb0>
In
_convert_transparent(), instead of failing a call toPyArg_ParseTuple(), callingPyErr_Clear()afterwards and then trying again,Pillow/src/_imaging.c
Lines 1084 to 1089 in 095bbc3
an alternative strategy would be to parse the second argument as a PyObject ("O") and then check if it is a tuple or not.
A similar change could be made in
_convert_matrix().