Skip to content

Commit

Permalink
Use Castle to generate delegate proxies
Browse files Browse the repository at this point in the history
  • Loading branch information
zvirja committed Apr 13, 2019
1 parent 6aeaca3 commit 1e665fd
Show file tree
Hide file tree
Showing 14 changed files with 117 additions and 182 deletions.
6 changes: 0 additions & 6 deletions src/NSubstitute/AssemblyProperties.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using NSubstitute.Core.Arguments;
using NSubstitute.Proxies;
using NSubstitute.Proxies.CastleDynamicProxy;
using NSubstitute.Proxies.DelegateProxy;
using NSubstitute.Routing;
using NSubstitute.Routing.AutoValues;

Expand Down Expand Up @@ -39,10 +37,7 @@ private static INSubContainer CreateDefaultContainer()
.RegisterPerScope<ISubstituteFactory, SubstituteFactory>()
.RegisterPerScope<ICallRouterResolver, CallRouterResolver>()
.RegisterPerScope<ISubstitutionContext, SubstitutionContext>()
.RegisterPerScope<CastleDynamicProxyFactory, CastleDynamicProxyFactory>()
.RegisterPerScope<DelegateProxyFactory, DelegateProxyFactory>()
.RegisterPerScope<IProxyFactory>(r =>
new ProxyFactory(r.Resolve<DelegateProxyFactory>(), r.Resolve<CastleDynamicProxyFactory>()))
.RegisterPerScope<IProxyFactory, CastleDynamicProxyFactory>()
.RegisterPerScope<ICallFactory, CallFactory>()
.RegisterPerScope<IPropertyHelper, PropertyHelper>()
.RegisterSingleton<IReceivedCallsExceptionThrower, ReceivedCallsExceptionThrower>();
Expand Down
17 changes: 17 additions & 0 deletions src/NSubstitute/Core/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,23 @@ public static string Join(this IEnumerable<string> strings, string separator)
return string.Join(separator, strings);
}

public static bool IsDelegate(this Type type)
{
// From CLR via C# (see full answer here: https://stackoverflow.com/a/4833071/2009373)
// > The System.MulticastDelegate class is derived from System.Delegate,
// > which is itself derived from System.Object.
// > The reason why there are two delegate classes is historical and unfortunate;
// > there should be just one delegate class in the FCL.
// > Sadly, you need to be aware of both of these classes
// > because even though all delegate types you create have MulticastDelegate as a base class,
// > you'll occasionally manipulate your delegate types by using methods defined by the Delegate class
// > instead of the MulticastDelegate class.
//
// Basically, MulticastDelegate and Delegate mean the same, but using MulticastDelegate for base check
// is slightly faster, as internally type.BaseType is walked in a loop and MulticastDelegate is reached faster.
return type.GetTypeInfo().IsSubclassOf(typeof(MulticastDelegate));
}

private static bool TypeCanBeNull(Type type)
{
return !type.GetTypeInfo().IsValueType || Nullable.GetUnderlyingType(type) != null;
Expand Down
11 changes: 1 addition & 10 deletions src/NSubstitute/Core/MethodFormatter.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using NSubstitute.Proxies.DelegateProxy;

namespace NSubstitute.Core
{
Expand All @@ -15,10 +14,7 @@ public bool CanFormat(MethodInfo methodInfo)
public string Format(MethodInfo methodInfo, IEnumerable<string> arguments)
{
var args = string.Join(", ", arguments);

return IsDelegateProxy(methodInfo)
? string.Format("Invoke({0})", args)
: string.Format("{0}{1}({2})", methodInfo.Name, FormatGenericType(methodInfo), args);
return $"{methodInfo.Name}{FormatGenericType(methodInfo)}({args})";
}

private string FormatGenericType(MethodInfo methodInfoOfCall)
Expand All @@ -27,10 +23,5 @@ private string FormatGenericType(MethodInfo methodInfoOfCall)
var genericArgs = methodInfoOfCall.GetGenericArguments();
return "<" + string.Join(", ", genericArgs.Select(x => x.GetNonMangledTypeName()).ToArray()) + ">";
}

private static bool IsDelegateProxy(MethodInfo methodInfo)
{
return methodInfo.GetCustomAttribute<ProxiedDelegateTypeAttribute>() != null;
}
}
}
6 changes: 1 addition & 5 deletions src/NSubstitute/Core/SequenceChecking/SequenceFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Linq;
using System.Reflection;
using NSubstitute.Core.Arguments;
using NSubstitute.Proxies.DelegateProxy;

namespace NSubstitute.Core.SequenceChecking
{
Expand Down Expand Up @@ -100,10 +99,7 @@ public string Format(bool multipleInstances, bool includeInstanceNumber)

var instanceIdentifier = includeInstanceNumber ? _instanceNumber + "@" : "";

var delegateType = MethodInfo.GetCustomAttribute<ProxiedDelegateTypeAttribute>()?.DelegateType;
var declaringTypeName = delegateType != null
? delegateType.GetNonMangledTypeName()
: MethodInfo.DeclaringType.Name;
var declaringTypeName = MethodInfo.DeclaringType.GetNonMangledTypeName();
return string.Format("{1}{0}.{2}", declaringTypeName, instanceIdentifier, call);
}

Expand Down
4 changes: 2 additions & 2 deletions src/NSubstitute/Core/SubstituteFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,14 @@ private object Create(Type[] typesToProxy, object[] constructorArguments, bool c

private static Type GetPrimaryProxyType(Type[] typesToProxy)
{
return typesToProxy.FirstOrDefault(t => t.GetTypeInfo().IsSubclassOf(typeof(Delegate)))
return typesToProxy.FirstOrDefault(t => t.IsDelegate())
?? typesToProxy.FirstOrDefault(t => t.GetTypeInfo().IsClass)
?? typesToProxy.First();
}

private static bool CanCallBaseImplementation(Type primaryProxyType)
{
var isDelegate = primaryProxyType.GetTypeInfo().IsSubclassOf(typeof(Delegate));
var isDelegate = primaryProxyType.IsDelegate();
var isClass = primaryProxyType.GetTypeInfo().IsClass;

return isClass && !isDelegate;
Expand Down
10 changes: 0 additions & 10 deletions src/NSubstitute/IsReadOnlyAttribute.cs

This file was deleted.

2 changes: 1 addition & 1 deletion src/NSubstitute/NSubstitute.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Castle.Core" Version="4.3.1-*" />
<PackageReference Include="Castle.Core" Version="4.4.0-*" />
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.3.0-*" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using Castle.DynamicProxy;
using NSubstitute.Core;
using NSubstitute.Exceptions;
Expand All @@ -24,17 +23,21 @@ public CastleDynamicProxyFactory(ICallFactory callFactory, IArgumentSpecificatio
}

public object GenerateProxy(ICallRouter callRouter, Type typeToProxy, Type[] additionalInterfaces, object[] constructorArguments)
{
return typeToProxy.IsDelegate()
? GenerateDelegateProxy(callRouter, typeToProxy, additionalInterfaces, constructorArguments)
: GenerateTypeProxy(callRouter, typeToProxy, additionalInterfaces, constructorArguments);
}

private object GenerateTypeProxy(ICallRouter callRouter, Type typeToProxy, Type[] additionalInterfaces, object[] constructorArguments)
{
VerifyClassHasNotBeenPassedAsAnAdditionalInterface(additionalInterfaces);

var proxyIdInterceptor = new ProxyIdInterceptor(typeToProxy);
var forwardingInterceptor = new CastleForwardingInterceptor(
new CastleInvocationMapper(
_callFactory,
_argSpecificationDequeue),
callRouter);
var forwardingInterceptor = CreateForwardingInterceptor(callRouter);

var proxyGenerationOptions = GetOptionsToMixinCallRouterProvider(callRouter);

var proxy = CreateProxyUsingCastleProxyGenerator(
typeToProxy,
additionalInterfaces,
Expand All @@ -46,13 +49,39 @@ public object GenerateProxy(ICallRouter callRouter, Type typeToProxy, Type[] add
return proxy;
}

/// <summary>
/// Dynamically define the type with specified <paramref name="typeName"/>.
/// </summary>
public TypeBuilder DefineDynamicType(string typeName, TypeAttributes flags)
private object GenerateDelegateProxy(ICallRouter callRouter, Type delegateType, Type[] additionalInterfaces, object[] constructorArguments)
{
VerifyNoAdditionalInterfacesGivenForDelegate(additionalInterfaces);
VerifyNoConstructorArgumentsGivenForDelegate(constructorArguments);

var forwardingInterceptor = CreateForwardingInterceptor(callRouter);
// Keep this interceptor, so that real proxy ID can be retrieved by proxy.Target.ToString().
var proxyIdInterceptor = new ProxyIdInterceptor(delegateType);

var proxyGenerationOptions = GetOptionsToMixinCallRouterProvider(callRouter);
proxyGenerationOptions.AddDelegateTypeMixin(delegateType);

var proxy = CreateProxyUsingCastleProxyGenerator(
typeToProxy: typeof(object),
additionalInterfaces: null,
constructorArguments: null,
interceptors: new IInterceptor[] {proxyIdInterceptor, forwardingInterceptor},
proxyGenerationOptions);

forwardingInterceptor.SwitchToFullDispatchMode();

// Ideally we should use ProxyUtil.CreateDelegateToMixin(proxy, delegateType).
// But it's slower than code below due to extra checks it performs.
return proxy.GetType().GetMethod("Invoke").CreateDelegate(delegateType, proxy);
}

private CastleForwardingInterceptor CreateForwardingInterceptor(ICallRouter callRouter)
{
// It's important to use the signed module, as we exposed internals type to the signed module.
return _proxyGenerator.ProxyBuilder.ModuleScope.DefineType(inSignedModulePreferably: true, typeName, flags);
return new CastleForwardingInterceptor(
new CastleInvocationMapper(
_callFactory,
_argSpecificationDequeue),
callRouter);
}

private object CreateProxyUsingCastleProxyGenerator(Type typeToProxy, Type[] additionalInterfaces,
Expand Down Expand Up @@ -101,19 +130,44 @@ private ProxyGenerationOptions GetOptionsToMixinCallRouterProvider(ICallRouter c

private static void VerifyNoConstructorArgumentsGivenForInterface(object[] constructorArguments)
{
if (constructorArguments != null && constructorArguments.Length > 0)
if (HasItems(constructorArguments))
{
throw new SubstituteException("Can not provide constructor arguments when substituting for an interface.");
}
}

private static void VerifyNoConstructorArgumentsGivenForDelegate(object[] constructorArguments)
{
if (HasItems(constructorArguments))
{
throw new SubstituteException("Can not provide constructor arguments when substituting for a delegate.");
}
}

private static void VerifyNoAdditionalInterfacesGivenForDelegate(Type[] constructorArguments)
{
if (HasItems(constructorArguments))
{
throw new SubstituteException(
"Can not specify additional interfaces when substituting for a delegate. " +
"You must specify only a single delegate type if you need to substitute for a delegate.");
}
}

private static void VerifyClassHasNotBeenPassedAsAnAdditionalInterface(Type[] additionalInterfaces)
{
if (additionalInterfaces != null && additionalInterfaces.Any(x => x.GetTypeInfo().IsClass))
{
throw new SubstituteException("Can not substitute for multiple classes. To substitute for multiple types only one type can be a concrete class; other types can only be interfaces.");
throw new SubstituteException(
"Can not substitute for multiple classes. " +
"To substitute for multiple types only one type can be a concrete class; other types can only be interfaces.");
}
}

private static bool HasItems<T>(T[] array)
{
return array != null && array.Length > 0;
}

private class AllMethodsExceptCallRouterCallsHook : AllMethodsHook
{
Expand Down
Loading

0 comments on commit 1e665fd

Please sign in to comment.