Skip to content

Commit 770fc01

Browse files
authored
Safe pointers (#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 4271e57 commit 770fc01

9 files changed

+97
-10
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+
}

src/runtime/NonCopyableAttribute.cs

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
@@ -83,6 +83,7 @@
8383
</Compile>
8484
<Compile Include="arrayobject.cs" />
8585
<Compile Include="assemblymanager.cs" />
86+
<Compile Include="BorrowedReference.cs" />
8687
<Compile Include="classderived.cs" />
8788
<Compile Include="classbase.cs" />
8889
<Compile Include="classmanager.cs" />
@@ -119,6 +120,8 @@
119120
<Compile Include="moduleobject.cs" />
120121
<Compile Include="modulepropertyobject.cs" />
121122
<Compile Include="nativecall.cs" />
123+
<Compile Include="NewReference.cs" />
124+
<Compile Include="NonCopyableAttribute.cs" />
122125
<Compile Include="overload.cs" />
123126
<Compile Include="propertyobject.cs" />
124127
<Compile Include="pyansistring.cs" />

src/runtime/assemblymanager.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ internal static void UpdatePath()
145145
probed.Clear();
146146
for (var i = 0; i < count; i++)
147147
{
148-
IntPtr item = Runtime.PyList_GetItem(list, i);
148+
BorrowedReference item = Runtime.PyList_GetItem(list, i);
149149
string path = Runtime.GetManagedString(item);
150150
if (path != null)
151151
{
@@ -492,4 +492,4 @@ internal static Type[] GetTypes(Assembly a)
492492
}
493493
}
494494
}
495-
}
495+
}

src/runtime/methodbinder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth
292292
for (int i = 0; i < pynkwargs; ++i)
293293
{
294294
var keyStr = Runtime.GetManagedString(Runtime.PyList_GetItem(keylist, i));
295-
kwargDict[keyStr] = Runtime.PyList_GetItem(valueList, i);
295+
kwargDict[keyStr] = Runtime.PyList_GetItem(valueList, i).DangerousGetAddress();
296296
}
297297
Runtime.XDecref(keylist);
298298
Runtime.XDecref(valueList);

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
@@ -1509,6 +1509,8 @@ internal static IntPtr PyUnicode_FromString(string s)
15091509
return PyUnicode_FromUnicode(s, s.Length);
15101510
}
15111511

1512+
internal static string GetManagedString(in BorrowedReference borrowedReference)
1513+
=> GetManagedString(borrowedReference.DangerousGetAddress());
15121514
/// <summary>
15131515
/// Function to access the internal PyUnicode/PyString object and
15141516
/// convert it to a managed string with the correct encoding.
@@ -1591,7 +1593,7 @@ internal static bool PyDict_Check(IntPtr ob)
15911593
internal static extern IntPtr PyDict_Values(IntPtr pointer);
15921594

15931595
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
1594-
internal static extern IntPtr PyDict_Items(IntPtr pointer);
1596+
internal static extern NewReference PyDict_Items(IntPtr pointer);
15951597

15961598
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
15971599
internal static extern IntPtr PyDict_Copy(IntPtr pointer);
@@ -1631,13 +1633,13 @@ internal static IntPtr PyList_New(long size)
16311633
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
16321634
internal static extern IntPtr PyList_AsTuple(IntPtr pointer);
16331635

1634-
internal static IntPtr PyList_GetItem(IntPtr pointer, long index)
1636+
internal static BorrowedReference PyList_GetItem(IntPtr pointer, long index)
16351637
{
16361638
return PyList_GetItem(pointer, new IntPtr(index));
16371639
}
16381640

16391641
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
1640-
private static extern IntPtr PyList_GetItem(IntPtr pointer, IntPtr index);
1642+
private static extern BorrowedReference PyList_GetItem(IntPtr pointer, IntPtr index);
16411643

16421644
internal static int PyList_SetItem(IntPtr pointer, long index, IntPtr value)
16431645
{

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy