-
-
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.
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. |
Modules/socketmodule.c
Outdated
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(...) }
Modules/socketmodule.c
Outdated
} | ||
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", |
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.
This ensures that the 3rd argument would be of the specified type.
In my case Py_TYPE(Py_None)
that way I ensure that if we have 4 arguments the 3rd must be None
.
Modules/socketmodule.c
Outdated
@@ -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) */ |
Modules/socketmodule.c
Outdated
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); |
Modules/socketmodule.c
Outdated
&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.
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.
Look at https://github.com/python/cpython/pull/107546/files#r2192102447.
The "O!" format along with Py_TYPE(Py_None)
ensures that the type of the 3rd argument would be None
.
Lib/test/test_socket.py
Outdated
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.
arglen = PyTuple_Size(args); | ||
if (arglen == 3 && optval == Py_None) { | ||
PyErr_Format(PyExc_TypeError, | ||
"setsockopt() take 4 arguments when socket option is None (%zd given)", |
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.
Would not the following be better: "setsockopt() requires 4 arguments when the third argument is None"?
} | ||
if (arglen == 4 && optval != Py_None) { | ||
PyErr_Format(PyExc_TypeError, | ||
"setsockopt() argument 3 must be NoneType, not %T", |
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() takes 3 arguments when the third argument is not None" or "setsockopt() only takes 4 arguments when the third argument is None"?
PyObject *none; | ||
PyObject *optval; | ||
|
||
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.
All this should be after the code for AF_VSOCK. Otherwise we will get wrong error messages for AF_VSOCK.
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