Skip to content

Commit 73cc71c

Browse files
committed
Fix attempt to call base method for abstract classes
Previously it was failing PEVerify similarly to #1728 but for abstract classes (this is possible in case of a polymorphic entities). Unlike interfaces the abstract base class can go into a situation when lazy initializer is not yet available, eg. code in a constructor.
1 parent 41782de commit 73cc71c

File tree

2 files changed

+68
-11
lines changed

2 files changed

+68
-11
lines changed

src/NHibernate.Test/StaticProxyTest/StaticProxyFactoryFixture.cs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,22 @@ public class PublicInterfaceTestClass : IPublic
4242
public virtual int Id { get; set; }
4343
}
4444

45+
[Serializable]
46+
public abstract class AbstractTestClass : IPublic
47+
{
48+
protected AbstractTestClass()
49+
{
50+
Assert.That(Id, Is.Zero);
51+
Assert.That(Name, Is.Null);
52+
Id = -1;
53+
Name = "Unknown";
54+
}
55+
56+
public abstract int Id { get; set; }
57+
58+
public abstract string Name { get; set; }
59+
}
60+
4561
[Serializable]
4662
public class SimpleTestClass
4763
{
@@ -157,7 +173,7 @@ public void VerifyProxyForClassWithAdditionalInterface()
157173
// lazy entity load proxy instead of the persistentClass can be specified. This is "translated" into
158174
// having an additional interface in the interface list, instead of just having INHibernateProxy.
159175
// (Quite a loosy semantic...)
160-
new HashSet<System.Type> {typeof(INHibernateProxy), typeof(IPublic)},
176+
new HashSet<System.Type> { typeof(INHibernateProxy), typeof(IPublic) },
161177
null, null, null);
162178

163179
#if NETFX
@@ -219,6 +235,30 @@ public void VerifyProxyForRefOutClass()
219235
#endif
220236
}
221237

238+
[Test]
239+
public void VerifyProxyForAbstractClass()
240+
{
241+
var factory = new StaticProxyFactory();
242+
factory.PostInstantiate(
243+
typeof(AbstractTestClass).FullName,
244+
typeof(AbstractTestClass),
245+
new HashSet<System.Type> { typeof(INHibernateProxy) },
246+
null, null, null);
247+
248+
#if NETFX
249+
VerifyGeneratedAssembly(
250+
() =>
251+
{
252+
#endif
253+
var proxy = factory.GetProxy(1, null);
254+
Assert.That(proxy, Is.Not.Null);
255+
Assert.That(proxy, Is.InstanceOf<IPublic>());
256+
Assert.That(proxy, Is.InstanceOf<AbstractTestClass>());
257+
#if NETFX
258+
});
259+
#endif
260+
}
261+
222262
[Test]
223263
public void InitializedProxyStaysInitializedAfterDeserialization()
224264
{

src/NHibernate/Proxy/NHibernateProxyBuilder.cs

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -369,16 +369,14 @@ private static void EmitCallBaseIfLazyInitializerIsNull(
369369
ILGenerator IL, MethodInfo method, FieldInfo lazyInitializerField, System.Type parentType)
370370
{
371371
/*
372-
<if (method.DeclaringType.IsAssignableFrom(parentType))
373-
{>
374372
if (this.__lazyInitializer == null)
375-
return base.<method>(args..)
373+
<if (method.IsAbstract)
374+
{>
375+
return default;
376+
<} else {>
377+
return base.<method>(args..);
376378
<}>
377379
*/
378-
if (!method.DeclaringType.IsAssignableFrom(parentType))
379-
// The proxy does not derive from a type implementing the method, do not attempt
380-
// calling its base. In such case, the lazy initializer is never null.
381-
return;
382380

383381
// When deriving from the entity class, the entity class constructor may trigger
384382
// virtual calls accessing the proxy state before its own constructor has a chance
@@ -393,9 +391,28 @@ private static void EmitCallBaseIfLazyInitializerIsNull(
393391
IL.Emit(OpCodes.Ldnull);
394392
IL.Emit(OpCodes.Bne_Un, skipBaseCall);
395393

396-
IL.Emit(OpCodes.Ldarg_0);
397-
EmitCallMethod(IL, OpCodes.Call, method);
398-
IL.Emit(OpCodes.Ret);
394+
if (method.IsAbstract)
395+
{
396+
if (!method.ReturnType.IsValueType)
397+
{
398+
IL.Emit(OpCodes.Ldnull);
399+
}
400+
else if (method.ReturnType != typeof(void))
401+
{
402+
var local = IL.DeclareLocal(method.ReturnType);
403+
IL.Emit(OpCodes.Ldloca, local);
404+
IL.Emit(OpCodes.Initobj, method.ReturnType);
405+
IL.Emit(OpCodes.Ldloc, local);
406+
}
407+
408+
IL.Emit(OpCodes.Ret);
409+
}
410+
else
411+
{
412+
IL.Emit(OpCodes.Ldarg_0);
413+
EmitCallMethod(IL, OpCodes.Call, method);
414+
IL.Emit(OpCodes.Ret);
415+
}
399416

400417
IL.MarkLabel(skipBaseCall);
401418
}

0 commit comments

Comments
 (0)