From efe84c2bef3771fa82f946e760b6100201982de1 Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Thu, 31 Dec 2020 13:17:25 -0800 Subject: [PATCH] disable implicit conversion from PyFloat to .NET integer types (fixes #1342) --- CHANGELOG.md | 3 ++ src/runtime/Python.Runtime.csproj | 1 + src/runtime/arrayobject.cs | 4 +- src/runtime/converter.cs | 78 ++++++++++++++----------------- src/runtime/pylong.cs | 2 +- src/runtime/runtime.cs | 39 +++++++--------- src/tests/test_conversion.py | 2 +- src/tests/test_method.py | 3 ++ 8 files changed, 63 insertions(+), 69 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1442075ef..f9dd4907c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,8 @@ details about the cause of the failure to the regular method return value (unless they are passed with `ref` or `out` keyword). - BREAKING: Drop support for the long-deprecated CLR.* prefix. - `PyObject` now implements `IEnumerable` in addition to `IEnumerable` +- floating point values passed from Python are no longer silently truncated +when .NET expects an integer [#1342][i1342] ### Fixed @@ -807,3 +809,4 @@ This version improves performance on benchmarks significantly compared to 2.3. [i755]: https://github.com/pythonnet/pythonnet/pull/755 [p534]: https://github.com/pythonnet/pythonnet/pull/534 [i449]: https://github.com/pythonnet/pythonnet/issues/449 +[i1342]: https://github.com/pythonnet/pythonnet/issues/1342 diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index acb8efc4e..16317e449 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -4,6 +4,7 @@ AnyCPU Python.Runtime Python.Runtime + 9.0 pythonnet https://github.com/pythonnet/pythonnet/blob/master/LICENSE https://github.com/pythonnet/pythonnet diff --git a/src/runtime/arrayobject.cs b/src/runtime/arrayobject.cs index e6a4bee19..828fad6b2 100644 --- a/src/runtime/arrayobject.cs +++ b/src/runtime/arrayobject.cs @@ -49,7 +49,7 @@ public static IntPtr tp_new(IntPtr tpRaw, IntPtr args, IntPtr kw) // create single dimensional array if (Runtime.PyInt_Check(op)) { - dimensions[0] = Runtime.PyLong_AsLongLong(op); + dimensions[0] = Runtime.PyLong_AsSignedSize_t(op); if (dimensions[0] == -1 && Exceptions.ErrorOccurred()) { Exceptions.Clear(); @@ -84,7 +84,7 @@ static NewReference CreateMultidimensional(Type elementType, long[] dimensions, return default; } - dimensions[dimIndex] = Runtime.PyLong_AsLongLong(dimObj); + dimensions[dimIndex] = Runtime.PyLong_AsSignedSize_t(dimObj); if (dimensions[dimIndex] == -1 && Exceptions.ErrorOccurred()) { Exceptions.RaiseTypeError("array constructor expects integer dimensions"); diff --git a/src/runtime/converter.cs b/src/runtime/converter.cs index 2f3810c58..06b8a1810 100644 --- a/src/runtime/converter.cs +++ b/src/runtime/converter.cs @@ -511,7 +511,7 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo case TypeCode.Int32: { // Python3 always use PyLong API - long num = Runtime.PyLong_AsLongLong(value); + nint num = Runtime.PyLong_AsSignedSize_t(value); if (num == -1 && Exceptions.ErrorOccurred()) { goto convert_error; @@ -541,7 +541,7 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo goto type_error; } - int num = Runtime.PyLong_AsLong(value); + nint num = Runtime.PyLong_AsSignedSize_t(value); if (num == -1 && Exceptions.ErrorOccurred()) { goto convert_error; @@ -567,7 +567,7 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo goto type_error; } - int num = Runtime.PyLong_AsLong(value); + nint num = Runtime.PyLong_AsSignedSize_t(value); if (num == -1 && Exceptions.ErrorOccurred()) { goto convert_error; @@ -604,7 +604,7 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo } goto type_error; } - int num = Runtime.PyLong_AsLong(value); + nint num = Runtime.PyLong_AsSignedSize_t(value); if (num == -1 && Exceptions.ErrorOccurred()) { goto convert_error; @@ -619,7 +619,7 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo case TypeCode.Int16: { - int num = Runtime.PyLong_AsLong(value); + nint num = Runtime.PyLong_AsSignedSize_t(value); if (num == -1 && Exceptions.ErrorOccurred()) { goto convert_error; @@ -634,18 +634,35 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo case TypeCode.Int64: { - long num = (long)Runtime.PyLong_AsLongLong(value); - if (num == -1 && Exceptions.ErrorOccurred()) + if (Runtime.Is32Bit) { - goto convert_error; + if (!Runtime.PyLong_Check(value)) + { + goto type_error; + } + long num = Runtime.PyExplicitlyConvertToInt64(value); + if (num == -1 && Exceptions.ErrorOccurred()) + { + goto convert_error; + } + result = num; + return true; + } + else + { + nint num = Runtime.PyLong_AsSignedSize_t(value); + if (num == -1 && Exceptions.ErrorOccurred()) + { + goto convert_error; + } + result = (long)num; + return true; } - result = num; - return true; } case TypeCode.UInt16: { - long num = Runtime.PyLong_AsLong(value); + nint num = Runtime.PyLong_AsSignedSize_t(value); if (num == -1 && Exceptions.ErrorOccurred()) { goto convert_error; @@ -660,43 +677,16 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo case TypeCode.UInt32: { - op = value; - if (Runtime.PyObject_TYPE(value) != Runtime.PyLongType) - { - op = Runtime.PyNumber_Long(value); - if (op == IntPtr.Zero) - { - goto convert_error; - } - } - if (Runtime.Is32Bit || Runtime.IsWindows) + nuint num = Runtime.PyLong_AsUnsignedSize_t(value); + if (num == unchecked((nuint)(-1)) && Exceptions.ErrorOccurred()) { - uint num = Runtime.PyLong_AsUnsignedLong32(op); - if (num == uint.MaxValue && Exceptions.ErrorOccurred()) - { - goto convert_error; - } - result = num; + goto convert_error; } - else + if (num > UInt32.MaxValue) { - ulong num = Runtime.PyLong_AsUnsignedLong64(op); - if (num == ulong.MaxValue && Exceptions.ErrorOccurred()) - { - goto convert_error; - } - try - { - result = Convert.ToUInt32(num); - } - catch (OverflowException) - { - // Probably wasn't an overflow in python but was in C# (e.g. if cpython - // longs are 64 bit then 0xFFFFFFFF + 1 will not overflow in - // PyLong_AsUnsignedLong) - goto overflow; - } + goto overflow; } + result = (uint)num; return true; } diff --git a/src/runtime/pylong.cs b/src/runtime/pylong.cs index 0e21c7c49..fdfd26aba 100644 --- a/src/runtime/pylong.cs +++ b/src/runtime/pylong.cs @@ -246,7 +246,7 @@ public int ToInt32() /// public long ToInt64() { - return Runtime.PyLong_AsLongLong(obj); + return Runtime.PyExplicitlyConvertToInt64(obj); } } } diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index f80db04b6..e42dd53e0 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -1251,30 +1251,27 @@ internal static IntPtr PyLong_FromUnsignedLong(object value) [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyLong_FromString(string value, IntPtr end, int radix); - [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] - internal static extern int PyLong_AsLong(IntPtr value); - [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl, - EntryPoint = "PyLong_AsUnsignedLong")] - internal static extern uint PyLong_AsUnsignedLong32(IntPtr value); - + EntryPoint = "PyLong_AsSize_t")] + internal static extern nuint PyLong_AsUnsignedSize_t(IntPtr value); [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl, - EntryPoint = "PyLong_AsUnsignedLong")] - internal static extern ulong PyLong_AsUnsignedLong64(IntPtr value); - - internal static object PyLong_AsUnsignedLong(IntPtr value) - { - if (Is32Bit || IsWindows) - return PyLong_AsUnsignedLong32(value); - else - return PyLong_AsUnsignedLong64(value); - } - - [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] - internal static extern long PyLong_AsLongLong(BorrowedReference value); - [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] - internal static extern long PyLong_AsLongLong(IntPtr value); + EntryPoint = "PyLong_AsSsize_t")] + internal static extern nint PyLong_AsSignedSize_t(IntPtr value); + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl, + EntryPoint = "PyLong_AsSsize_t")] + internal static extern nint PyLong_AsSignedSize_t(BorrowedReference value); + /// + /// This function is a rename of PyLong_AsLongLong, which has a commonly undesired + /// behavior to convert everything (including floats) to integer type, before returning + /// the value as . + /// + /// In most cases you need to check that value is an instance of PyLongObject + /// before using this function using . + /// + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl, + EntryPoint = "PyLong_AsLongLong")] + internal static extern long PyExplicitlyConvertToInt64(IntPtr value); [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern ulong PyLong_AsUnsignedLongLong(IntPtr value); diff --git a/src/tests/test_conversion.py b/src/tests/test_conversion.py index 313274647..6b152025d 100644 --- a/src/tests/test_conversion.py +++ b/src/tests/test_conversion.py @@ -343,7 +343,7 @@ def test_uint32_conversion(): ob.UInt32Field = System.UInt32(0) assert ob.UInt32Field == 0 - with pytest.raises(ValueError): + with pytest.raises(TypeError): ConversionTest().UInt32Field = "spam" with pytest.raises(TypeError): diff --git a/src/tests/test_method.py b/src/tests/test_method.py index 18eb5af8e..a69cc6f14 100644 --- a/src/tests/test_method.py +++ b/src/tests/test_method.py @@ -807,6 +807,9 @@ def test_no_object_in_param(): with pytest.raises(TypeError): MethodTest.TestOverloadedNoObject("test") + with pytest.raises(TypeError): + MethodTest.TestOverloadedNoObject(5.5) + def test_object_in_param(): """Test regression introduced by #151 in which Object method overloads pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy