From 1c21526b8c6ad39dc4c088c618e29f703666ca2a Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Fri, 8 Jul 2022 20:34:18 +0200 Subject: [PATCH 1/6] Add unit tests for (U)IntPtr conversions --- src/testing/conversiontest.cs | 7 +++++-- tests/test_conversion.py | 19 ++++++++++++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/testing/conversiontest.cs b/src/testing/conversiontest.cs index 7a00f139e..272bb74c2 100644 --- a/src/testing/conversiontest.cs +++ b/src/testing/conversiontest.cs @@ -1,5 +1,6 @@ namespace Python.Test { + using System; using System.Collections.Generic; /// @@ -26,6 +27,8 @@ public ConversionTest() public ulong UInt64Field = 0; public float SingleField = 0.0F; public double DoubleField = 0.0; + public IntPtr IntPtrField = IntPtr.Zero; + public UIntPtr UIntPtrField = UIntPtr.Zero; public decimal DecimalField = 0; public string StringField; public ShortEnum EnumField; @@ -42,7 +45,7 @@ public ConversionTest() } - + public interface ISpam { @@ -63,7 +66,7 @@ public string GetValue() return value; } } - + public class UnicodeString { public string value = "안녕"; diff --git a/tests/test_conversion.py b/tests/test_conversion.py index 6693d8000..1f89b3e0c 100644 --- a/tests/test_conversion.py +++ b/tests/test_conversion.py @@ -25,7 +25,7 @@ def test_bool_conversion(): with pytest.raises(TypeError): ob.BooleanField = 1 - + with pytest.raises(TypeError): ob.BooleanField = 0 @@ -679,3 +679,20 @@ def test_iconvertible_conversion(): assert 1024 == change_type(1024, System.Int32) assert 1024 == change_type(1024, System.Int64) assert 1024 == change_type(1024, System.Int16) + +def test_intptr_conversion(): + from System import IntPtr, UIntPtr, Int64 + + ob = ConversionTest() + + assert ob.IntPtrField == IntPtr.Zero + assert ob.UIntPtrField == UIntPtr.Zero + + ob.IntPtrField = IntPtr(-1) + assert ob.IntPtrField == IntPtr(-1) + + ob.IntPtrField = IntPtr(Int64(1024)) + assert ob.IntPtrField == IntPtr(1024) + + ob.UIntPtrField = ob.IntPtrField + assert ob.UIntPtrField == UIntPtr(1024) From 004232f496645ee7377394737ea647cc739e8f62 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 9 Jul 2022 16:05:27 +0200 Subject: [PATCH 2/6] Special-case construction of (U)IntPtr --- src/runtime/Converter.cs | 30 ++++++++++++++++++++++++++++++ tests/test_conversion.py | 14 +++++++------- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/runtime/Converter.cs b/src/runtime/Converter.cs index f86ba7900..5329db555 100644 --- a/src/runtime/Converter.cs +++ b/src/runtime/Converter.cs @@ -298,6 +298,36 @@ internal static bool ToManagedValue(BorrowedReference value, Type obType, { case CLRObject co: object tmp = co.inst; + if (obType == typeof(IntPtr)) + { + switch (tmp) + { + case nint val: + result = new IntPtr(val); + return true; + case Int64 val: + result = new IntPtr(val); + return true; + case Int32 val: + result = new IntPtr(val); + return true; + } + } + if (obType == typeof(UIntPtr)) + { + switch (tmp) + { + case nuint val: + result = new UIntPtr(val); + return true; + case UInt64 val: + result = new UIntPtr(val); + return true; + case UInt32 val: + result = new UIntPtr(val); + return true; + } + } if (obType.IsInstanceOfType(tmp)) { result = tmp; diff --git a/tests/test_conversion.py b/tests/test_conversion.py index 1f89b3e0c..97c357382 100644 --- a/tests/test_conversion.py +++ b/tests/test_conversion.py @@ -680,19 +680,19 @@ def test_iconvertible_conversion(): assert 1024 == change_type(1024, System.Int64) assert 1024 == change_type(1024, System.Int16) -def test_intptr_conversion(): - from System import IntPtr, UIntPtr, Int64 +def test_intptr_construction(): + from System import IntPtr, UIntPtr, Int64, UInt64 ob = ConversionTest() assert ob.IntPtrField == IntPtr.Zero assert ob.UIntPtrField == UIntPtr.Zero - ob.IntPtrField = IntPtr(-1) - assert ob.IntPtrField == IntPtr(-1) + ob.IntPtrField = IntPtr(Int64(-1)) + assert ob.IntPtrField.ToInt64() == -1 ob.IntPtrField = IntPtr(Int64(1024)) - assert ob.IntPtrField == IntPtr(1024) + assert ob.IntPtrField.ToInt64() == 1024 - ob.UIntPtrField = ob.IntPtrField - assert ob.UIntPtrField == UIntPtr(1024) + ob.UIntPtrField = UIntPtr(UInt64(1024)) + assert ob.UIntPtrField.ToUInt64() == 1024 From c88b1f6fbfe8cf585b15590122f13cd6199b9d79 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 9 Jul 2022 17:55:39 +0200 Subject: [PATCH 3/6] Move ptr construction to ClassObject --- src/runtime/Converter.cs | 32 +-------- src/runtime/Runtime.cs | 7 -- src/runtime/Types/ClassObject.cs | 117 +++++++++++++++++++++++++++---- 3 files changed, 103 insertions(+), 53 deletions(-) diff --git a/src/runtime/Converter.cs b/src/runtime/Converter.cs index 5329db555..e1820f05b 100644 --- a/src/runtime/Converter.cs +++ b/src/runtime/Converter.cs @@ -298,36 +298,6 @@ internal static bool ToManagedValue(BorrowedReference value, Type obType, { case CLRObject co: object tmp = co.inst; - if (obType == typeof(IntPtr)) - { - switch (tmp) - { - case nint val: - result = new IntPtr(val); - return true; - case Int64 val: - result = new IntPtr(val); - return true; - case Int32 val: - result = new IntPtr(val); - return true; - } - } - if (obType == typeof(UIntPtr)) - { - switch (tmp) - { - case nuint val: - result = new UIntPtr(val); - return true; - case UInt64 val: - result = new UIntPtr(val); - return true; - case UInt32 val: - result = new UIntPtr(val); - return true; - } - } if (obType.IsInstanceOfType(tmp)) { result = tmp; @@ -391,7 +361,7 @@ internal static bool ToManagedValue(BorrowedReference value, Type obType, // conversions (Python string -> managed string). if (obType == objectType) { - if (Runtime.IsStringType(value)) + if (Runtime.PyString_Check(value)) { return ToPrimitive(value, stringType, out result, setError); } diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index 1eeb96b54..7e6210f90 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -1281,13 +1281,6 @@ internal static bool PyFloat_Check(BorrowedReference ob) //==================================================================== // Python string API //==================================================================== - internal static bool IsStringType(BorrowedReference op) - { - BorrowedReference t = PyObject_TYPE(op); - return (t == PyStringType) - || (t == PyUnicodeType); - } - internal static bool PyString_Check(BorrowedReference ob) { return PyObject_TYPE(ob) == PyStringType; diff --git a/src/runtime/Types/ClassObject.cs b/src/runtime/Types/ClassObject.cs index 474e9dd7b..f68cf8b15 100644 --- a/src/runtime/Types/ClassObject.cs +++ b/src/runtime/Types/ClassObject.cs @@ -70,22 +70,9 @@ static NewReference tp_new_impl(BorrowedReference tp, BorrowedReference args, Bo // Primitive types do not have constructors, but they look like // they do from Python. If the ClassObject represents one of the // convertible primitive types, just convert the arg directly. - if (type.IsPrimitive || type == typeof(string)) + if (type.IsPrimitive) { - if (Runtime.PyTuple_Size(args) != 1) - { - Exceptions.SetError(Exceptions.TypeError, "no constructors match given arguments"); - return default; - } - - BorrowedReference op = Runtime.PyTuple_GetItem(args, 0); - - if (!Converter.ToManaged(op, type, out var result, true)) - { - return default; - } - - return CLRObject.GetReference(result!, tp); + return NewPrimitive(tp, args, type); } if (type.IsAbstract) @@ -99,6 +86,11 @@ static NewReference tp_new_impl(BorrowedReference tp, BorrowedReference args, Bo return NewEnum(type, args, tp); } + if (type == typeof(string)) + { + return NewString(args, tp); + } + if (IsGenericNullable(type)) { // Nullable has special handling in .NET runtime. @@ -112,6 +104,101 @@ static NewReference tp_new_impl(BorrowedReference tp, BorrowedReference args, Bo return self.NewObjectToPython(obj, tp); } + /// + /// Construct a new .NET String object from Python args + /// + private static NewReference NewString(BorrowedReference args, BorrowedReference tp) + { + if (Runtime.PyTuple_Size(args) == 1) + { + BorrowedReference ob = Runtime.PyTuple_GetItem(args, 0); + if (Runtime.PyString_Check(ob)) + { + if (Runtime.GetManagedString(ob) is string val) + return CLRObject.GetReference(val, tp); + } + + // TODO: Initialise using constructors instead + + Exceptions.SetError(Exceptions.TypeError, "no constructors match given arguments"); + return default; + } + + return default; + } + + /// + /// Create a new Python object for a primitive type + /// + /// The primitive types are Boolean, Byte, SByte, Int16, UInt16, + /// Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double, + /// and Single. + /// + /// All numeric types and Boolean can be handled by a simple + /// conversion, (U)IntPtr has to be handled separately as we + /// do not want to convert them automically to/from integers. + /// + /// .NET type to construct + /// Corresponding Python type + /// Constructor arguments + private static NewReference NewPrimitive(BorrowedReference tp, BorrowedReference args, Type type) + { + // TODO: Handle IntPtr + if (Runtime.PyTuple_Size(args) != 1) + { + Exceptions.SetError(Exceptions.TypeError, "no constructors match given arguments"); + return default; + } + + BorrowedReference op = Runtime.PyTuple_GetItem(args, 0); + object? result = null; + + if (type == typeof(IntPtr)) + { + if (ManagedType.GetManagedObject(op) is CLRObject clrObject) + { + switch (clrObject.inst) + { + case nint val: + result = new IntPtr(val); + break; + case Int64 val: + result = new IntPtr(val); + break; + case Int32 val: + result = new IntPtr(val); + break; + } + } + } + + if (type == typeof(UIntPtr)) + { + if (ManagedType.GetManagedObject(op) is CLRObject clrObject) + { + switch (clrObject.inst) + { + case nuint val: + result = new UIntPtr(val); + break; + case UInt64 val: + result = new UIntPtr(val); + break; + case UInt32 val: + result = new UIntPtr(val); + break; + } + } + } + + if (result == null && !Converter.ToManaged(op, type, out result, true)) + { + return default; + } + + return CLRObject.GetReference(result!, tp); + } + protected virtual void SetTypeNewSlot(BorrowedReference pyType, SlotsHolder slotsHolder) { TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.tp_new, new Interop.BBB_N(tp_new_impl), slotsHolder); From 65a6408ac772b3c071aa34ce977f54224743ce92 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 11 Jul 2022 11:41:10 +0200 Subject: [PATCH 4/6] Add int constructor for (U)IntPtr --- src/runtime/Types/ClassObject.cs | 18 +++++++++++++++++- tests/test_conversion.py | 3 +++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/runtime/Types/ClassObject.cs b/src/runtime/Types/ClassObject.cs index f68cf8b15..97216f24b 100644 --- a/src/runtime/Types/ClassObject.cs +++ b/src/runtime/Types/ClassObject.cs @@ -129,7 +129,7 @@ private static NewReference NewString(BorrowedReference args, BorrowedReference /// /// Create a new Python object for a primitive type - /// + /// /// The primitive types are Boolean, Byte, SByte, Int16, UInt16, /// Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double, /// and Single. @@ -170,6 +170,14 @@ private static NewReference NewPrimitive(BorrowedReference tp, BorrowedReference break; } } + else if (Runtime.PyInt_Check(op)) + { + long? num = Runtime.PyLong_AsLongLong(op); + if (num is long n) + { + result = new IntPtr(n); + } + } } if (type == typeof(UIntPtr)) @@ -189,6 +197,14 @@ private static NewReference NewPrimitive(BorrowedReference tp, BorrowedReference break; } } + else if (Runtime.PyInt_Check(op)) + { + ulong? num = Runtime.PyLong_AsUnsignedLongLong(op); + if (num is ulong n) + { + result = new UIntPtr(n); + } + } } if (result == null && !Converter.ToManaged(op, type, out result, true)) diff --git a/tests/test_conversion.py b/tests/test_conversion.py index 97c357382..678e09928 100644 --- a/tests/test_conversion.py +++ b/tests/test_conversion.py @@ -689,10 +689,13 @@ def test_intptr_construction(): assert ob.UIntPtrField == UIntPtr.Zero ob.IntPtrField = IntPtr(Int64(-1)) + assert ob.IntPtrField == IntPtr(-1) assert ob.IntPtrField.ToInt64() == -1 ob.IntPtrField = IntPtr(Int64(1024)) + assert ob.IntPtrField == IntPtr(1024) assert ob.IntPtrField.ToInt64() == 1024 ob.UIntPtrField = UIntPtr(UInt64(1024)) + assert ob.UIntPtrField == UIntPtr(1024) assert ob.UIntPtrField.ToUInt64() == 1024 From 775efd522d8e8cf022783bc8dd066e670b007fc2 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 11 Jul 2022 13:00:56 +0200 Subject: [PATCH 5/6] Extend intptr conversion tests and exceptions --- tests/test_conversion.py | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/tests/test_conversion.py b/tests/test_conversion.py index 678e09928..2641489a3 100644 --- a/tests/test_conversion.py +++ b/tests/test_conversion.py @@ -682,20 +682,34 @@ def test_iconvertible_conversion(): def test_intptr_construction(): from System import IntPtr, UIntPtr, Int64, UInt64 + from ctypes import sizeof, c_void_p + + ptr_size = sizeof(c_void_p) + max_intptr = 2 ** (ptr_size * 8 - 1) - 1 + min_intptr = -max_intptr - 1 + max_uintptr = 2 ** (ptr_size * 8) - 1 + min_uintptr = 0 ob = ConversionTest() assert ob.IntPtrField == IntPtr.Zero assert ob.UIntPtrField == UIntPtr.Zero - ob.IntPtrField = IntPtr(Int64(-1)) - assert ob.IntPtrField == IntPtr(-1) - assert ob.IntPtrField.ToInt64() == -1 + for v in [0, -1, 1024, max_intptr, min_intptr]: + ob.IntPtrField = IntPtr(Int64(v)) + assert ob.IntPtrField == IntPtr(v) + assert ob.IntPtrField.ToInt64() == v + + for v in [min_intptr - 1, max_intptr + 1]: + with pytest.raises(TypeError): + IntPtr(v) + + for v in [0, 1024, min_uintptr, max_uintptr, max_intptr]: + ob.UIntPtrField = UIntPtr(UInt64(v)) + assert ob.UIntPtrField == UIntPtr(v) + assert ob.UIntPtrField.ToUInt64() == v - ob.IntPtrField = IntPtr(Int64(1024)) - assert ob.IntPtrField == IntPtr(1024) - assert ob.IntPtrField.ToInt64() == 1024 + for v in [min_uintptr - 1, max_uintptr + 1, min_intptr]: + with pytest.raises(TypeError): + UIntPtr(v) - ob.UIntPtrField = UIntPtr(UInt64(1024)) - assert ob.UIntPtrField == UIntPtr(1024) - assert ob.UIntPtrField.ToUInt64() == 1024 From ae5e0746693c569af4c3a023b8ba2998e9971c45 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 11 Jul 2022 13:22:10 +0200 Subject: [PATCH 6/6] Check (U)IntPtr size explicitly --- src/runtime/Runtime.cs | 5 +++++ src/runtime/Types/ClassObject.cs | 14 ++++++++++++-- tests/test_conversion.py | 4 ++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index 7e6210f90..20bef23d4 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -59,6 +59,11 @@ private static string GetDefaultDllName(Version version) internal static bool TypeManagerInitialized => _typesInitialized; internal static readonly bool Is32Bit = IntPtr.Size == 4; + // Available in newer .NET Core versions (>= 5) as IntPtr.MaxValue etc. + internal static readonly long IntPtrMaxValue = Is32Bit ? Int32.MaxValue : Int64.MaxValue; + internal static readonly long IntPtrMinValue = Is32Bit ? Int32.MinValue : Int64.MinValue; + internal static readonly ulong UIntPtrMaxValue = Is32Bit ? UInt32.MaxValue : UInt64.MaxValue; + // .NET core: System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Windows) internal static bool IsWindows = Environment.OSVersion.Platform == PlatformID.Win32NT; diff --git a/src/runtime/Types/ClassObject.cs b/src/runtime/Types/ClassObject.cs index 97216f24b..70ec53b18 100644 --- a/src/runtime/Types/ClassObject.cs +++ b/src/runtime/Types/ClassObject.cs @@ -173,10 +173,15 @@ private static NewReference NewPrimitive(BorrowedReference tp, BorrowedReference else if (Runtime.PyInt_Check(op)) { long? num = Runtime.PyLong_AsLongLong(op); - if (num is long n) + if (num is long n && n >= Runtime.IntPtrMinValue && n <= Runtime.IntPtrMaxValue) { result = new IntPtr(n); } + else + { + Exceptions.SetError(Exceptions.OverflowError, "value not in range for IntPtr"); + return default; + } } } @@ -200,10 +205,15 @@ private static NewReference NewPrimitive(BorrowedReference tp, BorrowedReference else if (Runtime.PyInt_Check(op)) { ulong? num = Runtime.PyLong_AsUnsignedLongLong(op); - if (num is ulong n) + if (num is ulong n && n <= Runtime.UIntPtrMaxValue) { result = new UIntPtr(n); } + else + { + Exceptions.SetError(Exceptions.OverflowError, "value not in range for UIntPtr"); + return default; + } } } diff --git a/tests/test_conversion.py b/tests/test_conversion.py index 2641489a3..a5b4c6fd9 100644 --- a/tests/test_conversion.py +++ b/tests/test_conversion.py @@ -701,7 +701,7 @@ def test_intptr_construction(): assert ob.IntPtrField.ToInt64() == v for v in [min_intptr - 1, max_intptr + 1]: - with pytest.raises(TypeError): + with pytest.raises(OverflowError): IntPtr(v) for v in [0, 1024, min_uintptr, max_uintptr, max_intptr]: @@ -710,6 +710,6 @@ def test_intptr_construction(): assert ob.UIntPtrField.ToUInt64() == v for v in [min_uintptr - 1, max_uintptr + 1, min_intptr]: - with pytest.raises(TypeError): + with pytest.raises(OverflowError): UIntPtr(v)