Skip to content

Commit fc76122

Browse files
hazzikfredericDelaporte
authored andcommitted
Fix attempt of static proxies to call base method for abstract classes (#1884)
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. Also test the explicit interface case, check access into constructor, and check proxy on type with interfaces. And correctly call explicit interface methods of base type
1 parent 4c1e27f commit fc76122

File tree

2 files changed

+232
-16
lines changed

2 files changed

+232
-16
lines changed

src/NHibernate.Test/StaticProxyTest/StaticProxyFactoryFixture.cs

Lines changed: 152 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,57 @@ public class InternalInterfaceTestClass : IInternal
3333

3434
public interface IPublic
3535
{
36-
int Id { get; }
36+
int Id { get; set; }
37+
string Name { get; set; }
3738
}
3839

3940
[Serializable]
4041
public class PublicInterfaceTestClass : IPublic
4142
{
4243
public virtual int Id { get; set; }
44+
public virtual string Name { get; set; }
45+
46+
public PublicInterfaceTestClass()
47+
{
48+
// Check access to properties from the default constructor do not fail once proxified
49+
Id = -1;
50+
Assert.That(Id, Is.EqualTo(-1));
51+
Name = "Unknown";
52+
Assert.That(Name, Is.EqualTo("Unknown"));
53+
}
54+
}
55+
56+
[Serializable]
57+
public class PublicExplicitInterfaceTestClass : IPublic
58+
{
59+
int IPublic.Id { get; set; }
60+
string IPublic.Name { get; set; }
61+
62+
public PublicExplicitInterfaceTestClass()
63+
{
64+
// Check access to properties from the default constructor do not fail once proxified
65+
IPublic pub = this;
66+
pub.Id = -1;
67+
Assert.That(pub.Id, Is.EqualTo(-1));
68+
pub.Name = "Unknown";
69+
Assert.That(pub.Name, Is.EqualTo("Unknown"));
70+
}
71+
}
72+
73+
[Serializable]
74+
public abstract class AbstractTestClass : IPublic
75+
{
76+
protected AbstractTestClass()
77+
{
78+
Id = -1;
79+
Assert.That(Id, Is.Zero);
80+
Name = "Unknown";
81+
Assert.That(Name, Is.Null);
82+
}
83+
84+
public abstract int Id { get; set; }
85+
86+
public abstract string Name { get; set; }
4387
}
4488

4589
[Serializable]
@@ -156,8 +200,8 @@ public void VerifyProxyForClassWithAdditionalInterface()
156200
// By way of the "proxy" attribute on the "class" mapping, an interface to use for the
157201
// lazy entity load proxy instead of the persistentClass can be specified. This is "translated" into
158202
// having an additional interface in the interface list, instead of just having INHibernateProxy.
159-
// (Quite a loosy semantic...)
160-
new HashSet<System.Type> {typeof(INHibernateProxy), typeof(IPublic)},
203+
// (Quite a loose semantic...)
204+
new HashSet<System.Type> { typeof(INHibernateProxy), typeof(IPublic) },
161205
null, null, null);
162206

163207
#if NETFX
@@ -174,6 +218,85 @@ public void VerifyProxyForClassWithAdditionalInterface()
174218
#endif
175219
}
176220

221+
[Test]
222+
public void VerifyProxyForClassWithInterface()
223+
{
224+
var factory = new StaticProxyFactory();
225+
factory.PostInstantiate(
226+
typeof(PublicInterfaceTestClass).FullName,
227+
typeof(PublicInterfaceTestClass),
228+
new HashSet<System.Type> {typeof(INHibernateProxy)},
229+
null, null, null);
230+
231+
#if NETFX
232+
VerifyGeneratedAssembly(
233+
() =>
234+
{
235+
#endif
236+
var proxy = factory.GetProxy(1, null);
237+
Assert.That(proxy, Is.Not.Null);
238+
Assert.That(proxy, Is.InstanceOf<IPublic>());
239+
Assert.That(proxy, Is.InstanceOf<PublicInterfaceTestClass>());
240+
241+
// Check interface and implicit implementations do both call the delegated state
242+
var state = new PublicInterfaceTestClass { Id = 5, Name = "State" };
243+
proxy.HibernateLazyInitializer.SetImplementation(state);
244+
var pub = (IPublic) proxy;
245+
var ent = (PublicInterfaceTestClass) proxy;
246+
Assert.That(pub.Id, Is.EqualTo(5), "IPublic.Id");
247+
Assert.That(ent.Id, Is.EqualTo(5), "entity.Id");
248+
Assert.That(pub.Name, Is.EqualTo("State"), "IPublic.Name");
249+
Assert.That(ent.Name, Is.EqualTo("State"), "entity.Name");
250+
ent.Id = 10;
251+
pub.Name = "Test";
252+
Assert.That(pub.Id, Is.EqualTo(10), "IPublic.Id");
253+
Assert.That(state.Id, Is.EqualTo(10), "state.Id");
254+
Assert.That(ent.Name, Is.EqualTo("Test"), "entity.Name");
255+
Assert.That(state.Name, Is.EqualTo("Test"), "state.Name");
256+
#if NETFX
257+
});
258+
#endif
259+
}
260+
261+
[Test]
262+
public void VerifyProxyForClassWithExplicitInterface()
263+
{
264+
var factory = new StaticProxyFactory();
265+
factory.PostInstantiate(
266+
typeof(PublicExplicitInterfaceTestClass).FullName,
267+
typeof(PublicExplicitInterfaceTestClass),
268+
new HashSet<System.Type> {typeof(INHibernateProxy)},
269+
null, null, null);
270+
#if NETFX
271+
VerifyGeneratedAssembly(
272+
() =>
273+
{
274+
#endif
275+
var proxy = factory.GetProxy(1, null);
276+
Assert.That(proxy, Is.Not.Null);
277+
Assert.That(proxy, Is.InstanceOf<IPublic>());
278+
Assert.That(proxy, Is.InstanceOf<PublicExplicitInterfaceTestClass>());
279+
280+
// Check interface and implicit implementations do both call the delegated state
281+
IPublic state = new PublicExplicitInterfaceTestClass();
282+
state.Id = 5;
283+
state.Name = "State";
284+
proxy.HibernateLazyInitializer.SetImplementation(state);
285+
var entity = (IPublic) proxy;
286+
Assert.That(entity.Id, Is.EqualTo(5), "Id");
287+
Assert.That(entity.Name, Is.EqualTo("State"), "Name");
288+
289+
entity.Id = 10;
290+
entity.Name = "Test";
291+
Assert.That(entity.Id, Is.EqualTo(10), "entity.Id");
292+
Assert.That(state.Id, Is.EqualTo(10), "state.Id");
293+
Assert.That(entity.Name, Is.EqualTo("Test"), "entity.Name");
294+
Assert.That(state.Name, Is.EqualTo("Test"), "state.Name");
295+
#if NETFX
296+
});
297+
#endif
298+
}
299+
177300
[Test]
178301
public void VerifyProxyForRefOutClass()
179302
{
@@ -219,6 +342,30 @@ public void VerifyProxyForRefOutClass()
219342
#endif
220343
}
221344

345+
[Test]
346+
public void VerifyProxyForAbstractClass()
347+
{
348+
var factory = new StaticProxyFactory();
349+
factory.PostInstantiate(
350+
typeof(AbstractTestClass).FullName,
351+
typeof(AbstractTestClass),
352+
new HashSet<System.Type> { typeof(INHibernateProxy) },
353+
null, null, null);
354+
355+
#if NETFX
356+
VerifyGeneratedAssembly(
357+
() =>
358+
{
359+
#endif
360+
var proxy = factory.GetProxy(1, null);
361+
Assert.That(proxy, Is.Not.Null);
362+
Assert.That(proxy, Is.InstanceOf<IPublic>());
363+
Assert.That(proxy, Is.InstanceOf<AbstractTestClass>());
364+
#if NETFX
365+
});
366+
#endif
367+
}
368+
222369
[Test]
223370
public void InitializedProxyStaysInitializedAfterDeserialization()
224371
{
@@ -369,10 +516,10 @@ public void VerifyFieldInterceptorProxyWithAdditionalInterface()
369516
// By way of the "proxy" attribute on the "class" mapping, an interface to use for the
370517
// lazy entity load proxy instead of the persistentClass can be specified. This is "translated" into
371518
// having an additional interface in the interface list, instead of just having INHibernateProxy.
372-
// (Quite a loosy semantic...)
519+
// (Quite a loose semantic...)
373520
// The field interceptor proxy ignores this setting, as it does not delegate its implementation
374521
// to an instance of the persistentClass, and so cannot implement interface methods if it does not
375-
// inherit the persitentClass.
522+
// inherit the persistentClass.
376523
new HashSet<System.Type> {typeof(INHibernateProxy), typeof(IPublic)},
377524
null, null, null);
378525
#if NETFX

src/NHibernate/Proxy/NHibernateProxyBuilder.cs

Lines changed: 80 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using System.Linq.Expressions;
45
using System.Reflection;
56
using System.Reflection.Emit;
67
using System.Runtime.Serialization;
@@ -366,19 +367,20 @@ private static void ImplementCallMethodOnImplementation(
366367
}
367368

368369
private static void EmitCallBaseIfLazyInitializerIsNull(
369-
ILGenerator IL, MethodInfo method, FieldInfo lazyInitializerField, System.Type parentType)
370+
ILGenerator IL,
371+
MethodInfo method,
372+
FieldInfo lazyInitializerField,
373+
System.Type parentType)
370374
{
371375
/*
372-
<if (method.DeclaringType.IsAssignableFrom(parentType))
373-
{>
374376
if (this.__lazyInitializer == null)
375-
return base.<method>(args..)
377+
<if (method.IsAbstract)
378+
{>
379+
return default;
380+
<} else {>
381+
return base.<method>(args..);
376382
<}>
377383
*/
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;
382384

383385
// When deriving from the entity class, the entity class constructor may trigger
384386
// virtual calls accessing the proxy state before its own constructor has a chance
@@ -393,9 +395,76 @@ private static void EmitCallBaseIfLazyInitializerIsNull(
393395
IL.Emit(OpCodes.Ldnull);
394396
IL.Emit(OpCodes.Bne_Un, skipBaseCall);
395397

396-
IL.Emit(OpCodes.Ldarg_0);
397-
EmitCallMethod(IL, OpCodes.Call, method);
398-
IL.Emit(OpCodes.Ret);
398+
if (method.DeclaringType.IsInterface &&
399+
method.DeclaringType.IsAssignableFrom(parentType))
400+
{
401+
var interfaceMap = parentType.GetInterfaceMap(method.DeclaringType);
402+
var methodIndex = Array.IndexOf(interfaceMap.InterfaceMethods, method);
403+
method = interfaceMap.TargetMethods[methodIndex];
404+
}
405+
406+
if (method.IsAbstract)
407+
{
408+
/*
409+
* return default(<ReturnType>);
410+
*/
411+
412+
if (!method.ReturnType.IsValueType)
413+
{
414+
IL.Emit(OpCodes.Ldnull);
415+
}
416+
else if (method.ReturnType != typeof(void))
417+
{
418+
var local = IL.DeclareLocal(method.ReturnType);
419+
IL.Emit(OpCodes.Ldloca, local);
420+
IL.Emit(OpCodes.Initobj, method.ReturnType);
421+
IL.Emit(OpCodes.Ldloc, local);
422+
}
423+
424+
IL.Emit(OpCodes.Ret);
425+
}
426+
else if (method.IsPrivate)
427+
{
428+
/*
429+
* var mi = (MethodInfo)MethodBase.GetMethodFromHandle(<method>, <parentType>);
430+
* var delegate = (<delegateType>)mi.CreateDelegate(typeof(<delegateType>), this);
431+
* delegate.Invoke(args...);
432+
*/
433+
434+
var parameters = method.GetParameters();
435+
436+
var delegateType = Expression.GetDelegateType(
437+
parameters.Select(p => p.ParameterType).Concat(new[] {method.ReturnType}).ToArray());
438+
439+
var invokeDelegate = delegateType.GetMethod("Invoke");
440+
441+
IL.Emit(OpCodes.Ldtoken, method);
442+
IL.Emit(OpCodes.Ldtoken, parentType);
443+
IL.Emit(OpCodes.Call, ReflectionCache.MethodBaseMethods.GetMethodFromHandleWithDeclaringType);
444+
IL.Emit(OpCodes.Castclass, typeof(MethodInfo));
445+
IL.Emit(OpCodes.Ldtoken, delegateType);
446+
IL.Emit(OpCodes.Call, ReflectionCache.TypeMethods.GetTypeFromHandle);
447+
IL.Emit(OpCodes.Ldarg_0);
448+
IL.Emit(
449+
OpCodes.Callvirt,
450+
typeof(MethodInfo).GetMethod(
451+
nameof(MethodInfo.CreateDelegate),
452+
new[] {typeof(System.Type), typeof(object)}));
453+
IL.Emit(OpCodes.Castclass, delegateType);
454+
455+
EmitCallMethod(IL, OpCodes.Callvirt, invokeDelegate);
456+
IL.Emit(OpCodes.Ret);
457+
}
458+
else
459+
{
460+
/*
461+
* base.<method>(args...);
462+
*/
463+
464+
IL.Emit(OpCodes.Ldarg_0);
465+
EmitCallMethod(IL, OpCodes.Call, method);
466+
IL.Emit(OpCodes.Ret);
467+
}
399468

400469
IL.MarkLabel(skipBaseCall);
401470
}

0 commit comments

Comments
 (0)