Skip to content

Commit 9abd5df

Browse files
committed
Convert by-ref-like args to/from ByRefLikeProxy types instead of nullifying them
1 parent 48ccf5d commit 9abd5df

6 files changed

Lines changed: 261 additions & 137 deletions

File tree

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2004-2025 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.
@@ -39,7 +39,7 @@ public ArgumentReference(Type argumentType, int position)
3939

4040
public override void EmitAddress(ILGenerator gen)
4141
{
42-
throw new NotSupportedException();
42+
gen.Emit(Position > 255 ? OpCodes.Ldarga : OpCodes.Ldarga_S, Position);
4343
}
4444

4545
public override void Emit(ILGenerator gen)

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

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ namespace Castle.DynamicProxy.Generators.Emitters.SimpleAST
1818
using System.Diagnostics;
1919
using System.Reflection.Emit;
2020

21+
using Castle.DynamicProxy.Internal;
22+
using Castle.DynamicProxy.Tokens;
23+
2124
internal class ConvertArgumentFromObjectExpression : IExpression
2225
{
2326
private readonly IExpression obj;
@@ -42,17 +45,29 @@ public void Emit(ILGenerator gen)
4245

4346
if (dereferencedArgumentType.IsValueType)
4447
{
45-
// Unbox conversion
46-
// Assumes fromType is a boxed value
47-
// if we can, we emit a box and ldind, otherwise, we will use unbox.any
48-
if (LdindOpCodesDictionary.Instance[dereferencedArgumentType] != LdindOpCodesDictionary.EmptyOpCode)
48+
#if FEATURE_BYREFLIKE
49+
if (dereferencedArgumentType.IsByRefLikeSafe())
4950
{
50-
gen.Emit(OpCodes.Unbox, dereferencedArgumentType);
51-
OpCodeUtil.EmitLoadIndirectOpCodeForType(gen, dereferencedArgumentType);
51+
gen.Emit(OpCodes.Ldtoken, dereferencedArgumentType);
52+
gen.Emit(OpCodes.Call, TypeMethods.GetTypeFromHandle);
53+
gen.Emit(OpCodes.Call, ByRefLikeProxyMethods.GetPtr);
54+
gen.Emit(OpCodes.Ldobj, dereferencedArgumentType);
5255
}
5356
else
57+
#endif
5458
{
55-
gen.Emit(OpCodes.Unbox_Any, dereferencedArgumentType);
59+
// Unbox conversion
60+
// Assumes fromType is a boxed value
61+
// if we can, we emit a box and ldind, otherwise, we will use unbox.any
62+
if (LdindOpCodesDictionary.Instance[dereferencedArgumentType] != LdindOpCodesDictionary.EmptyOpCode)
63+
{
64+
gen.Emit(OpCodes.Unbox, dereferencedArgumentType);
65+
OpCodeUtil.EmitLoadIndirectOpCodeForType(gen, dereferencedArgumentType);
66+
}
67+
else
68+
{
69+
gen.Emit(OpCodes.Unbox_Any, dereferencedArgumentType);
70+
}
5671
}
5772
}
5873
else
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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.Generators.Emitters.SimpleAST
18+
{
19+
using System;
20+
using System.Diagnostics;
21+
using System.Reflection;
22+
using System.Reflection.Emit;
23+
24+
/// <summary>
25+
/// Represents the storage location <c>X</c> referenced by a <see cref="Reference"/>
26+
/// holding an unmanaged pointer <c>&amp;X</c> to it.
27+
/// It essentially has the same function as the pointer indirection / dereferencing operator <c>*</c>.
28+
/// </summary>
29+
[DebuggerDisplay("*{ptr}")]
30+
internal class PointerReference : Reference
31+
{
32+
private readonly IExpression ptr;
33+
34+
public PointerReference(IExpression ptr, Type elementType)
35+
: base(elementType)
36+
{
37+
this.ptr = ptr;
38+
}
39+
40+
public override void EmitAddress(ILGenerator gen)
41+
{
42+
ptr.Emit(gen);
43+
}
44+
45+
public override void Emit(ILGenerator gen)
46+
{
47+
ptr.Emit(gen);
48+
OpCodeUtil.EmitLoadIndirectOpCodeForType(gen, Type);
49+
}
50+
51+
public override void EmitStore(IExpression value, ILGenerator gen)
52+
{
53+
ptr.Emit(gen);
54+
value.Emit(gen);
55+
OpCodeUtil.EmitStoreIndirectOpCodeForType(gen, Type);
56+
}
57+
}
58+
}

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

Lines changed: 28 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -287,26 +287,15 @@ public void CopyOut(out IExpression[] arguments, out LocalReference?[] byRefArgu
287287

288288
IExpression dereferencedArgument;
289289

290-
#if FEATURE_BYREFLIKE
291-
if (dereferencedArgumentType.IsByRefLikeSafe())
292-
{
293-
// The argument value in the invocation `Arguments` array is an `object`
294-
// and cannot be converted back to its original by-ref-like type.
295-
// We need to replace it with some other value.
290+
// Note that we don't need special logic for by-ref-like values / `ByRefLikeProxy` here,
291+
// since `ConvertArgumentFromObjectExpression` knows how to deal with those.
296292

297-
// For now, we just substitute the by-ref-like type's default value:
298-
dereferencedArgument = new DefaultValueExpression(dereferencedArgumentType);
299-
}
300-
else
301-
#endif
302-
{
303-
dereferencedArgument = new ConvertArgumentFromObjectExpression(
304-
new MethodInvocationExpression(
305-
ThisExpression.Instance,
306-
InvocationMethods.GetArgumentValue,
307-
new LiteralIntExpression(i)),
308-
dereferencedArgumentType);
309-
}
293+
dereferencedArgument = new ConvertArgumentFromObjectExpression(
294+
new MethodInvocationExpression(
295+
ThisExpression.Instance,
296+
InvocationMethods.GetArgumentValue,
297+
new LiteralIntExpression(i)),
298+
dereferencedArgumentType);
310299

311300
if (argumentType.IsByRef)
312301
{
@@ -333,17 +322,20 @@ public void CopyIn(LocalReference?[] byRefArguments)
333322
#if FEATURE_BYREFLIKE
334323
if (localCopy.Type.IsByRefLikeSafe())
335324
{
336-
// The by-ref-like value in the local buffer variable cannot be put back
337-
// into the invocation `Arguments` array, because it cannot be boxed.
338-
// We need to replace it with some other value.
339-
340-
// For now, we just erase it by substituting `null`:
325+
// For by-ref-like values, a `ByRefLikeProxy` has previously been placed in `IInvocation.Arguments`.
326+
// We must not replace that proxy, but use it to update the referenced by-ref-like parameter:
341327
method.CodeBuilder.AddStatement(
342-
new MethodInvocationExpression(
343-
ThisExpression.Instance,
344-
InvocationMethods.SetArgumentValue,
345-
new LiteralIntExpression(i),
346-
NullExpression.Instance));
328+
new AssignStatement(
329+
new PointerReference(
330+
new MethodInvocationExpression(
331+
new MethodInvocationExpression(
332+
ThisExpression.Instance,
333+
InvocationMethods.GetArgumentValue,
334+
new LiteralIntExpression(i)),
335+
ByRefLikeProxyMethods.GetPtr,
336+
new TypeTokenExpression(localCopy.Type)),
337+
localCopy.Type),
338+
localCopy));
347339
}
348340
else
349341
#endif
@@ -361,25 +353,14 @@ public void CopyIn(LocalReference?[] byRefArguments)
361353
public void SetReturnValue(LocalReference returnValue)
362354
{
363355
#if FEATURE_BYREFLIKE
364-
if (returnValue.Type.IsByRefLikeSafe())
365-
{
366-
// The by-ref-like return value cannot be put into the `ReturnValue` property,
367-
// because it cannot be boxed. We need to replace it with some other value.
368-
369-
// For now, we just erase it by substituting `null`:
370-
method.CodeBuilder.AddStatement(new MethodInvocationExpression(
371-
ThisExpression.Instance,
372-
InvocationMethods.SetReturnValue,
373-
NullExpression.Instance));
374-
}
375-
else
356+
// TODO: For by-ref-like return values, we will need to read `IInvocation.ReturnValue`
357+
// and set the return value via pointer indirection (`ByRefLikeProxy.GetPtr`).
376358
#endif
377-
{
378-
method.CodeBuilder.AddStatement(new MethodInvocationExpression(
379-
ThisExpression.Instance,
380-
InvocationMethods.SetReturnValue,
381-
new ConvertArgumentToObjectExpression(returnValue)));
382-
}
359+
360+
method.CodeBuilder.AddStatement(new MethodInvocationExpression(
361+
ThisExpression.Instance,
362+
InvocationMethods.SetReturnValue,
363+
new ConvertArgumentToObjectExpression(returnValue)));
383364
}
384365
}
385366
}

0 commit comments

Comments
 (0)