Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Castle to generate delegate proxies #537

Merged
merged 1 commit into from
Apr 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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));
zvirja marked this conversation as resolved.
Show resolved Hide resolved
}

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