Skip to content

Commit c0fba5f

Browse files
authored
Merge pull request #571 from stakx/forward-non-intercepted-methods-to-target
Forward non-intercepted methods on class proxies to target
2 parents 4076c5d + 77743ac commit c0fba5f

File tree

9 files changed

+204
-11
lines changed

9 files changed

+204
-11
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
Enhancements:
66
- .NET Standard 2.0 and 2.1 support (@lg2de, #485)
7+
- Non-intercepted methods on a class proxy with target are now forwarded to the target (@stakx, #571)
78

89
Bugfixes:
910
- Proxying certain `[Serializable]` classes produces proxy types that fail PEVerify test (@stakx, #367)
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright 2004-2021 Castle Project - http://www.castleproject.org/
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
namespace Castle.DynamicProxy.Tests.BugsReported
16+
{
17+
using System;
18+
using System.Reflection;
19+
using System.Threading.Tasks;
20+
21+
using NUnit.Framework;
22+
23+
[TestFixture]
24+
public class GitHubIssue536 : BasePEVerifyTestCase
25+
{
26+
[Test]
27+
public void DynamicProxy_NonIntercepted_Property_Leaked()
28+
{
29+
var instance = new TestClassForCache();
30+
var toProxy = instance.GetType();
31+
32+
var proxyGenerationOptions = new ProxyGenerationOptions(new TestCacheProxyGenerationHook());
33+
34+
var generator = new ProxyGenerator();
35+
var proxy = generator.CreateClassProxyWithTarget(toProxy,
36+
instance,
37+
proxyGenerationOptions);
38+
39+
var accessor = (ITestCacheInterface)proxy;
40+
accessor.InstanceProperty = 1;
41+
42+
Assert.AreEqual(accessor.InstanceProperty, instance.InstanceProperty);
43+
}
44+
45+
public class TestCacheProxyGenerationHook : AllMethodsHook
46+
{
47+
public override bool ShouldInterceptMethod(Type type, MethodInfo methodInfo)
48+
{
49+
return false;
50+
}
51+
}
52+
53+
public interface ITestCacheInterface
54+
{
55+
int InstanceProperty { get; set; }
56+
}
57+
58+
public class TestClassForCache : ITestCacheInterface
59+
{
60+
public virtual int InstanceProperty { get; set; }
61+
}
62+
}
63+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2004-2019 Castle Project - http://www.castleproject.org/
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
namespace Castle.DynamicProxy.Tests.Classes
16+
{
17+
public class HasVirtualStringAutoProperty
18+
{
19+
public virtual string Property { get; set; }
20+
}
21+
}

src/Castle.Core.Tests/DynamicProxy.Tests/NonProxiedTargetMethodsTestCase.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ namespace Castle.DynamicProxy.Tests
1616
{
1717
using Castle.DynamicProxy.Tests.Classes;
1818
using Castle.DynamicProxy.Tests.Explicit;
19+
using Castle.DynamicProxy.Tests.Interceptors;
1920
using Castle.DynamicProxy.Tests.InterClasses;
2021
using Castle.DynamicProxy.Tests.Interfaces;
2122

@@ -119,5 +120,48 @@ public void Target_method_out_ref_parameters_WithTargetInterface()
119120
Assert.DoesNotThrow(() => proxy.Did(ref result));
120121
Assert.AreEqual(5, result);
121122
}
123+
124+
[Test]
125+
public void Unproxied_methods_should_pass_through_to_target()
126+
{
127+
var target = new HasVirtualStringAutoProperty();
128+
129+
var options = new ProxyGenerationOptions(
130+
hook: new ProxySomeMethodsHook(
131+
shouldInterceptMethod: (_, method) => method.Name == "set_" + nameof(HasVirtualStringAutoProperty.Property)));
132+
133+
var convertToLowerThenProceed = new WithCallbackInterceptor(invocation =>
134+
{
135+
string value = (string)invocation.GetArgumentValue(0);
136+
string lowerCase = value?.ToLowerInvariant();
137+
invocation.SetArgumentValue(0, lowerCase);
138+
invocation.Proceed();
139+
});
140+
141+
var proxy = generator.CreateClassProxyWithTarget(target, options, convertToLowerThenProceed);
142+
143+
proxy.Property = "HELLO WORLD";
144+
145+
Assert.AreEqual("hello world", target.Property);
146+
Assert.AreEqual("hello world", proxy.Property);
147+
}
148+
149+
[Test]
150+
public void Unproxied_public_method_should_not_invoke_interceptor()
151+
{
152+
var target = new VirtualClassWithMethod();
153+
var options = new ProxyGenerationOptions(new ProxyNothingHook());
154+
var proxy = generator.CreateClassProxyWithTarget(target, options, new ThrowingInterceptor());
155+
proxy.Method(); // the hook says "don't proxy anything", so this should not call the throwing interceptor
156+
}
157+
158+
[Test]
159+
public void Unproxied_non_public_method_should_not_invoke_interceptor()
160+
{
161+
var target = new ClassWithProtectedMethod();
162+
var options = new ProxyGenerationOptions(new ProxyNothingHook());
163+
var proxy = generator.CreateClassProxyWithTarget(target, options, new ThrowingInterceptor());
164+
proxy.PublicMethod();
165+
}
122166
}
123167
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright 2004-2019 Castle Project - http://www.castleproject.org/
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
namespace Castle.DynamicProxy.Tests
16+
{
17+
using System;
18+
using System.Reflection;
19+
20+
#if FEATURE_SERIALIZATION
21+
[Serializable]
22+
#endif
23+
public class ProxySomeMethodsHook : IProxyGenerationHook
24+
{
25+
private readonly Func<Type, MethodInfo, bool> shouldInterceptMethod;
26+
27+
public ProxySomeMethodsHook(Func<Type, MethodInfo, bool> shouldInterceptMethod)
28+
{
29+
this.shouldInterceptMethod = shouldInterceptMethod;
30+
}
31+
32+
public bool ShouldInterceptMethod(Type type, MethodInfo methodInfo)
33+
{
34+
return shouldInterceptMethod(type, methodInfo);
35+
}
36+
37+
void IProxyGenerationHook.MethodsInspected() { }
38+
39+
void IProxyGenerationHook.NonProxyableMemberNotification(Type type, MemberInfo memberInfo) { }
40+
}
41+
}

src/Castle.Core/DynamicProxy/Contributors/ClassProxyWithTargetTargetContributor.cs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,21 @@ protected override MethodGenerator GetMethodGenerator(MetaMethod method, ClassEm
5656
return null;
5757
}
5858

59+
var methodIsDirectlyAccessible = IsDirectlyAccessible(method);
60+
5961
if (!method.Proxyable)
6062
{
61-
return new MinimialisticMethodGenerator(method,
62-
overrideMethod);
63+
if (methodIsDirectlyAccessible)
64+
{
65+
return new ForwardingMethodGenerator(method, overrideMethod, (c, m) => c.GetField("__target"));
66+
}
67+
else
68+
{
69+
return IndirectlyCalledMethodGenerator(method, @class, overrideMethod, skipInterceptors: true);
70+
}
6371
}
6472

65-
if (IsDirectlyAccessible(method) == false)
73+
if (!methodIsDirectlyAccessible)
6674
{
6775
return IndirectlyCalledMethodGenerator(method, @class, overrideMethod);
6876
}
@@ -137,15 +145,16 @@ private Type GetInvocationType(MetaMethod method, ClassEmitter @class)
137145
}
138146

139147
private MethodGenerator IndirectlyCalledMethodGenerator(MetaMethod method, ClassEmitter proxy,
140-
OverrideMethodDelegate overrideMethod)
148+
OverrideMethodDelegate overrideMethod,
149+
bool skipInterceptors = false)
141150
{
142151
var @delegate = GetDelegateType(method, proxy);
143152
var contributor = GetContributor(@delegate, method);
144153
var invocation = new CompositionInvocationTypeGenerator(targetType, method, null, false, contributor)
145154
.Generate(proxy, namingScope)
146155
.BuildType();
147156
return new MethodWithInvocationGenerator(method,
148-
proxy.GetField("__interceptors"),
157+
skipInterceptors ? NullExpression.Instance : proxy.GetField("__interceptors"),
149158
invocation,
150159
(c, m) => c.GetField("__target"),
151160
overrideMethod,

src/Castle.Core/DynamicProxy/Contributors/MembersCollector.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,18 @@ MetaMethod AddMethod(MethodInfo method, bool isStandalone)
163163
/// to select methods.
164164
/// </summary>
165165
protected bool AcceptMethod(MethodInfo method, bool onlyVirtuals, IProxyGenerationHook hook)
166+
{
167+
return AcceptMethodPreScreen(method, onlyVirtuals, hook) && hook.ShouldInterceptMethod(type, method);
168+
}
169+
170+
/// <summary>
171+
/// Performs some basic screening to filter out non-interceptable methods.
172+
/// </summary>
173+
/// <remarks>
174+
/// The <paramref name="hook"/> will get invoked for non-interceptable method notification only;
175+
/// it does not get asked whether or not to intercept the <paramref name="method"/>.
176+
/// </remarks>
177+
protected bool AcceptMethodPreScreen(MethodInfo method, bool onlyVirtuals, IProxyGenerationHook hook)
166178
{
167179
if (IsInternalAndNotVisibleToDynamicProxy(method))
168180
{
@@ -207,7 +219,7 @@ protected bool AcceptMethod(MethodInfo method, bool onlyVirtuals, IProxyGenerati
207219
return false;
208220
}
209221

210-
return hook.ShouldInterceptMethod(type, method);
222+
return true;
211223
}
212224

213225
private static bool IsInternalAndNotVisibleToDynamicProxy(MethodInfo method)

src/Castle.Core/DynamicProxy/Contributors/WrappedClassMembersCollector.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,15 @@ protected override MetaMethod GetMethodToGenerate(MethodInfo method, IProxyGener
4242
return null;
4343
}
4444

45-
var accepted = AcceptMethod(method, true, hook);
46-
if (!accepted && !method.IsAbstract)
45+
var interceptable = AcceptMethodPreScreen(method, true, hook);
46+
if (!interceptable)
4747
{
4848
//we don't need to do anything...
4949
return null;
5050
}
5151

52+
var accepted = hook.ShouldInterceptMethod(type, method);
53+
5254
return new MetaMethod(method, method, isStandalone, accepted, hasTarget: true);
5355
}
5456

src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,17 @@ internal class MethodWithInvocationGenerator : MethodGenerator
3434
private readonly IInvocationCreationContributor contributor;
3535
private readonly GetTargetExpressionDelegate getTargetExpression;
3636
private readonly GetTargetExpressionDelegate getTargetTypeExpression;
37-
private readonly Reference interceptors;
37+
private readonly IExpression interceptors;
3838
private readonly Type invocation;
3939

40-
public MethodWithInvocationGenerator(MetaMethod method, Reference interceptors, Type invocation,
40+
public MethodWithInvocationGenerator(MetaMethod method, IExpression interceptors, Type invocation,
4141
GetTargetExpressionDelegate getTargetExpression,
4242
OverrideMethodDelegate createMethod, IInvocationCreationContributor contributor)
4343
: this(method, interceptors, invocation, getTargetExpression, null, createMethod, contributor)
4444
{
4545
}
4646

47-
public MethodWithInvocationGenerator(MetaMethod method, Reference interceptors, Type invocation,
47+
public MethodWithInvocationGenerator(MetaMethod method, IExpression interceptors, Type invocation,
4848
GetTargetExpressionDelegate getTargetExpression,
4949
GetTargetExpressionDelegate getTargetTypeExpression,
5050
OverrideMethodDelegate createMethod, IInvocationCreationContributor contributor)

0 commit comments

Comments
 (0)