Skip to content

Commit 3566921

Browse files
committed
Synthesize <Clone>$ method
1 parent 13a6f25 commit 3566921

3 files changed

Lines changed: 176 additions & 12 deletions

File tree

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
// Copyright 2004-2026 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+
#nullable enable
16+
17+
namespace Castle.DynamicProxy.Contributors
18+
{
19+
using System;
20+
using System.Reflection;
21+
22+
using Castle.DynamicProxy.Generators;
23+
using Castle.DynamicProxy.Generators.Emitters;
24+
using Castle.DynamicProxy.Generators.Emitters.SimpleAST;
25+
26+
internal sealed class RecordCloningContributor : ITypeContributor
27+
{
28+
private readonly Type targetType;
29+
private readonly INamingScope namingScope;
30+
31+
private MethodInfo? baseCloneMethod;
32+
private bool overridable;
33+
private bool shouldIntercept;
34+
35+
public RecordCloningContributor(Type targetType, INamingScope namingScope)
36+
{
37+
this.targetType = targetType;
38+
this.namingScope = namingScope;
39+
}
40+
41+
public void CollectElementsToProxy(IProxyGenerationHook hook, MetaType model)
42+
{
43+
baseCloneMethod = targetType.GetMethod("<Clone>$", BindingFlags.Public | BindingFlags.Instance);
44+
if (baseCloneMethod == null)
45+
{
46+
return;
47+
}
48+
49+
var cloneMetaMethod = model.FindMethod(baseCloneMethod);
50+
if (cloneMetaMethod != null)
51+
{
52+
// The target contributor may have chosen to generate interception code for this method.
53+
// We override that decision here. This effectively renders `<Clone>$` uninterceptable,
54+
// in favor of some default behavior provided by DynamicProxy. This may be a bad idea.
55+
cloneMetaMethod.Ignore = true;
56+
}
57+
58+
overridable = baseCloneMethod.IsVirtual && !baseCloneMethod.IsFinal;
59+
shouldIntercept = overridable && hook.ShouldInterceptMethod(targetType, baseCloneMethod);
60+
}
61+
62+
public void Generate(ClassEmitter @class)
63+
{
64+
if (baseCloneMethod == null) return;
65+
66+
ImplementCopyConstructor(@class, out var copyCtor);
67+
ImplementCloneMethod(@class, copyCtor);
68+
}
69+
70+
private void ImplementCopyConstructor(ClassEmitter @class, out ConstructorInfo copyCtor)
71+
{
72+
var other = new ArgumentReference(@class.TypeBuilder);
73+
var copyCtorEmitter = @class.CreateConstructor(other);
74+
var baseCopyCtor = targetType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, [targetType], null);
75+
76+
copyCtorEmitter.CodeBuilder.AddStatement(
77+
new ConstructorInvocationStatement(
78+
baseCopyCtor,
79+
other));
80+
81+
foreach (var field in @class.GetAllFields())
82+
{
83+
if (field.Reference.IsStatic) continue;
84+
85+
copyCtorEmitter.CodeBuilder.AddStatement(
86+
new AssignStatement(
87+
field,
88+
new FieldReference(
89+
field.Reference,
90+
other)));
91+
}
92+
93+
copyCtorEmitter.CodeBuilder.AddStatement(ReturnStatement.Instance);
94+
95+
copyCtor = copyCtorEmitter.ConstructorBuilder;
96+
}
97+
98+
private void ImplementCloneMethod(ClassEmitter @class, ConstructorInfo copyCtor)
99+
{
100+
if (shouldIntercept)
101+
{
102+
var cloneCallbackMethod = CreateCallbackMethod(@class, copyCtor);
103+
var cloneMetaMethod = new MetaMethod(baseCloneMethod!, cloneCallbackMethod, true, true, true);
104+
var invocationType = CreateInvocationType(@class, cloneMetaMethod, cloneCallbackMethod);
105+
106+
var cloneMethodGenerator = new MethodWithInvocationGenerator(
107+
cloneMetaMethod,
108+
@class.GetField("__interceptors"),
109+
invocationType,
110+
(c, m) => new TypeTokenExpression(@class.TypeBuilder),
111+
@class.CreateMethod,
112+
null);
113+
114+
cloneMethodGenerator.Generate(@class, namingScope);
115+
}
116+
else if (overridable)
117+
{
118+
var cloneMethodEmitter = @class.CreateMethod(
119+
name: baseCloneMethod!.Name,
120+
attrs: (baseCloneMethod.Attributes & MethodAttributes.MemberAccessMask) | MethodAttributes.ReuseSlot | MethodAttributes.HideBySig | MethodAttributes.Virtual,
121+
returnType: baseCloneMethod.ReturnType, // no need to use covariant return type
122+
argumentTypes: []);
123+
124+
cloneMethodEmitter.CodeBuilder.AddStatement(
125+
new ReturnStatement(
126+
new NewInstanceExpression(
127+
copyCtor,
128+
ThisExpression.Instance)));
129+
}
130+
}
131+
132+
private MethodInfo CreateCallbackMethod(ClassEmitter @class, ConstructorInfo copyCtor)
133+
{
134+
var callbackMethod = @class.CreateMethod(
135+
name: baseCloneMethod!.Name + "_callback",
136+
attrs: MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.NewSlot | MethodAttributes.HideBySig,
137+
returnType: copyCtor.DeclaringType,
138+
argumentTypes: Type.EmptyTypes);
139+
140+
callbackMethod.CodeBuilder.AddStatement(
141+
new ReturnStatement(
142+
new NewInstanceExpression(
143+
copyCtor,
144+
ThisExpression.Instance)));
145+
146+
return callbackMethod.MethodBuilder;
147+
}
148+
149+
private Type CreateInvocationType(ClassEmitter @class, MetaMethod cloneMetaMethod, MethodInfo cloneCallbackMethod)
150+
{
151+
var generator = new InheritanceInvocationTypeGenerator(
152+
targetType,
153+
cloneMetaMethod,
154+
cloneCallbackMethod,
155+
null);
156+
157+
return generator.Generate(@class, namingScope).BuildType();
158+
}
159+
}
160+
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2004-2021 Castle Project - http://www.castleproject.org/
1+
// Copyright 2004-2026 Castle Project - http://www.castleproject.org/
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -192,6 +192,8 @@ private IEnumerable<Type> GetTypeImplementerMapping(out IEnumerable<ITypeContrib
192192
}
193193
#endif
194194

195+
contributorsList.Add(new RecordCloningContributor(targetType, namingScope));
196+
195197
var proxyTargetAccessorContributor = GetProxyTargetAccessorContributor();
196198
contributorsList.Add(proxyTargetAccessorContributor);
197199
try

src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/FieldReference.cs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,26 +25,28 @@ internal class FieldReference : Reference
2525
{
2626
private readonly FieldInfo field;
2727
private readonly FieldBuilder? fieldBuilder;
28-
private readonly bool isStatic;
28+
private readonly IExpression? instance;
2929

30-
public FieldReference(FieldInfo field)
30+
public FieldReference(FieldInfo field, IExpression? instance = null)
3131
: base(field.FieldType)
3232
{
3333
this.field = field;
34+
this.instance = instance ?? ThisExpression.Instance;
3435
if ((field.Attributes & FieldAttributes.Static) != 0)
3536
{
36-
isStatic = true;
37+
this.instance = null;
3738
}
3839
}
3940

40-
public FieldReference(FieldBuilder fieldBuilder)
41+
public FieldReference(FieldBuilder fieldBuilder, IExpression? instance = null)
4142
: base(fieldBuilder.FieldType)
4243
{
4344
this.fieldBuilder = fieldBuilder;
4445
field = fieldBuilder;
46+
this.instance = instance ?? ThisExpression.Instance;
4547
if ((fieldBuilder.Attributes & FieldAttributes.Static) != 0)
4648
{
47-
isStatic = true;
49+
this.instance = null;
4850
}
4951
}
5052

@@ -60,40 +62,40 @@ public FieldInfo Reference
6062

6163
public override void EmitAddress(ILGenerator gen)
6264
{
63-
if (isStatic)
65+
if (instance == null)
6466
{
6567
gen.Emit(OpCodes.Ldsflda, Reference);
6668
}
6769
else
6870
{
69-
ThisExpression.Instance.Emit(gen);
71+
instance.Emit(gen);
7072
gen.Emit(OpCodes.Ldflda, Reference);
7173
}
7274
}
7375

7476
public override void Emit(ILGenerator gen)
7577
{
76-
if (isStatic)
78+
if (instance == null)
7779
{
7880
gen.Emit(OpCodes.Ldsfld, Reference);
7981
}
8082
else
8183
{
82-
ThisExpression.Instance.Emit(gen);
84+
instance.Emit(gen);
8385
gen.Emit(OpCodes.Ldfld, Reference);
8486
}
8587
}
8688

8789
public override void EmitStore(IExpression value, ILGenerator gen)
8890
{
89-
if (isStatic)
91+
if (instance == null)
9092
{
9193
value.Emit(gen);
9294
gen.Emit(OpCodes.Stsfld, Reference);
9395
}
9496
else
9597
{
96-
ThisExpression.Instance.Emit(gen);
98+
instance.Emit(gen);
9799
value.Emit(gen);
98100
gen.Emit(OpCodes.Stfld, Reference);
99101
}

0 commit comments

Comments
 (0)