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 @@ -15,6 +15,7 @@ Bugfixes:
- Fix Castle.DynamicProxy.Generators.AttributesToAvoidReplicating not being thread safe (InvalidOperationException "Collection was modified; enumeration operation may not execute.") (@BrunoJuchli, #334)
- Fix TraceLoggerFactory to allow specifying the default logger level (@acjh, #342)
- Ensure that DynamicProxy doesn't create invalid dynamic assemblies when proxying types from non-strong-named assemblies (@stakx, #327)
- Fix interceptor selectors being passed `System.RuntimeType` for class proxies instead of the target type (@stakx, #359)

## 4.2.1 (2017-10-11)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
// Copyright 2004-2018 Castle Project - http://www.castleproject.org/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Castle.DynamicProxy.Tests
{
using System;
using System.Reflection;

using Castle.DynamicProxy.Tests.Interceptors;

using NUnit.Framework;

[TestFixture]
public class InterceptorSelectorTargetTypeTestCase : BasePEVerifyTestCase
{
// NOTE: This fixture does not just contain tests targeting IInterceptorSelector.SelectInterceptors,
// but also tests targeting IInvocation.TargetType. These tests complement the former set of tests
// to ensure consistency between the two.

[Test]
public void When_using_CreateClassProxy_SelectInterceptors_receives_type_equal_to_proxied_type()
{
var selector = new InterceptorSelector();

var proxy = generator.CreateClassProxy<Foo>(new ProxyGenerationOptions { Selector = selector }, new DoNothingInterceptor());
proxy.Method();

Assert.AreEqual(typeof(Foo), selector.ReceivedType);
}

[Test]
public void When_using_CreateClassProxy_Invocation_TargetType_is_equal_to_proxied_type()
{
var interceptor = new Interceptor();

var proxy = generator.CreateClassProxy<Foo>(interceptor);
proxy.Method();

Assert.AreEqual(typeof(Foo), interceptor.ReceivedTargetType);
}

[Test]
public void When_using_CreateClassProxyWithTarget_SelectInterceptors_receives_type_equal_to_type_of_target()
{
var selector = new InterceptorSelector();

var proxy = generator.CreateClassProxyWithTarget<Foo>(new FooTarget(), new ProxyGenerationOptions { Selector = selector }, new DoNothingInterceptor());
proxy.Method();

Assert.AreEqual(typeof(FooTarget), selector.ReceivedType);
}

[Test]
public void When_using_CreateClassProxyWithTarget_Invocation_TargetType_is_equal_to_type_of_target()
{
var interceptor = new Interceptor();

var proxy = generator.CreateClassProxyWithTarget<Foo>(new FooTarget(), interceptor);
proxy.Method();

Assert.AreEqual(typeof(FooTarget), interceptor.ReceivedTargetType);
}

[Test]
public void When_using_CreateInterfaceProxyWithoutTarget_SelectInterceptors_receives_type_equal_to_null()
{
var selector = new InterceptorSelector();

var proxy = generator.CreateInterfaceProxyWithoutTarget<IFoo>(new ProxyGenerationOptions { Selector = selector }, new DoNothingInterceptor());
proxy.Method();

Assert.AreEqual(null, selector.ReceivedType);
}

[Test]
public void When_using_CreateInterfaceProxyWithoutTarget_Invocation_TargetType_is_equal_to_null()
{
var interceptor = new Interceptor();

var proxy = generator.CreateInterfaceProxyWithoutTarget<IFoo>(interceptor);
proxy.Method();

Assert.AreEqual(null, interceptor.ReceivedTargetType);
}

[Test]
public void When_using_CreateInterfaceProxyWithTarget_SelectInterceptors_receives_type_equal_to_type_of_target()
{
var selector = new InterceptorSelector();

var proxy = generator.CreateInterfaceProxyWithTarget<IFoo>(new FooTarget(), new ProxyGenerationOptions { Selector = selector }, new DoNothingInterceptor());
proxy.Method();

Assert.AreEqual(typeof(FooTarget), selector.ReceivedType);
}

[Test]
public void When_using_CreateInterfaceProxyWithTarget_Invocation_TargetType_is_equal_to_type_of_target()
{
var interceptor = new Interceptor();

var proxy = generator.CreateInterfaceProxyWithTarget<IFoo>(new FooTarget(), interceptor);
proxy.Method();

Assert.AreEqual(typeof(FooTarget), interceptor.ReceivedTargetType);
}

[Test]
public void When_using_CreateInterfaceProxyWithTargetInterface_SelectInterceptors_receives_type_equal_to_type_of_target()
{
var selector = new InterceptorSelector();

var proxy = generator.CreateInterfaceProxyWithTargetInterface<IFoo>(new FooTarget(), new ProxyGenerationOptions { Selector = selector }, new DoNothingInterceptor());
proxy.Method();

Assert.AreEqual(typeof(FooTarget), selector.ReceivedType);
}

[Test]
public void When_using_CreateInterfaceProxyWithTargetInterface_Invocation_TargetType_is_equal_to_type_of_target()
{
var interceptor = new Interceptor();

var proxy = generator.CreateInterfaceProxyWithTargetInterface<IFoo>(new FooTarget(), interceptor);
proxy.Method();

Assert.AreEqual(typeof(FooTarget), interceptor.ReceivedTargetType);
}

public interface IFoo
{
void Method();
}

public abstract class Foo : IFoo
{
public abstract void Method();
}

public sealed class FooTarget : Foo
{
public override void Method()
{
}
}

#if FEATURE_SERIALIZATION
[Serializable]
#endif
public sealed class InterceptorSelector : IInterceptorSelector
{
public Type ReceivedType { get; private set; }

public IInterceptor[] SelectInterceptors(Type type, MethodInfo method, IInterceptor[] interceptors)
{
this.ReceivedType = type;
return interceptors;
}
}

public sealed class Interceptor : IInterceptor
{
public Type ReceivedTargetType { get; private set; }

public void Intercept(IInvocation invocation)
{
ReceivedTargetType = invocation.TargetType;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,13 @@ protected override MethodGenerator GetMethodGenerator(MetaMethod method, ClassEm

var invocation = GetInvocationType(method, @class, options);

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

return new MethodWithInvocationGenerator(method,
@class.GetField("__interceptors"),
invocation,
(c, m) => new TypeTokenExpression(targetType),
getTargetTypeExpression,
getTargetTypeExpression,
overrideMethod,
null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,26 @@ public class MethodWithInvocationGenerator : MethodGenerator
{
private readonly IInvocationCreationContributor contributor;
private readonly GetTargetExpressionDelegate getTargetExpression;
private readonly GetTargetExpressionDelegate getTargetTypeExpression;
private readonly Reference interceptors;
private readonly Type invocation;

public MethodWithInvocationGenerator(MetaMethod method, Reference interceptors, Type invocation,
GetTargetExpressionDelegate getTargetExpression,
OverrideMethodDelegate createMethod, IInvocationCreationContributor contributor)
: this(method, interceptors, invocation, getTargetExpression, null, createMethod, contributor)
{
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding the new parameter to the existing (publicly visible) ctor would be a breaking change, hence a new ctor.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No one should be using these classes anyway as they really are internal implementation, but happy for this to stay as is for now and can be removed later.


public MethodWithInvocationGenerator(MetaMethod method, Reference interceptors, Type invocation,
GetTargetExpressionDelegate getTargetExpression,
GetTargetExpressionDelegate getTargetTypeExpression,
OverrideMethodDelegate createMethod, IInvocationCreationContributor contributor)
: base(method, createMethod)
{
this.invocation = invocation;
this.getTargetExpression = getTargetExpression;
this.getTargetTypeExpression = getTargetTypeExpression;
this.interceptors = interceptors;
this.contributor = contributor;
}
Expand Down Expand Up @@ -147,11 +157,19 @@ private Expression SetMethodInterceptors(ClassEmitter @class, INamingScope namin

var methodInterceptorsField = BuildMethodInterceptorsField(@class, MethodToOverride, namingScope);

Expression targetTypeExpression;
if (getTargetTypeExpression != null)
{
targetTypeExpression = getTargetTypeExpression(@class, MethodToOverride);
}
else
{
targetTypeExpression = new MethodInvocationExpression(null, TypeUtilMethods.GetTypeOrNull, getTargetExpression(@class, MethodToOverride));
}

var emptyInterceptors = new NewArrayExpression(0, typeof(IInterceptor));
var selectInterceptors = new MethodInvocationExpression(selector, InterceptorSelectorMethods.SelectInterceptors,
new MethodInvocationExpression(null,
TypeUtilMethods.GetTypeOrNull,
getTargetExpression(@class, MethodToOverride)),
targetTypeExpression,
Copy link
Member Author

@stakx stakx May 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The simplest possible bugfix (which wouldn't require any changes in locations other than this one) would be to wrap the result of getTargetExpression(...) with a call to TypeUtil.GetTypeOrNull only if it is not a TypeTokenExpression already. I discarded this simpler solution because it feels like a hack. (It requires out-of-band knowledge about the kind of Expression that class proxies will produce inside their version of getTargetExpression.) The fix proposed in this PR is more elaborate, but arguably cleaner.

proxiedMethodTokenExpression, interceptors.ToExpression())
{ VirtualCall = true };

Expand Down
2 changes: 1 addition & 1 deletion src/Castle.Core/DynamicProxy/IInterceptorSelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public interface IInterceptorSelector
/// <summary>
/// Selects the interceptors that should intercept calls to the given <paramref name = "method" />.
/// </summary>
/// <param name = "type">The type declaring the method to intercept.</param>
/// <param name = "type">The type of the target object.</param>
/// <param name = "method">The method that will be intercepted.</param>
/// <param name = "interceptors">All interceptors registered with the proxy.</param>
/// <returns>An array of interceptors to invoke upon calling the <paramref name = "method" />.</returns>
Expand Down