Skip to content

Commit 332f8e7

Browse files
authored
Fix (U)IntPtr construction (#1861)
* Add unit tests for (U)IntPtr conversions * Special-case construction of (U)IntPtr * Check (U)IntPtr size explicitly
1 parent 25f21f9 commit 332f8e7

File tree

5 files changed

+174
-26
lines changed

5 files changed

+174
-26
lines changed

src/runtime/Converter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ internal static bool ToManagedValue(BorrowedReference value, Type obType,
361361
// conversions (Python string -> managed string).
362362
if (obType == objectType)
363363
{
364-
if (Runtime.IsStringType(value))
364+
if (Runtime.PyString_Check(value))
365365
{
366366
return ToPrimitive(value, stringType, out result, setError);
367367
}

src/runtime/Runtime.cs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ private static string GetDefaultDllName(Version version)
5959
internal static bool TypeManagerInitialized => _typesInitialized;
6060
internal static readonly bool Is32Bit = IntPtr.Size == 4;
6161

62+
// Available in newer .NET Core versions (>= 5) as IntPtr.MaxValue etc.
63+
internal static readonly long IntPtrMaxValue = Is32Bit ? Int32.MaxValue : Int64.MaxValue;
64+
internal static readonly long IntPtrMinValue = Is32Bit ? Int32.MinValue : Int64.MinValue;
65+
internal static readonly ulong UIntPtrMaxValue = Is32Bit ? UInt32.MaxValue : UInt64.MaxValue;
66+
6267
// .NET core: System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
6368
internal static bool IsWindows = Environment.OSVersion.Platform == PlatformID.Win32NT;
6469

@@ -1281,13 +1286,6 @@ internal static bool PyFloat_Check(BorrowedReference ob)
12811286
//====================================================================
12821287
// Python string API
12831288
//====================================================================
1284-
internal static bool IsStringType(BorrowedReference op)
1285-
{
1286-
BorrowedReference t = PyObject_TYPE(op);
1287-
return (t == PyStringType)
1288-
|| (t == PyUnicodeType);
1289-
}
1290-
12911289
internal static bool PyString_Check(BorrowedReference ob)
12921290
{
12931291
return PyObject_TYPE(ob) == PyStringType;

src/runtime/Types/ClassObject.cs

Lines changed: 128 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -70,22 +70,9 @@ static NewReference tp_new_impl(BorrowedReference tp, BorrowedReference args, Bo
7070
// Primitive types do not have constructors, but they look like
7171
// they do from Python. If the ClassObject represents one of the
7272
// convertible primitive types, just convert the arg directly.
73-
if (type.IsPrimitive || type == typeof(string))
73+
if (type.IsPrimitive)
7474
{
75-
if (Runtime.PyTuple_Size(args) != 1)
76-
{
77-
Exceptions.SetError(Exceptions.TypeError, "no constructors match given arguments");
78-
return default;
79-
}
80-
81-
BorrowedReference op = Runtime.PyTuple_GetItem(args, 0);
82-
83-
if (!Converter.ToManaged(op, type, out var result, true))
84-
{
85-
return default;
86-
}
87-
88-
return CLRObject.GetReference(result!, tp);
75+
return NewPrimitive(tp, args, type);
8976
}
9077

9178
if (type.IsAbstract)
@@ -99,6 +86,11 @@ static NewReference tp_new_impl(BorrowedReference tp, BorrowedReference args, Bo
9986
return NewEnum(type, args, tp);
10087
}
10188

89+
if (type == typeof(string))
90+
{
91+
return NewString(args, tp);
92+
}
93+
10294
if (IsGenericNullable(type))
10395
{
10496
// Nullable<T> has special handling in .NET runtime.
@@ -112,6 +104,127 @@ static NewReference tp_new_impl(BorrowedReference tp, BorrowedReference args, Bo
112104
return self.NewObjectToPython(obj, tp);
113105
}
114106

107+
/// <summary>
108+
/// Construct a new .NET String object from Python args
109+
/// </summary>
110+
private static NewReference NewString(BorrowedReference args, BorrowedReference tp)
111+
{
112+
if (Runtime.PyTuple_Size(args) == 1)
113+
{
114+
BorrowedReference ob = Runtime.PyTuple_GetItem(args, 0);
115+
if (Runtime.PyString_Check(ob))
116+
{
117+
if (Runtime.GetManagedString(ob) is string val)
118+
return CLRObject.GetReference(val, tp);
119+
}
120+
121+
// TODO: Initialise using constructors instead
122+
123+
Exceptions.SetError(Exceptions.TypeError, "no constructors match given arguments");
124+
return default;
125+
}
126+
127+
return default;
128+
}
129+
130+
/// <summary>
131+
/// Create a new Python object for a primitive type
132+
///
133+
/// The primitive types are Boolean, Byte, SByte, Int16, UInt16,
134+
/// Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double,
135+
/// and Single.
136+
///
137+
/// All numeric types and Boolean can be handled by a simple
138+
/// conversion, (U)IntPtr has to be handled separately as we
139+
/// do not want to convert them automically to/from integers.
140+
/// </summary>
141+
/// <param name="type">.NET type to construct</param>
142+
/// <param name="tp">Corresponding Python type</param>
143+
/// <param name="args">Constructor arguments</param>
144+
private static NewReference NewPrimitive(BorrowedReference tp, BorrowedReference args, Type type)
145+
{
146+
// TODO: Handle IntPtr
147+
if (Runtime.PyTuple_Size(args) != 1)
148+
{
149+
Exceptions.SetError(Exceptions.TypeError, "no constructors match given arguments");
150+
return default;
151+
}
152+
153+
BorrowedReference op = Runtime.PyTuple_GetItem(args, 0);
154+
object? result = null;
155+
156+
if (type == typeof(IntPtr))
157+
{
158+
if (ManagedType.GetManagedObject(op) is CLRObject clrObject)
159+
{
160+
switch (clrObject.inst)
161+
{
162+
case nint val:
163+
result = new IntPtr(val);
164+
break;
165+
case Int64 val:
166+
result = new IntPtr(val);
167+
break;
168+
case Int32 val:
169+
result = new IntPtr(val);
170+
break;
171+
}
172+
}
173+
else if (Runtime.PyInt_Check(op))
174+
{
175+
long? num = Runtime.PyLong_AsLongLong(op);
176+
if (num is long n && n >= Runtime.IntPtrMinValue && n <= Runtime.IntPtrMaxValue)
177+
{
178+
result = new IntPtr(n);
179+
}
180+
else
181+
{
182+
Exceptions.SetError(Exceptions.OverflowError, "value not in range for IntPtr");
183+
return default;
184+
}
185+
}
186+
}
187+
188+
if (type == typeof(UIntPtr))
189+
{
190+
if (ManagedType.GetManagedObject(op) is CLRObject clrObject)
191+
{
192+
switch (clrObject.inst)
193+
{
194+
case nuint val:
195+
result = new UIntPtr(val);
196+
break;
197+
case UInt64 val:
198+
result = new UIntPtr(val);
199+
break;
200+
case UInt32 val:
201+
result = new UIntPtr(val);
202+
break;
203+
}
204+
}
205+
else if (Runtime.PyInt_Check(op))
206+
{
207+
ulong? num = Runtime.PyLong_AsUnsignedLongLong(op);
208+
if (num is ulong n && n <= Runtime.UIntPtrMaxValue)
209+
{
210+
result = new UIntPtr(n);
211+
}
212+
else
213+
{
214+
Exceptions.SetError(Exceptions.OverflowError, "value not in range for UIntPtr");
215+
return default;
216+
}
217+
}
218+
}
219+
220+
if (result == null && !Converter.ToManaged(op, type, out result, true))
221+
{
222+
return default;
223+
}
224+
225+
return CLRObject.GetReference(result!, tp);
226+
}
227+
115228
protected virtual void SetTypeNewSlot(BorrowedReference pyType, SlotsHolder slotsHolder)
116229
{
117230
TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.tp_new, new Interop.BBB_N(tp_new_impl), slotsHolder);

src/testing/conversiontest.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
namespace Python.Test
22
{
3+
using System;
34
using System.Collections.Generic;
45

56
/// <summary>
@@ -26,6 +27,8 @@ public ConversionTest()
2627
public ulong UInt64Field = 0;
2728
public float SingleField = 0.0F;
2829
public double DoubleField = 0.0;
30+
public IntPtr IntPtrField = IntPtr.Zero;
31+
public UIntPtr UIntPtrField = UIntPtr.Zero;
2932
public decimal DecimalField = 0;
3033
public string StringField;
3134
public ShortEnum EnumField;
@@ -42,7 +45,7 @@ public ConversionTest()
4245

4346
}
4447

45-
48+
4649

4750
public interface ISpam
4851
{
@@ -63,7 +66,7 @@ public string GetValue()
6366
return value;
6467
}
6568
}
66-
69+
6770
public class UnicodeString
6871
{
6972
public string value = "안녕";

tests/test_conversion.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def test_bool_conversion():
2525

2626
with pytest.raises(TypeError):
2727
ob.BooleanField = 1
28-
28+
2929
with pytest.raises(TypeError):
3030
ob.BooleanField = 0
3131

@@ -679,3 +679,37 @@ def test_iconvertible_conversion():
679679
assert 1024 == change_type(1024, System.Int32)
680680
assert 1024 == change_type(1024, System.Int64)
681681
assert 1024 == change_type(1024, System.Int16)
682+
683+
def test_intptr_construction():
684+
from System import IntPtr, UIntPtr, Int64, UInt64
685+
from ctypes import sizeof, c_void_p
686+
687+
ptr_size = sizeof(c_void_p)
688+
max_intptr = 2 ** (ptr_size * 8 - 1) - 1
689+
min_intptr = -max_intptr - 1
690+
max_uintptr = 2 ** (ptr_size * 8) - 1
691+
min_uintptr = 0
692+
693+
ob = ConversionTest()
694+
695+
assert ob.IntPtrField == IntPtr.Zero
696+
assert ob.UIntPtrField == UIntPtr.Zero
697+
698+
for v in [0, -1, 1024, max_intptr, min_intptr]:
699+
ob.IntPtrField = IntPtr(Int64(v))
700+
assert ob.IntPtrField == IntPtr(v)
701+
assert ob.IntPtrField.ToInt64() == v
702+
703+
for v in [min_intptr - 1, max_intptr + 1]:
704+
with pytest.raises(OverflowError):
705+
IntPtr(v)
706+
707+
for v in [0, 1024, min_uintptr, max_uintptr, max_intptr]:
708+
ob.UIntPtrField = UIntPtr(UInt64(v))
709+
assert ob.UIntPtrField == UIntPtr(v)
710+
assert ob.UIntPtrField.ToUInt64() == v
711+
712+
for v in [min_uintptr - 1, max_uintptr + 1, min_intptr]:
713+
with pytest.raises(OverflowError):
714+
UIntPtr(v)
715+

0 commit comments

Comments
 (0)