Skip to content

Commit 9f3e32a

Browse files
lostmsuAlexCatarino
authored andcommitted
Safe pointers (pythonnet#1043)
* NewReference type and an example usage * BorrowedReference + example, that exposes dangerous pattern * make BorrowedReference readonly ref struct * BorrowedReference.Pointer is a private readonly field * renamed NewReference.ToPyObject to MoveToPyObject * removed public property Pointer from NewReference and replaced with DangerousGetAddress
1 parent abe9473 commit 9f3e32a

File tree

9 files changed

+96
-9
lines changed

9 files changed

+96
-9
lines changed

src/runtime/BorrowedReference.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
namespace Python.Runtime
2+
{
3+
using System;
4+
/// <summary>
5+
/// Represents a reference to a Python object, that is being lent, and
6+
/// can only be safely used until execution returns to the caller.
7+
/// </summary>
8+
readonly ref struct BorrowedReference
9+
{
10+
readonly IntPtr pointer;
11+
public bool IsNull => this.pointer == IntPtr.Zero;
12+
13+
/// <summary>Gets a raw pointer to the Python object</summary>
14+
public IntPtr DangerousGetAddress()
15+
=> this.IsNull ? throw new NullReferenceException() : this.pointer;
16+
17+
BorrowedReference(IntPtr pointer)
18+
{
19+
this.pointer = pointer;
20+
}
21+
}
22+
}

src/runtime/NewReference.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
namespace Python.Runtime
2+
{
3+
using System;
4+
/// <summary>
5+
/// Represents a reference to a Python object, that is tracked by Python's reference counting.
6+
/// </summary>
7+
[NonCopyable]
8+
ref struct NewReference
9+
{
10+
IntPtr pointer;
11+
public bool IsNull => this.pointer == IntPtr.Zero;
12+
13+
/// <summary>Gets a raw pointer to the Python object</summary>
14+
public IntPtr DangerousGetAddress()
15+
=> this.IsNull ? throw new NullReferenceException() : this.pointer;
16+
17+
/// <summary>
18+
/// Returns <see cref="PyObject"/> wrapper around this reference, which now owns
19+
/// the pointer. Sets the original reference to <c>null</c>, as it no longer owns it.
20+
/// </summary>
21+
public PyObject MoveToPyObject()
22+
{
23+
if (this.IsNull) throw new NullReferenceException();
24+
25+
var result = new PyObject(this.pointer);
26+
this.pointer = IntPtr.Zero;
27+
return result;
28+
}
29+
/// <summary>
30+
/// Removes this reference to a Python object, and sets it to <c>null</c>.
31+
/// </summary>
32+
public void Dispose()
33+
{
34+
if (!this.IsNull)
35+
Runtime.XDecref(this.pointer);
36+
this.pointer = IntPtr.Zero;
37+
}
38+
}
39+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace Python.Runtime
2+
{
3+
using System;
4+
[AttributeUsage(AttributeTargets.Struct)]
5+
class NonCopyableAttribute : Attribute { }
6+
}

src/runtime/Python.Runtime.15.csproj

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,13 @@
129129
<PackageReference Include="Microsoft.TargetingPack.NETFramework.v4.5" Version="1.0.1" ExcludeAssets="All" PrivateAssets="All" />
130130
</ItemGroup>
131131

132+
<ItemGroup>
133+
<PackageReference Include="NonCopyableAnalyzer" Version="0.5.1">
134+
<PrivateAssets>all</PrivateAssets>
135+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
136+
</PackageReference>
137+
</ItemGroup>
138+
132139
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
133140

134141
<PropertyGroup>

src/runtime/Python.Runtime.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
</Compile>
8686
<Compile Include="arrayobject.cs" />
8787
<Compile Include="assemblymanager.cs" />
88+
<Compile Include="BorrowedReference.cs" />
8889
<Compile Include="classderived.cs" />
8990
<Compile Include="classbase.cs" />
9091
<Compile Include="classmanager.cs" />
@@ -121,6 +122,8 @@
121122
<Compile Include="moduleobject.cs" />
122123
<Compile Include="modulepropertyobject.cs" />
123124
<Compile Include="nativecall.cs" />
125+
<Compile Include="NewReference.cs" />
126+
<Compile Include="NonCopyableAttribute.cs" />
124127
<Compile Include="overload.cs" />
125128
<Compile Include="propertyobject.cs" />
126129
<Compile Include="pyansistring.cs" />

src/runtime/assemblymanager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ internal static void UpdatePath()
188188
pypath.Add("");
189189
for (var i = 0; i < count; i++)
190190
{
191-
IntPtr item = Runtime.PyList_GetItem(list, i);
191+
BorrowedReference item = Runtime.PyList_GetItem(list, i);
192192
string path = Runtime.GetManagedString(item);
193193
if (path != null)
194194
{

src/runtime/methodbinder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth
303303
for (int i = 0; i < pynkwargs; ++i)
304304
{
305305
var keyStr = Runtime.GetManagedString(Runtime.PyList_GetItem(keylist, i));
306-
kwargDict[keyStr] = Runtime.PyList_GetItem(valueList, i);
306+
kwargDict[keyStr] = Runtime.PyList_GetItem(valueList, i).DangerousGetAddress();
307307
}
308308

309309
Runtime.XDecref(keylist);

src/runtime/pydict.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,12 +139,20 @@ public PyObject Values()
139139
/// </remarks>
140140
public PyObject Items()
141141
{
142-
IntPtr items = Runtime.PyDict_Items(obj);
143-
if (items == IntPtr.Zero)
142+
var items = Runtime.PyDict_Items(this.obj);
143+
try
144144
{
145-
throw new PythonException();
145+
if (items.IsNull)
146+
{
147+
throw new PythonException();
148+
}
149+
150+
return items.MoveToPyObject();
151+
}
152+
finally
153+
{
154+
items.Dispose();
146155
}
147-
return new PyObject(items);
148156
}
149157

150158

src/runtime/runtime.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1528,6 +1528,8 @@ internal static IntPtr PyUnicode_FromString(string s)
15281528
return PyUnicode_FromUnicode(s, s.Length);
15291529
}
15301530

1531+
internal static string GetManagedString(in BorrowedReference borrowedReference)
1532+
=> GetManagedString(borrowedReference.DangerousGetAddress());
15311533
/// <summary>
15321534
/// Function to access the internal PyUnicode/PyString object and
15331535
/// convert it to a managed string with the correct encoding.
@@ -1610,7 +1612,7 @@ internal static bool PyDict_Check(IntPtr ob)
16101612
internal static extern IntPtr PyDict_Values(IntPtr pointer);
16111613

16121614
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
1613-
internal static extern IntPtr PyDict_Items(IntPtr pointer);
1615+
internal static extern NewReference PyDict_Items(IntPtr pointer);
16141616

16151617
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
16161618
internal static extern IntPtr PyDict_Copy(IntPtr pointer);
@@ -1650,13 +1652,13 @@ internal static IntPtr PyList_New(long size)
16501652
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
16511653
internal static extern IntPtr PyList_AsTuple(IntPtr pointer);
16521654

1653-
internal static IntPtr PyList_GetItem(IntPtr pointer, long index)
1655+
internal static BorrowedReference PyList_GetItem(IntPtr pointer, long index)
16541656
{
16551657
return PyList_GetItem(pointer, new IntPtr(index));
16561658
}
16571659

16581660
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
1659-
private static extern IntPtr PyList_GetItem(IntPtr pointer, IntPtr index);
1661+
private static extern BorrowedReference PyList_GetItem(IntPtr pointer, IntPtr index);
16601662

16611663
internal static int PyList_SetItem(IntPtr pointer, long index, IntPtr value)
16621664
{

0 commit comments

Comments
 (0)