Skip to content
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Enhancements:

Bugfixes:
- `InvalidProgramException` when proxying `MemoryStream` with .NET 7 (@stakx, #651)
- `invocation.MethodInvocationTarget` throws `ArgumentNullException` for default interface method (@stakx, #684)

Deprecations:
- .NET Core 2.1, .NET Core 3.1, .NET 6, and mono tests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,28 @@ public void Can_proceed_to_method_default_implementation_in_proxied_interface()
Assert.AreEqual(expected, actual);
}

[Test]
public void Invocation_InvocationTarget_is_proxy_for_intercepted_method_with_default_implementation()
{
var interceptor = new KeepDataInterceptor();
var proxy = generator.CreateInterfaceProxyWithoutTarget<IHaveMethodWithDefaultImplementation>(interceptor);
proxy.MethodWithDefaultImplementation();
Assert.AreSame(
expected: proxy,
actual: interceptor.Invocation.InvocationTarget);
}

[Test]
public void Invocation_MethodInvocationTarget_is_intercepted_method_with_default_implementation()
{
var interceptor = new KeepDataInterceptor();
var proxy = generator.CreateInterfaceProxyWithoutTarget<IHaveMethodWithDefaultImplementation>(interceptor);
proxy.MethodWithDefaultImplementation();
Assert.AreSame(
expected: typeof(IHaveMethodWithDefaultImplementation).GetMethod(nameof(IHaveMethodWithDefaultImplementation.MethodWithDefaultImplementation)),
actual: interceptor.Invocation.MethodInvocationTarget);
}

#endregion

#region Generic methods with default implementation
Expand Down Expand Up @@ -296,6 +318,28 @@ public void Can_proceed_to_method_default_implementation_from_additional_interfa
Assert.AreEqual(expected, actual);
}

[Test]
public void Invocation_InvocationTarget_is_proxy_for_intercepted_method_with_default_implementation_from_additional_interface()
{
var interceptor = new KeepDataInterceptor();
var proxy = generator.CreateInterfaceProxyWithoutTarget(typeof(IEmpty), new[] { typeof(IHaveMethodWithDefaultImplementation) }, interceptor);
((IHaveMethodWithDefaultImplementation)proxy).MethodWithDefaultImplementation();
Assert.AreSame(
expected: proxy,
actual: interceptor.Invocation.InvocationTarget);
}

[Test]
public void Invocation_MethodInvocationTarget_is_intercepted_method_with_default_implementation_from_additional_interface()
{
var interceptor = new KeepDataInterceptor();
var proxy = generator.CreateInterfaceProxyWithoutTarget(typeof(IEmpty), new[] { typeof(IHaveMethodWithDefaultImplementation) }, interceptor);
((IHaveMethodWithDefaultImplementation)proxy).MethodWithDefaultImplementation();
Assert.AreSame(
expected: typeof(IHaveMethodWithDefaultImplementation).GetMethod(nameof(IHaveMethodWithDefaultImplementation.MethodWithDefaultImplementation)),
actual: interceptor.Invocation.MethodInvocationTarget);
}

#endregion

#region Non-public methods with default implementation
Expand Down Expand Up @@ -457,6 +501,34 @@ public void Can_proceed_to_method_default_implementation_from_mixin_in_proxied_i
var actual = ((IHaveMethodWithDefaultImplementation)proxy).MethodWithDefaultImplementation();
Assert.AreEqual(expected, actual);
}

[Test]
public void Invocation_InvocationTarget_is_mixin_for_intercepted_method_with_default_implementation_from_mixin()
{
var options = new ProxyGenerationOptions();
var mixin = new InheritsMethodWithDefaultImplementation();
options.AddMixinInstance(mixin);
var interceptor = new KeepDataInterceptor();
var proxy = generator.CreateInterfaceProxyWithoutTarget(typeof(IEmpty), options, interceptor);
((IHaveMethodWithDefaultImplementation)proxy).MethodWithDefaultImplementation();
Assert.AreSame(
expected: mixin,
actual: interceptor.Invocation.InvocationTarget);
}

[Test]
public void Invocation_MethodInvocationTarget_is_intercepted_method_with_default_implementation_from_mixin()
{
var options = new ProxyGenerationOptions();
options.AddMixinInstance(new InheritsMethodWithDefaultImplementation());
var interceptor = new KeepDataInterceptor();
var proxy = generator.CreateInterfaceProxyWithoutTarget(typeof(IEmpty), options, interceptor);
((IHaveMethodWithDefaultImplementation)proxy).MethodWithDefaultImplementation();
Assert.AreSame(
expected: typeof(IHaveMethodWithDefaultImplementation).GetMethod(nameof(IHaveMethodWithDefaultImplementation.MethodWithDefaultImplementation)),
actual: interceptor.Invocation.MethodInvocationTarget);
}

#endregion

public interface IHaveGenericMethodWithDefaultImplementation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,16 @@ protected override MethodGenerator GetMethodGenerator(MetaMethod method, ClassEm
return ExplicitlyImplementedInterfaceMethodGenerator(method, @class, overrideMethod);
}

// since this contributor is used for class proxies without target,
// the invocation type will be derived from `InheritanceInvocation`:
var invocation = GetInvocationType(method, @class);

GetTargetExpressionDelegate getTargetTypeExpression = (c, m) => new TypeTokenExpression(targetType);

// `MethodWithInvocationGenerator` uses its `getTargetExpression` argument
// to determine the first argument to be passed to the received invocation type;
// and since `InheritanceInvocation`s' first ctor param is `targetType`,
// we pass `getTargetTypeExpression` here:
return new MethodWithInvocationGenerator(method,
@class.GetField("__interceptors"),
invocation,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,11 @@ private IEnumerable<Type> GetTypeImplementerMapping(out IEnumerable<ITypeContrib
// 3. then additional interfaces
if (interfaces.Length > 0)
{
var additionalInterfacesContributor = new InterfaceProxyWithoutTargetContributor(namingScope, (c, m) => NullExpression.Instance) { Logger = Logger };
// this is explained in `InterfaceProxyWithoutTargetGenerator.GetProxyTargetContributor`:
GetTargetExpressionDelegate getTargetType =
(c, m) => m.IsAbstract ? NullExpression.Instance
: new TypeTokenExpression(m.DeclaringType);
var additionalInterfacesContributor = new InterfaceProxyWithoutTargetContributor(namingScope, getTargetType) { Logger = Logger };
contributorsList.Add(additionalInterfacesContributor);

foreach (var @interface in interfaces)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,11 @@ protected override Type GenerateType(string typeName, INamingScope namingScope)
protected virtual InterfaceProxyWithoutTargetContributor GetContributorForAdditionalInterfaces(
INamingScope namingScope)
{
return new InterfaceProxyWithoutTargetContributor(namingScope, (c, m) => NullExpression.Instance) { Logger = Logger };
// this is explained in `InterfaceProxyWithoutTargetGenerator.GetProxyTargetContributor`:
GetTargetExpressionDelegate getTargetType =
(c, m) => m.IsAbstract ? NullExpression.Instance
: new TypeTokenExpression(m.DeclaringType);
return new InterfaceProxyWithoutTargetContributor(namingScope, getTargetType) { Logger = Logger };
}

protected virtual IEnumerable<Type> GetTypeImplementerMapping(Type proxyTargetType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,16 @@ public InterfaceProxyWithoutTargetGenerator(ModuleScope scope, Type targetType,

protected override CompositeTypeContributor GetProxyTargetContributor(Type proxyTargetType, INamingScope namingScope)
{
return new InterfaceProxyWithoutTargetContributor(namingScope, (c, m) => NullExpression.Instance) { Logger = Logger };
// The type of contributor instantiated below will use an inheritance-based invocation type.
// Those expect a target type, not a target instance (see first ctor param of `InheritanceInvocation`).
// For interface proxies without target, there typically isn't a target at all...
// except when an interface method has a default implementation (i.e. isn't abstract).
// Then the target type is the method's declaring interface itself.
// (Similar scenario: class proxies w/o target inherit method impls from the proxied class.)
GetTargetExpressionDelegate getTargetType =
(c, m) => m.IsAbstract ? NullExpression.Instance
: new TypeTokenExpression(m.DeclaringType);
return new InterfaceProxyWithoutTargetContributor(namingScope, getTargetType) { Logger = Logger };
}

protected override ProxyTargetAccessorContributor GetProxyTargetAccessorContributor()
Expand Down
15 changes: 11 additions & 4 deletions src/Castle.Core/DynamicProxy/Internal/InvocationHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,17 @@ private static MethodInfo ObtainMethod(MethodInfo proxiedMethod, Type type)
MethodInfo methodOnTarget = null;
if (declaringType.IsInterface)
{
var mapping = type.GetInterfaceMap(declaringType);
var index = Array.IndexOf(mapping.InterfaceMethods, proxiedMethod);
Debug.Assert(index != -1);
methodOnTarget = mapping.TargetMethods[index];
if (proxiedMethod.IsAbstract)
{
var mapping = type.GetInterfaceMap(declaringType);
var index = Array.IndexOf(mapping.InterfaceMethods, proxiedMethod);
Debug.Assert(index != -1);
methodOnTarget = mapping.TargetMethods[index];
}
else
{
methodOnTarget = proxiedMethod;
}
}
else
{
Expand Down
Loading