-
- Notifications
You must be signed in to change notification settings - Fork 32.3k
gh-107545: Fix misleading setsockopt error message #107546
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
base: main
Are you sure you want to change the base?
Conversation
Most changes to Python require a NEWS entry. Please add it using the blurb_it web app or the blurb command-line tool. |
I choose to solve the issue using a white-list approach rather then a black-list approach since there might be more errors that Error message for: import socket with socket.socket() as s: s.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 2 ** 31) is now |
Looks good, how about adding some tests for new error messages. |
Misc/NEWS.d/next/Core and Builtins/2023-08-01-22-30-35.gh-issue-107545.f9i3pQ.rst Outdated Show resolved Hide resolved
I added the relevant tests. Any new review comments? |
Considering this is your first PR in cpython, please be patient! The next step is waiting for the review by core devs. Generally, core developers don't have enough time to review every PR, so you can see there are many PRs that can't be merged into the main. Before they come to review, we just need to keep our PR compliant and wait :) |
f712b65
to 6439014
Compare 6439014
to 167d212
Compare Since this PR is old, I validated that the issue is still reproducible. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, but I think that we can get rid of _testcapi
.
The more user friendly approach is to parse arguments as "iiOO|O"
and then use == Py_None
, PyIndex_Check()
and PyBuffer_Check()
for the third argument. This will allow to generate better error messages like "... should be integer, bytes-like object or None".
Lib/test/test_socket.py Outdated
@unittest.skipIf(_testcapi is None, "requires _testcapi") | ||
def test_setsockopt_errors(self): | ||
# See issue #107546. | ||
from _testcapi import INT_MAX, INT_MIN |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could just use some large values like 2**1000
and -2**1000
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm scared by big numbers, 2**100
and -2**100
should be enough to trigger OverflowError on all platforms :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But not on platforms with 128-bit int
. I often use 2**1000
as more future proof option.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does Python support any platform with 128-bit integer? Does such platform exist?
Thanks for the feedback. |
Modules/socketmodule.c Outdated
@@ -3338,26 +3338,32 @@ sock_setsockopt(PyObject *self, PyObject *args) | |||
Py_buffer optval; | |||
int flag; | |||
unsigned int optlen; | |||
PyObject *none; | |||
PyObject *type; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Semantically, this is not type, but value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I choose the name optval
for all cases which access the value of the option.
Modules/socketmodule.c Outdated
PyObject *none; | ||
PyObject *type; | ||
| ||
if (!PyArg_ParseTuple(args, "iiO|I:setsockopt", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fourth argument is only accepted if the third argument is None. One way to verify this is to use the "O" converter with a default value of NULL and check that it is still NULL in all cases except when the third argument is None.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ended up using a different approach, since I didn't wanted the code to break in the future if someone will add another overload and forget to check that the value is NULL.
So, I first check that the code receives either 3 or 4 arguments with the appropriate types while allowing a 4th argument only if the 3rd is None.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this would work too. The minor downside of this approach is that you needs to explicitly raise an error message for wrong number of arguments.
You can also use a single PyArg_ParseTuple()
with format "iiO|I:setsockopt"
and then check the number of arguments:
if (arglen == 3 && optval == Py_None) { // error } if (arglen == 4 && optval != Py_None) { // error }
Bot note that only integers are currently supported for AF_VSOCK (I think we will change this, but in a separate issue).
Lib/test/test_socket.py Outdated
with self.assertRaises(OverflowError): | ||
sock.setsockopt(2 ** 100, socket.SO_REUSEADDR, 1) | ||
| ||
with self.assertRaises(TypeError): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can test the error message as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good Idea, I also created some more type checks and added their exact message to the tests code.
Thank you for your update, @naweiss. Here is the next portion of comments. |
I noticed this too. We need to look at the history of the code. Anyway, this is a different issue. |
if (!PyArg_ParseTuple(args, "iiO:setsockopt", | ||
&level, &optname, &optval)) { | ||
return NULL; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please move this test for None and 4 arguments here?
if (optval == Py_None) { PyErr_Format(...) }
} | ||
break; | ||
case 4: | ||
if (!PyArg_ParseTuple(args, "iiO!I:setsockopt", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (!PyArg_ParseTuple(args, "iiO!I:setsockopt", | |
if (!PyArg_ParseTuple(args, "iiOI:setsockopt", |
@@ -3374,36 +3397,45 @@ sock_setsockopt(PyObject *self, PyObject *args) | |||
goto done; | |||
} | |||
| |||
PyErr_Clear(); | |||
/* setsockopt(level, opt, None, flag) */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/* setsockopt(level, opt, None, flag) */ | |
/* setsockopt(level, opt, None, optlen) */ |
res = setsockopt(get_sock_fd(s), level, optname, | ||
buffer.buf, (int)buffer.len); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
res = setsockopt(get_sock_fd(s), level, optname, | |
buffer.buf, (int)buffer.len); | |
res = setsockopt(get_sock_fd(s), level, optname, | |
buffer.buf, (int)buffer.len); |
&level, &optname, Py_TYPE(Py_None), &optval, &optlen)) { | ||
return NULL; | ||
} | ||
break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need if (optval != Py_None) {error...}
here.
sock.setsockopt(2 ** 100, socket.SO_REUSEADDR, 1) | ||
| ||
msg = "socket option should be should be integer, bytes-like object or None" | ||
with self.assertRaises(TypeError, msg=msg): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be assertRaisesRegex
. And the expected error message is a regular expression, so you need to escape parentheses.
Modules/socketmodule.c Outdated
PyObject *none; | ||
PyObject *type; | ||
| ||
if (!PyArg_ParseTuple(args, "iiO|I:setsockopt", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this would work too. The minor downside of this approach is that you needs to explicitly raise an error message for wrong number of arguments.
You can also use a single PyArg_ParseTuple()
with format "iiO|I:setsockopt"
and then check the number of arguments:
if (arglen == 3 && optval == Py_None) { // error } if (arglen == 4 && optval != Py_None) { // error }
Bot note that only integers are currently supported for AF_VSOCK (I think we will change this, but in a separate issue).
According to Python's documentation,
setsockopt
can get eitherint
,buffer
orNone
as the third argument.The way it is currently implemented is by trying to parse all of the three arguments as ints, if this fails no matter what the reason is we just try the next overload.
Since
2**31
causesPyExc_OverflowError
we try the next overloads. Because thebuffer
overload is last we get type error for the third argument instead ofOverflowError
(e.g.TypeError: a bytes-like object is required, not 'int'
).setsockopt
error message #107545