-
-
Notifications
You must be signed in to change notification settings - Fork 31.9k
bpo-38005: Fixed comparing and creating of InterpreterID and ChannelID. #15652
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
bpo-38005: Fixed comparing and creating of InterpreterID and ChannelID. #15652
Conversation
* They are now never equal to strings and non-integer numbers. * Comparison with a large number no longer raises OverflowError. * Arbitrary exceptions no longer silenced in constructors and comparisons. * TypeError raised in the constructor contains now the name of the type.
Lib/test/test__xxsubinterpreters.py
Outdated
class Int(str): | ||
def __init__(self, value): | ||
self._value = value | ||
def __int__(self): | ||
return self._value | ||
|
||
id = interpreters.InterpreterID(Int(10), force=True) | ||
self.assertEqual(int(id), 10) | ||
for id in ('10', b'10', bytearray(b'10'), memoryview(b'10'), '1_0', |
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 is a questionable behavior to me.
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
Thanks for working on this! I'm fine with the change as-is (including dropping support for float & bytes), but have left comments where I'd like you to at least consider a few small changes.
"O|$pppp:ChannelID.__new__", kwlist, | ||
&id, &send, &recv, &force, &resolve)) | ||
"O&|$pppp:ChannelID.__new__", kwlist, | ||
channel_id_converter, &cid, &send, &recv, &force, &resolve)) |
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.
That's a neat trick. :)
else if (PyLong_Check(other)) { | ||
/* Fast path */ | ||
int overflow; | ||
long long othercid = PyLong_AsLongLongAndOverflow(other, &overflow); |
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.
Use int64_t or the macro?
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.
Using int64_t
can cause loss of bits and returning wrong result if long long
is larger than int64_t
.
But using long long
is good here. The comparison with cid->id
will return false if the value is too large for int64_t
. Overflow also returns false instead of raising an exception (added a new test for this).
else if (PyLong_CheckExact(other)) { | ||
/* Fast path */ | ||
int overflow; | ||
long long otherid = PyLong_AsLongLongAndOverflow(other, &overflow); |
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.
Use int64_t or the macro?
self._value = value | ||
def __int__(self): | ||
return self._value | ||
def __index__(self): |
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.
__index__
makes sense, but what about __int__
too. Shouldn't both work?
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.
float
has __int__
, but not __index__
. (10.1).__int__()
returns 10
. So __int__
should not work.
Note that I changed return self._value
to return 10
because int(10)
and int(str(10))
has the same result. So it was not clear what way the str
subclass with __int__
used, bot has the same result.
Lib/test/test__xxsubinterpreters.py
Outdated
interpreters.InterpreterID(id) | ||
for id in (-1, '-1', 'spam'): | ||
with self.assertRaises(ValueError): |
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.
subtest?
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.
For such short loops it is better to rewrite tests in other way:
self.assertRaises(TypeError, interpreters.InterpreterID, 10.0)
self.assertRaises(TypeError, interpreters.InterpreterID, b'10')
self.assertRaises(TypeError, interpreters.InterpreterID, [])
self.assertRaises(ValueError, interpreters.InterpreterID, -1)
self.assertRaises(ValueError, interpreters.InterpreterID, '-1')
self.assertRaises(ValueError, interpreters.InterpreterID, 'spam')
self.assertRaises(OverflowError, interpreters.InterpreterID, 2**64)
The traceback would show what line is failed.
with self.assertRaises(OverflowError): | ||
interpreters.InterpreterID(2**64) | ||
with self.assertRaises(TypeError): | ||
interpreters.InterpreterID(object()) |
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 would be nice to have a test still for object()
.
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 replaced it with []
. But if you prefer, I'll return object()
.
@@ -572,6 +567,14 @@ def test_equality(self): | |||
self.assertTrue(id1 == id1) | |||
self.assertTrue(id1 == id2) | |||
self.assertTrue(id1 == int(id1)) | |||
self.assertTrue(int(id1) == id1) |
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.
nice :)
self._value = value | ||
def __int__(self): | ||
return self._value | ||
def __index__(self): |
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.
float
has __int__
, but not __index__
. (10.1).__int__()
returns 10
. So __int__
should not work.
Note that I changed return self._value
to return 10
because int(10)
and int(str(10))
has the same result. So it was not clear what way the str
subclass with __int__
used, bot has the same result.
Lib/test/test__xxsubinterpreters.py
Outdated
interpreters.InterpreterID(id) | ||
for id in (-1, '-1', 'spam'): | ||
with self.assertRaises(ValueError): |
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.
For such short loops it is better to rewrite tests in other way:
self.assertRaises(TypeError, interpreters.InterpreterID, 10.0)
self.assertRaises(TypeError, interpreters.InterpreterID, b'10')
self.assertRaises(TypeError, interpreters.InterpreterID, [])
self.assertRaises(ValueError, interpreters.InterpreterID, -1)
self.assertRaises(ValueError, interpreters.InterpreterID, '-1')
self.assertRaises(ValueError, interpreters.InterpreterID, 'spam')
self.assertRaises(OverflowError, interpreters.InterpreterID, 2**64)
The traceback would show what line is failed.
with self.assertRaises(OverflowError): | ||
interpreters.InterpreterID(2**64) | ||
with self.assertRaises(TypeError): | ||
interpreters.InterpreterID(object()) |
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 replaced it with []
. But if you prefer, I'll return object()
.
@@ -572,6 +567,14 @@ def test_equality(self): | |||
self.assertTrue(id1 == id1) | |||
self.assertTrue(id1 == id2) | |||
self.assertTrue(id1 == int(id1)) | |||
self.assertTrue(int(id1) == id1) | |||
self.assertTrue(id1 == float(int(id1))) | |||
self.assertTrue(float(int(id1)) == id1) |
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 one test crashed with the past implementation.
else if (PyLong_Check(other)) { | ||
/* Fast path */ | ||
int overflow; | ||
long long othercid = PyLong_AsLongLongAndOverflow(other, &overflow); |
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.
Using int64_t
can cause loss of bits and returning wrong result if long long
is larger than int64_t
.
But using long long
is good here. The comparison with cid->id
will return false if the value is too large for int64_t
. Overflow also returns false instead of raising an exception (added a new test for this).
pyid = idobj; | ||
Py_INCREF(pyid); | ||
} | ||
else if (PyUnicode_Check(idobj)) { |
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 am not sure strings should be supported in constructor. The error message says that the argument must be an int. And the code can be simpler without supporting strings. What is the use case for this?
Thanks @serhiy-storchaka for the PR 🌮🎉.. I'm working now to backport this PR to: 3.8. |
…D. (pythonGH-15652) * Fix a crash in comparing with float (and maybe other crashes). * They are now never equal to strings and non-integer numbers. * Comparison with a large number no longer raises OverflowError. * Arbitrary exceptions no longer silenced in constructors and comparisons. * TypeError raised in the constructor contains now the name of the type. * Accept only ChannelID and int-like objects in channel functions. * Accept only InterpreterId, int-like objects and str in the InterpreterId constructor. * Accept int-like objects, not just int in interpreter related functions. (cherry picked from commit bf16991) Co-authored-by: Serhiy Storchaka <[email protected]>
GH-16129 is a backport of this pull request to the 3.8 branch. |
Thanks @serhiy-storchaka for the PR 🌮🎉.. I'm working now to backport this PR to: 3.8. |
Sorry @serhiy-storchaka, I had trouble checking out the |
…annelID. (pythonGH-15652) * Fix a crash in comparing with float (and maybe other crashes). * They are now never equal to strings and non-integer numbers. * Comparison with a large number no longer raises OverflowError. * Arbitrary exceptions no longer silenced in constructors and comparisons. * TypeError raised in the constructor contains now the name of the type. * Accept only ChannelID and int-like objects in channel functions. * Accept only InterpreterId, int-like objects and str in the InterpreterId constructor. * Accept int-like objects, not just int in interpreter related functions. (cherry picked from commit bf16991) Co-authored-by: Serhiy Storchaka <[email protected]>
GH-16145 is a backport of this pull request to the 3.8 branch. |
…annelID. (GH-15652) (GH-16145) * Fix a crash in comparing with float (and maybe other crashes). * They are now never equal to strings and non-integer numbers. * Comparison with a large number no longer raises OverflowError. * Arbitrary exceptions no longer silenced in constructors and comparisons. * TypeError raised in the constructor contains now the name of the type. * Accept only ChannelID and int-like objects in channel functions. * Accept only InterpreterId, int-like objects and str in the InterpreterId constructor. * Accept int-like objects, not just int in interpreter related functions. (cherry picked from commit bf16991)
https://bugs.python.org/issue38005