mirror of
https://github.com/pybind/pybind11.git
synced 2026-04-21 15:29:11 +00:00
Fix unsigned error value casting
When casting to an unsigned type from a python 2 `int`, we currently cast using `(unsigned long long) PyLong_AsUnsignedLong(src.ptr())`. If the Python cast fails, it returns (unsigned long) -1, but then we cast this to `unsigned long long`, which means we get 4294967295, but because that isn't equal to `(unsigned long long) -1`, we don't detect the failure. This commit moves the unsigned casting into a `detail::as_unsigned` function which, upon error, casts -1 to the final type, and otherwise casts the return value to the final type to avoid the problematic double cast when an error occurs. The error most commonly shows up wherever `long` is 32-bits (e.g. under both 32- and 64-bit Windows, and under 32-bit linux) when passing a negative value to a bound function taking an `unsigned long`. Fixes #929. The added tests also trigger a latent segfault under PyPy: when casting to an integer smaller than `long` (e.g. casting to a `uint32_t` on a 64-bit `long` architecture) we check both for a Python error and also that the resulting intermediate value will fit in the final type. If there is no conversion error, but we get a value that would overflow, we end up calling `PyErr_ExceptionMatches()` illegally: that call is only allowed when there is a current exception. Under PyPy, this segfaults the test suite. It doesn't appear to segfault under CPython, but the documentation suggests that it *could* do so. The fix is to only check for the exception match if we actually got an error.
This commit is contained in:
@@ -143,6 +143,44 @@ def test_string_view(capture):
|
||||
"""
|
||||
|
||||
|
||||
def test_integer_casting():
|
||||
"""Issue #929 - out-of-range integer values shouldn't be accepted"""
|
||||
import sys
|
||||
assert m.i32_str(-1) == "-1"
|
||||
assert m.i64_str(-1) == "-1"
|
||||
assert m.i32_str(2000000000) == "2000000000"
|
||||
assert m.u32_str(2000000000) == "2000000000"
|
||||
if sys.version_info < (3,):
|
||||
assert m.i32_str(long(-1)) == "-1"
|
||||
assert m.i64_str(long(-1)) == "-1"
|
||||
assert m.i64_str(long(-999999999999)) == "-999999999999"
|
||||
assert m.u64_str(long(999999999999)) == "999999999999"
|
||||
else:
|
||||
assert m.i64_str(-999999999999) == "-999999999999"
|
||||
assert m.u64_str(999999999999) == "999999999999"
|
||||
|
||||
with pytest.raises(TypeError) as excinfo:
|
||||
m.u32_str(-1)
|
||||
assert "incompatible function arguments" in str(excinfo.value)
|
||||
with pytest.raises(TypeError) as excinfo:
|
||||
m.u64_str(-1)
|
||||
assert "incompatible function arguments" in str(excinfo.value)
|
||||
with pytest.raises(TypeError) as excinfo:
|
||||
m.i32_str(-3000000000)
|
||||
assert "incompatible function arguments" in str(excinfo.value)
|
||||
with pytest.raises(TypeError) as excinfo:
|
||||
m.i32_str(3000000000)
|
||||
assert "incompatible function arguments" in str(excinfo.value)
|
||||
|
||||
if sys.version_info < (3,):
|
||||
with pytest.raises(TypeError) as excinfo:
|
||||
m.u32_str(long(-1))
|
||||
assert "incompatible function arguments" in str(excinfo.value)
|
||||
with pytest.raises(TypeError) as excinfo:
|
||||
m.u64_str(long(-1))
|
||||
assert "incompatible function arguments" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_tuple(doc):
|
||||
"""std::pair <-> tuple & std::tuple <-> tuple"""
|
||||
assert m.pair_passthrough((True, "test")) == ("test", True)
|
||||
|
||||
Reference in New Issue
Block a user