8000 disable implicit conversion from PyFloat to .NET integer types (fixes… · pythonnet/pythonnet@7c4a6b5 · GitHub
[go: up one dir, main page]

Skip to content

Commit 7c4a6b5

Browse files
committed
disable implicit conversion from PyFloat to .NET integer types (fixes #1342)
1 parent 1f40564 commit 7c4a6b5

File tree

7 files changed

+62
-68
lines changed

7 files changed

+62
-68
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ details about the cause of the failure
2727
to the regular method return value (unless they are passed with `ref` or `out` keyword).
2828
- BREAKING: Drop support for the long-deprecated CLR.* prefix.
2929
- `PyObject` now implements `IEnumerable<PyObject>` in addition to `IEnumerable`
30+
- floating point values passed from Python are no longer silently truncated
31+
when .NET expects an integer [#1342][i1342]
3032

3133
### Fixed
3234

@@ -807,3 +809,4 @@ This version improves performance on benchmarks significantly compared to 2.3.
807809
[i755]: https://github.com/pythonnet/pythonnet/pull/755
808810
[p534]: https://github.com/pythonnet/pythonnet/pull/534
809811
[i449]: https://github.com/pythonnet/pythonnet/issues/449
812+
[i1342]: https://github.com/pythonnet/pythonnet/issues/1342

src/runtime/Python.Runtime.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<Platforms>AnyCPU</Platforms>
55
<RootNamespace>Python.Runtime</RootNamespace>
66
<AssemblyName>Python.Runtime</AssemblyName>
7+
<LangVersion>9.0</LangVersion>
78
<PackageId>pythonnet</PackageId>
89
<PackageLicenseUrl>https://github.com/pythonnet/pythonnet/blob/master/LICENSE</PackageLicenseUrl>
910
<RepositoryUrl>https://github.com/pythonnet/pythonnet</RepositoryUrl>

src/runtime/arrayobject.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public static IntPtr tp_new(IntPtr tpRaw, IntPtr args, IntPtr kw)
4949
// create single dimensional array
5050
if (Runtime.PyInt_Check(op))
5151
{
52-
dimensions[0] = Runtime.PyLong_AsLongLong(op);
52+
dimensions[0] = Runtime.PyLong_AsSignedSize_t(op);
5353
if (dimensions[0] == -1 && Exceptions.ErrorOccurred())
5454
{
5555
Exceptions.Clear();
@@ -84,7 +84,7 @@ static NewReference CreateMultidimensional(Type elementType, long[] dimensions,
8484
return default;
8585
}
8686

87-
dimensions[dimIndex] = Runtime.PyLong_AsLongLong(dimObj);
87+
dimensions[dimIndex] = Runtime.PyLong_AsSignedSize_t(dimObj);
8888
if (dimensions[dimIndex] == -1 && Exceptions.ErrorOccurred())
8989
{
9090
Exceptions.RaiseTypeError("array constructor expects integer dimensions");

src/runtime/converter.cs

Lines changed: 34 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -511,7 +511,7 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo
511511
case TypeCode.Int32:
512512
{
513513
// Python3 always use PyLong API
514-
long num = Runtime.PyLong_AsLongLong(value);
514+
nint num = Runtime.PyLong_AsSignedSize_t(value);
515515
if (num == -1 && Exceptions.ErrorOccurred())
516516
{
517517
goto convert_error;
@@ -541,7 +541,7 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo
541541
goto type_error;
542542
}
543543

544-
int num = Runtime.PyLong_AsLong(value);
544+
nint num = Runtime.PyLong_AsSignedSize_t(value);
545545
if (num == -1 && Exceptions.ErrorOccurred())
546546
{
547547
goto convert_error;
@@ -567,7 +567,7 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo
567567
goto type_error;
568568
}
569569

570-
int num = Runtime.PyLong_AsLong(value);
570+
nint num = Runtime.PyLong_AsSignedSize_t(value);
571571
if (num == -1 && Exceptions.ErrorOccurred())
572572
{
573573
goto convert_error;
@@ -604,7 +604,7 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo
604604
}
605605
goto type_error;
606606
}
607-
int num = Runtime.PyLong_AsLong(value);
607+
nint num = Runtime.PyLong_AsSignedSize_t(value);
608608
if (num == -1 && Exceptions.ErrorOccurred())
609609
{
610610
goto convert_error;
@@ -619,7 +619,7 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo
619619

620620
case TypeCode.Int16:
621621
{
622-
int num = Runtime.PyLong_AsLong(value);
622+
nint num = Runtime.PyLong_AsSignedSize_t(value);
623623
if (num == -1 && Exceptions.ErrorOccurred())
624624
{
625625
goto convert_error;
@@ -634,18 +634,35 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo
634634

635635
case TypeCode.Int64:
636636
{
637-
long num = (long)Runtime.PyLong_AsLongLong(value);
638-
if (num == -1 && Exceptions.ErrorOccurred())
637+
if (Runtime.Is32Bit)
639638
{
640-
goto convert_error;
639+
if (!Runtime.PyLong_Check(value))
640+
{
641+
goto type_error;
642+
}
643+
long num = Runtime.PyExplicitlyConvertToInt64(value);
644+
if (num == -1 && Exceptions.ErrorOccurred())
645+
{
646+
goto convert_error;
647+
}
648+
result = num;
649+
return true;
650+
}
651+
else
652+
{
653+
nint num = Runtime.PyLong_AsSignedSize_t(value);
654+
if (num == -1 && Exceptions.ErrorOccurred())
655+
{
656+
goto convert_error;
657+
}
658+
result = (long)num;
659+
return true;
641660
}
642-
result = num;
643-
return true;
644661
}
645662

646663
case TypeCode.UInt16:
647664
{
648-
long num = Runtime.PyLong_AsLong(value);
665+
nint num = Runtime.PyLong_AsSignedSize_t(value);
649666
if (num == -1 && Exceptions.ErrorOccurred())
650667
{
651668
goto convert_error;
@@ -660,43 +677,16 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo
660677

661678
case TypeCode.UInt32:
662679
{
663-
op = value;
664-
if (Runtime.PyObject_TYPE(value) != Runtime.PyLongType)
665-
{
666-
op = Runtime.PyNumber_Long(value);
667-
if (op == IntPtr.Zero)
668-
{
669-
goto convert_error;
670-
}
671-
}
672-
if (Runtime.Is32Bit || Runtime.IsWindows)
680+
nuint num = Runtime.PyLong_AsUnsignedSize_t(value);
681+
if (num == unchecked((nuint)(-1)) && Exceptions.ErrorOccurred())
673682
{
674-
uint num = Runtime.PyLong_AsUnsignedLong32(op);
675-
if (num == uint.MaxValue && Exceptions.ErrorOccurred())
676-
{
677-
goto convert_error;
678-
}
679-
result = num;
683+
goto convert_error;
680684
}
681-
else
685+
if (num > UInt32.MaxValue)
682686
{
683-
ulong num = Runtime.PyLong_AsUnsignedLong64(op);
684-
if (num == ulong.MaxValue && Exceptions.ErrorOccurred())
685-
{
686-
goto convert_error;
687-
}
688-
try
689-
{
690-
result = Convert.ToUInt32(num);
691-
}
692-
catch (OverflowException)
693-
{
694-
// Probably wasn't an overflow in python but was in C# (e.g. if cpython
695-
// longs are 64 bit then 0xFFFFFFFF + 1 will not overflow in
696-
// PyLong_AsUnsignedLong)
697-
goto overflow;
698-
}
687+
goto overflow;
699688
}
689+
result = (uint)num;
700690
return true;
701691
}
702692

src/runtime/pylong.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ public int ToInt32()
246246
/// </remarks>
247247
public long ToInt64()
248248
{
249-
return Runtime.PyLong_AsLongLong(obj);
249+
return Runtime.PyExplicitlyConvertToInt64(obj);
250250
}
251251
}
252252
}

src/runtime/runtime.cs

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1251,30 +1251,27 @@ internal static IntPtr PyLong_FromUnsignedLong(object value)
12511251
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
12521252
internal static extern IntPtr PyLong_FromString(string value, IntPtr end, int radix);
12531253

1254-
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
1255-
internal static extern int PyLong_AsLong(IntPtr value);
1256-
12571254
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl,
1258-
EntryPoint = "PyLong_AsUnsignedLong")]
1259-
internal static extern uint PyLong_AsUnsignedLong32(IntPtr value);
1260-
1255+
EntryPoint = "PyLong_AsSize_t")]
1256+
internal static extern nuint PyLong_AsUnsignedSize_t(IntPtr value);
12611257
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl,
1262-
EntryPoint = "PyLong_AsUnsignedLong")]
1263-
internal static extern ulong PyLong_AsUnsignedLong64(IntPtr value);
1264-
1265-
internal static object PyLong_AsUnsignedLong(IntPtr value)
1266-
{
1267-
if (Is32Bit || IsWindows)
1268-
return PyLong_AsUnsignedLong32(value);
1269-
else
1270-
return PyLong_AsUnsignedLong64(value);
1271-
}
1272-
1273-
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
1274-
internal static extern long PyLong_AsLongLong(BorrowedReference value);
1275-
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
1276-
internal static extern long PyLong_AsLongLong(IntPtr value);
1258+
EntryPoint = "PyLong_AsSsize_t")]
1259+
internal static extern nint PyLong_AsSignedSize_t(IntPtr value);
1260+
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl,
1261+
EntryPoint = "PyLong_AsSsize_t")]
1262+
internal static extern nint PyLong_AsSignedSize_t(BorrowedReference value);
12771263

1264+
/// <summary>
1265+
/// This function is a rename of PyLong_AsLongLong, which has a commonly undesired
1266+
/// behavior to convert everything (including floats) to integer type, before returning
1267+
/// the value as <see cref="Int64"/>.
1268+
///
1269+
/// <para>In most cases you need to check that value is an instance of PyLongObject
1270+
/// before using this function using <see cref="PyLong_Check(IntPtr)"/>.</para>
1271+
/// </summary>
1272+
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl,
1273+
EntryPoint = "PyLong_AsLongLong")]
1274+
internal static extern long PyExplicitlyConvertToInt64(IntPtr value);
12781275
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
12791276
internal static extern ulong PyLong_AsUnsignedLongLong(IntPtr value);
12801277

src/tests/test_method.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,9 @@ def test_no_object_in_param():
807807
with pytest.raises(TypeError):
808808
MethodTest.TestOverloadedNoObject("test")
809809

810+
with pytest.raises(TypeError):
811+
MethodTest.TestOverloadedNoObject(5.5)
812+
810813

811814
def test_object_in_param():
812815
"""Test regression introduced by #151 in which Object method overloads

0 commit comments

Comments
 (0)
0