Closed rbeurskens closed 1 year ago
Thanks for reporting this @rbeurskens. Don't worry about your repro code example making use of NSubstitute for now, I expect it shouldn't be too difficult figuring out the underlying calls and deriving DynamicProxy-only repro code.
OK, it's taken me a while, but here is a corresponding DynamicProxy-only repro code example:
var generator = new ProxyGenerator();
var factory = generator.CreateClassProxy<DerivedFactory>(new QueryMethodInvocationTargetInterceptor());
factory.Create<object>(out _);
class QueryMethodInvocationTargetInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
_ = invocation.MethodInvocationTarget;
}
}
public interface IFactory // NOTE: the issue is also reproducible without this interface
{
void Create<T>(out T result);
}
public class Factory : IFactory
{
public virtual void Create<T>(out T result) => result = default;
}
public class DerivedFactory : Factory
{
public override void Create<T>(out T result) => result = default;
}
It seems that the error is caused in this method: https://github.com/castleproject/Core/blob/dca4ed09df545dd7512c82778127219795668d30/src/Castle.Core/DynamicProxy/Generators/MethodSignatureComparer.cs#L104-L153
When given two matching generic types (or type parameters) by-ref (in our repro code: the out T result
parameters' types), this method claims that they do not match. This could possibly be fixed by introducing some new logic that erases by-ref-ness for further comparisons:
private bool EqualSignatureTypes(Type x, Type y)
{
if (x.IsGenericParameter != y.IsGenericParameter)
{
return false;
}
else if (x.IsGenericType != y.IsGenericType)
{
return false;
}
+ if (x.IsByRef != y.IsByRef)
+ {
+ return false;
+ }
+
+ if (x.IsByRef)
+ {
+ x = x.GetElementType();
+ y = y.GetElementType();
+ }
+
if (x.IsGenericParameter)
...
But I haven't checked yet whether this code breaks any existing test cases.
The suggested code addition does not appear to break any existing test cases, but we should still double-check its validity, and whether it can possibly be added in a more optimal place (from a run-time performance standpoint).
Scratch the code fix suggested above, the following would be more correct (because it doesn't skip the first two checks):
private bool EqualSignatureTypes(Type x, Type y)
{
+ if (x.IsByRef != y.IsByRef)
+ {
+ return false;
+ }
+ else if (x.IsByRef)
+ {
+ return EqualSignatureTypes(x.GetElementType(), y.GetElementType());
+ }
+
if (x.IsGenericParameter != y.IsGenericParameter)
...
Note that I encountered this using NSubstitute and I'm not using Castle.Core directly, but the stack trace comes from Castle.Core and tells me it is likely a bug and asks me to report it. (NSubstitute issue: https://github.com/nsubstitute/NSubstitute/issues/716 ) Version used is 5.0.0, running on .NET Framework 4.8
This occurs when trying to configure an interception for a (generic) method (with an out argument) that is overridden but not defined on the class being proxied.