Skip to content

Commit f857d59

Browse files
committed
When reflecting nested types, ensure their corresponding PyType is allocated.
fixes #1414 Without this fix attempting to use a nested .NET class that derives from its parent would cause a crash due to false mutual dependency.
1 parent 748d3d7 commit f857d59

File tree

3 files changed

+89
-24
lines changed

3 files changed

+89
-24
lines changed

src/embed_tests/ClassManagerTests.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using NUnit.Framework;
2+
3+
using Python.Runtime;
4+
5+
namespace Python.EmbeddingTest
6+
{
7+
public class ClassManagerTests
8+
{
9+
[OneTimeSetUp]
10+
public void SetUp()
11+
{
12+
PythonEngine.Initialize();
13+
}
14+
15+
[OneTimeTearDown]
16+
public void Dispose()
17+
{
18+
PythonEngine.Shutdown();
19+
}
20+
21+
[Test]
22+
public void NestedClassDerivingFromParent()
23+
{
24+
var f = new NestedTestContainer().ToPython();
25+
f.GetAttr(nameof(NestedTestContainer.Bar));
26+
}
27+
}
28+
29+
public class NestedTestParent
30+
{
31+
public class Nested : NestedTestParent
32+
{
33+
}
34+
}
35+
36+
public class NestedTestContainer
37+
{
38+
public NestedTestParent Bar = new NestedTestParent.Nested();
39+
}
40+
}

src/runtime/classmanager.cs

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
using System;
22
using System.Collections;
33
using System.Collections.Generic;
4+
using System.Diagnostics;
5+
using System.Linq;
46
using System.Reflection;
57
using System.Runtime.InteropServices;
68
using System.Security;
7-
using System.Linq;
89

910
namespace Python.Runtime
1011
{
@@ -151,8 +152,12 @@ internal static Dictionary<ManagedType, InterDomainContext> RestoreRuntimeData(R
151152
invalidClasses.Add(pair);
152153
continue;
153154
}
155+
// Ensure, that matching Python type exists first.
156+
// It is required for self-referential classes
157+
// (e.g. with members, that refer to the same class)
158+
var pyType = InitPyType(pair.Key.Value, pair.Value);
154159
// re-init the class
155-
InitClassBase(pair.Key.Value, pair.Value);
160+
InitClassBase(pair.Key.Value, pair.Value, pyType);
156161
// We modified the Type object, notify it we did.
157162
Runtime.PyType_Modified(pair.Value.TypeReference);
158163
var context = contexts[pair.Value.pyHandle];
@@ -184,9 +189,13 @@ internal static ClassBase GetClass(Type type)
184189
}
185190
cb = CreateClass(type);
186191
cache.Add(type, cb);
192+
// Ensure, that matching Python type exists first.
193+
// It is required for self-referential classes
194+
// (e.g. with members, that refer to the same class)
195+
var pyType = InitPyType(type, cb);
187196
// Initialize the object later, as this might call this GetClass method
188197
// recursively (for example when a nested class inherits its declaring class...)
189-
InitClassBase(type, cb);
198+
InitClassBase(type, cb, pyType);
190199
return cb;
191200
}
192201

@@ -249,16 +258,18 @@ private static ClassBase CreateClass(Type type)
249258
return impl;
250259
}
251260

252-
private static void InitClassBase(Type type, ClassBase impl)
261+
private static PyType InitPyType(Type type, ClassBase impl)
253262
{
254-
// Ensure, that matching Python type exists first.
255-
// It is required for self-referential classes
256-
// (e.g. with members, that refer to the same class)
257263
var pyType = TypeManager.GetOrCreateClass(type);
258264

259265
// Set the handle attributes on the implementing instance.
260266
impl.tpHandle = impl.pyHandle = pyType.Handle;
261267

268+
return pyType;
269+
}
270+
271+
private static void InitClassBase(Type type, ClassBase impl, PyType pyType)
272+
{
262273
// First, we introspect the managed type and build some class
263274
// information, including generating the member descriptors
264275
// that we'll be putting in the Python class __dict__.
@@ -549,6 +560,11 @@ private static ClassInfo GetClassInfo(Type type)
549560
}
550561
// Note the given instance might be uninitialized
551562
ob = GetClass(tp);
563+
if (ob.pyHandle == IntPtr.Zero && ob is ClassObject)
564+
{
565+
ob.pyHandle = ob.tpHandle = TypeManager.GetOrCreateClass(tp).Handle;
566+
}
567+
Debug.Assert(ob.pyHandle != IntPtr.Zero);
552568
// GetClass returns a Borrowed ref. ci.members owns the reference.
553569
ob.IncrRefCount();
554570
ci.members[mi.Name] = ob;

src/runtime/typemanager.cs

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,17 @@ internal static PyType GetOrCreateClass(Type type)
146146
{
147147
if (!cache.TryGetValue(type, out var pyType))
148148
{
149-
pyType = CreateClass(type);
149+
pyType = AllocateClass(type);
150+
cache.Add(type, pyType);
151+
try
152+
{
153+
InitializeClass(type, pyType);
154+
}
155+
catch
156+
{
157+
cache.Remove(type);
158+
throw;
159+
}
150160
}
151161
return pyType;
152162
}
@@ -209,12 +219,25 @@ internal static unsafe PyType CreateType(Type impl)
209219
}
210220

211221

212-
static PyType CreateClass(Type clrType)
222+
static void InitializeClass(Type clrType, PyType pyType)
213223
{
214-
string name = GetPythonTypeName(clrType);
224+
if (pyType.BaseReference != null)
225+
{
226+
return;
227+
}
215228

216229
using var baseTuple = GetBaseTypeTuple(clrType);
217230

231+
InitializeBases(pyType, baseTuple);
232+
// core fields must be initialized in partially constructed classes,
233+
// otherwise it would be impossible to manipulate GCHandle and check type size
234+
InitializeCoreFields(pyType);
235+
}
236+
237+
static PyType AllocateClass(Type clrType)
238+
{
239+
string name = GetPythonTypeName(clrType);
240+
218241
IntPtr type = AllocateTypeObject(name, Runtime.PyCLRMetaType);
219242
var pyType = new PyType(StolenReference.DangerousFromPointer(type));
220243
pyType.Flags = TypeFlags.Default
@@ -223,20 +246,6 @@ static PyType CreateClass(Type clrType)
223246
| TypeFlags.BaseType
224247
| TypeFlags.HaveGC;
225248

226-
cache.Add(clrType, pyType);
227-
try
228-
{
229-
InitializeBases(pyType, baseTuple);
230-
// core fields must be initialized in partically constructed classes,
231-
// otherwise it would be impossible to manipulate GCHandle and check type size
232-
InitializeCoreFields(pyType);
233-
}
234-
catch
235-
{
236-
cache.Remove(clrType);
237-
throw;
238-
}
239-
240249
return pyType;
241250
}
242251

0 commit comments

Comments
 (0)