Fix (U)IntPtr construction (#1861)
* Add unit tests for (U)IntPtr conversions * Special-case construction of (U)IntPtr * Check (U)IntPtr size explicitly
This commit is contained in:
Родитель
25f21f99b2
Коммит
332f8e79ef
|
@ -361,7 +361,7 @@ namespace Python.Runtime
|
|||
// conversions (Python string -> managed string).
|
||||
if (obType == objectType)
|
||||
{
|
||||
if (Runtime.IsStringType(value))
|
||||
if (Runtime.PyString_Check(value))
|
||||
{
|
||||
return ToPrimitive(value, stringType, out result, setError);
|
||||
}
|
||||
|
|
|
@ -59,6 +59,11 @@ namespace Python.Runtime
|
|||
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;
|
||||
|
||||
|
@ -1281,13 +1286,6 @@ namespace Python.Runtime
|
|||
//====================================================================
|
||||
// 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;
|
||||
|
|
|
@ -70,22 +70,9 @@ namespace Python.Runtime
|
|||
// 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 @@ namespace Python.Runtime
|
|||
return NewEnum(type, args, tp);
|
||||
}
|
||||
|
||||
if (type == typeof(string))
|
||||
{
|
||||
return NewString(args, tp);
|
||||
}
|
||||
|
||||
if (IsGenericNullable(type))
|
||||
{
|
||||
// Nullable<T> has special handling in .NET runtime.
|
||||
|
@ -112,6 +104,127 @@ namespace Python.Runtime
|
|||
return self.NewObjectToPython(obj, tp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new .NET String object from Python args
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="type">.NET type to construct</param>
|
||||
/// <param name="tp">Corresponding Python type</param>
|
||||
/// <param name="args">Constructor arguments</param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
else if (Runtime.PyInt_Check(op))
|
||||
{
|
||||
long? num = Runtime.PyLong_AsLongLong(op);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
else if (Runtime.PyInt_Check(op))
|
||||
{
|
||||
ulong? num = Runtime.PyLong_AsUnsignedLongLong(op);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
namespace Python.Test
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
|
@ -26,6 +27,8 @@ namespace Python.Test
|
|||
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 @@ namespace Python.Test
|
|||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public interface ISpam
|
||||
{
|
||||
|
@ -63,7 +66,7 @@ namespace Python.Test
|
|||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class UnicodeString
|
||||
{
|
||||
public string value = "안녕";
|
||||
|
|
|
@ -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,37 @@ 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_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
|
||||
|
||||
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(OverflowError):
|
||||
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
|
||||
|
||||
for v in [min_uintptr - 1, max_uintptr + 1, min_intptr]:
|
||||
with pytest.raises(OverflowError):
|
||||
UIntPtr(v)
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче