From cddb73b45f12654fd6897c25a0f7d3cbe6c166cb Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Fri, 23 Jan 2026 18:36:01 +0100 Subject: [PATCH 01/14] Inline 4 single-use methods in `GeneratorUtil` --- .../DynamicProxy/Generators/GeneratorUtil.cs | 36 ++++++------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/src/Castle.Core/DynamicProxy/Generators/GeneratorUtil.cs b/src/Castle.Core/DynamicProxy/Generators/GeneratorUtil.cs index a9847d664..f41833ab3 100644 --- a/src/Castle.Core/DynamicProxy/Generators/GeneratorUtil.cs +++ b/src/Castle.Core/DynamicProxy/Generators/GeneratorUtil.cs @@ -37,7 +37,11 @@ public static void CopyOutAndRefParameters(Reference[] dereferencedArguments, Lo { if (arguments == null) { - arguments = StoreInvocationArgumentsInLocal(emitter, invocation); + arguments = emitter.CodeBuilder.DeclareLocal(typeof(object[])); + emitter.CodeBuilder.AddStatement( + new AssignStatement( + arguments, + new MethodInvocationExpression(invocation, InvocationMethods.GetArguments))); } #if FEATURE_BYREFLIKE @@ -64,33 +68,15 @@ public static void CopyOutAndRefParameters(Reference[] dereferencedArguments, Lo else #endif { - emitter.CodeBuilder.AddStatement(AssignArgument(dereferencedArguments, i, arguments)); + emitter.CodeBuilder.AddStatement( + new AssignStatement( + dereferencedArguments[i], + new ConvertExpression( + dereferencedArguments[i].Type, + new ArrayElementReference(arguments, i)))); } } } } - - private static ConvertExpression Argument(int i, LocalReference invocationArgs, Reference[] arguments) - { - return new ConvertExpression(arguments[i].Type, new ArrayElementReference(invocationArgs, i)); - } - - private static AssignStatement AssignArgument(Reference[] dereferencedArguments, int i, - LocalReference invocationArgs) - { - return new AssignStatement(dereferencedArguments[i], Argument(i, invocationArgs, dereferencedArguments)); - } - - private static AssignStatement GetArguments(LocalReference invocationArgs, LocalReference invocation) - { - return new AssignStatement(invocationArgs, new MethodInvocationExpression(invocation, InvocationMethods.GetArguments)); - } - - private static LocalReference StoreInvocationArgumentsInLocal(MethodEmitter emitter, LocalReference invocation) - { - var invocationArgs = emitter.CodeBuilder.DeclareLocal(typeof(object[])); - emitter.CodeBuilder.AddStatement(GetArguments(invocationArgs, invocation)); - return invocationArgs; - } } } \ No newline at end of file From 8c461404d729b0ff6963afad06b0699b931146c5 Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Fri, 23 Jan 2026 19:07:42 +0100 Subject: [PATCH 02/14] Declare explicit `argumentsArray` local ... for `GeneratorUtil.CopyOutAndRefParameters`, so that this method does not need to dig the arguments array out of the invocation instance. --- .../DynamicProxy/Generators/GeneratorUtil.cs | 17 ++--------------- .../Generators/MethodWithInvocationGenerator.cs | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/src/Castle.Core/DynamicProxy/Generators/GeneratorUtil.cs b/src/Castle.Core/DynamicProxy/Generators/GeneratorUtil.cs index f41833ab3..770e413f1 100644 --- a/src/Castle.Core/DynamicProxy/Generators/GeneratorUtil.cs +++ b/src/Castle.Core/DynamicProxy/Generators/GeneratorUtil.cs @@ -19,31 +19,18 @@ namespace Castle.DynamicProxy.Generators using Castle.DynamicProxy.Generators.Emitters; using Castle.DynamicProxy.Generators.Emitters.SimpleAST; using Castle.DynamicProxy.Internal; - using Castle.DynamicProxy.Tokens; internal static class GeneratorUtil { - public static void CopyOutAndRefParameters(Reference[] dereferencedArguments, LocalReference invocation, + public static void CopyOutAndRefParameters(Reference[] dereferencedArguments, LocalReference argumentsArray, MethodInfo method, MethodEmitter emitter) { var parameters = method.GetParameters(); - // Create it only if there are byref writable arguments. - LocalReference arguments = null; - for (var i = 0; i < parameters.Length; i++) { if (parameters[i].IsByRef && !parameters[i].IsReadOnly) { - if (arguments == null) - { - arguments = emitter.CodeBuilder.DeclareLocal(typeof(object[])); - emitter.CodeBuilder.AddStatement( - new AssignStatement( - arguments, - new MethodInvocationExpression(invocation, InvocationMethods.GetArguments))); - } - #if FEATURE_BYREFLIKE var dereferencedParameterType = parameters[i].ParameterType.GetElementType(); if (dereferencedParameterType.IsByRefLikeSafe()) @@ -73,7 +60,7 @@ public static void CopyOutAndRefParameters(Reference[] dereferencedArguments, Lo dereferencedArguments[i], new ConvertExpression( dereferencedArguments[i].Type, - new ArrayElementReference(arguments, i)))); + new ArrayElementReference(argumentsArray, i)))); } } } diff --git a/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs b/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs index 1e8592b10..cdfa05ae3 100644 --- a/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs +++ b/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs @@ -102,10 +102,17 @@ protected override MethodEmitter BuildProxiedMethodBody(MethodEmitter emitter, C var methodInterceptors = SetMethodInterceptors(@class, namingScope, emitter, proxiedMethodTokenExpression); var dereferencedArguments = IndirectReference.WrapIfByRef(emitter.Arguments); + + var argumentsArray = emitter.CodeBuilder.DeclareLocal(typeof(object[])); + emitter.CodeBuilder.AddStatement( + new AssignStatement( + argumentsArray, + new ReferencesToObjectArrayExpression(dereferencedArguments))); + var hasByRefArguments = HasByRefArguments(emitter.Arguments); - var arguments = GetCtorArguments(@class, proxiedMethodTokenExpression, dereferencedArguments, methodInterceptors); - var ctorArguments = ModifyArguments(@class, arguments); + var ctorArguments = GetCtorArguments(@class, proxiedMethodTokenExpression, argumentsArray, methodInterceptors); + ctorArguments = ModifyArguments(@class, ctorArguments); var invocationLocal = emitter.CodeBuilder.DeclareLocal(invocationType); emitter.CodeBuilder.AddStatement(new AssignStatement(invocationLocal, @@ -129,7 +136,7 @@ protected override MethodEmitter BuildProxiedMethodBody(MethodEmitter emitter, C emitter.CodeBuilder.AddStatement(FinallyStatement.Instance); } - GeneratorUtil.CopyOutAndRefParameters(dereferencedArguments, invocationLocal, MethodToOverride, emitter); + GeneratorUtil.CopyOutAndRefParameters(dereferencedArguments, argumentsArray, MethodToOverride, emitter); if (hasByRefArguments) { @@ -232,7 +239,7 @@ private void EmitLoadGenericMethodArguments(MethodEmitter methodEmitter, MethodI genericParamsArrayLocal)); } - private IExpression[] GetCtorArguments(ClassEmitter @class, IExpression proxiedMethodTokenExpression, Reference[] dereferencedArguments, IExpression methodInterceptors) + private IExpression[] GetCtorArguments(ClassEmitter @class, IExpression proxiedMethodTokenExpression, LocalReference argumentsArray, IExpression methodInterceptors) { return new[] { @@ -240,7 +247,7 @@ private IExpression[] GetCtorArguments(ClassEmitter @class, IExpression proxiedM ThisExpression.Instance, methodInterceptors ?? interceptors, proxiedMethodTokenExpression, - new ReferencesToObjectArrayExpression(dereferencedArguments) + argumentsArray, }; } From ef2f1b08c329823151a86a34e86a9627e296bf43 Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Fri, 23 Jan 2026 19:16:17 +0100 Subject: [PATCH 03/14] Get rid of `IndirectReference.WrapIfByRef` --- .../Emitters/SimpleAST/IndirectReference.cs | 20 +-------- .../ReferencesToObjectArrayExpression.cs | 43 ++++++++----------- .../DynamicProxy/Generators/GeneratorUtil.cs | 20 ++++++--- .../MethodWithInvocationGenerator.cs | 6 +-- 4 files changed, 37 insertions(+), 52 deletions(-) diff --git a/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/IndirectReference.cs b/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/IndirectReference.cs index 3cc727401..b87617724 100644 --- a/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/IndirectReference.cs +++ b/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/IndirectReference.cs @@ -1,4 +1,4 @@ -// Copyright 2004-2025 Castle Project - http://www.castleproject.org/ +// Copyright 2004-2026 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. @@ -59,23 +59,5 @@ public override void EmitStore(IExpression value, ILGenerator gen) value.Emit(gen); OpCodeUtil.EmitStoreIndirectOpCodeForType(gen, Type); } - - public static Reference WrapIfByRef(Reference reference) - { - return reference.Type.IsByRef ? new IndirectReference(reference) : reference; - } - - // TODO: Better name - public static Reference[] WrapIfByRef(Reference[] references) - { - var result = new Reference[references.Length]; - - for (var i = 0; i < references.Length; i++) - { - result[i] = WrapIfByRef(references[i]); - } - - return result; - } } } \ No newline at end of file diff --git a/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ReferencesToObjectArrayExpression.cs b/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ReferencesToObjectArrayExpression.cs index 67cb7b714..a573ac57f 100644 --- a/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ReferencesToObjectArrayExpression.cs +++ b/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ReferencesToObjectArrayExpression.cs @@ -1,4 +1,4 @@ -// Copyright 2004-2025 Castle Project - http://www.castleproject.org/ +// Copyright 2004-2026 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. @@ -16,38 +16,38 @@ namespace Castle.DynamicProxy.Generators.Emitters.SimpleAST { - using System; - using System.Reflection; using System.Reflection.Emit; using Castle.DynamicProxy.Internal; internal class ReferencesToObjectArrayExpression : IExpression { - private readonly Reference[] args; + private readonly ArgumentReference[] arguments; - public ReferencesToObjectArrayExpression(params Reference[] args) + public ReferencesToObjectArrayExpression(ArgumentReference[] arguments) { - this.args = args; + this.arguments = arguments; } public void Emit(ILGenerator gen) { - var local = gen.DeclareLocal(typeof(object?[])); + var argumentsArray = gen.DeclareLocal(typeof(object?[])); - gen.Emit(OpCodes.Ldc_I4, args.Length); + gen.Emit(OpCodes.Ldc_I4, arguments.Length); gen.Emit(OpCodes.Newarr, typeof(object)); - gen.Emit(OpCodes.Stloc, local); + gen.Emit(OpCodes.Stloc, argumentsArray); - for (var i = 0; i < args.Length; i++) + for (var i = 0; i < arguments.Length; i++) { - gen.Emit(OpCodes.Ldloc, local); + gen.Emit(OpCodes.Ldloc, argumentsArray); gen.Emit(OpCodes.Ldc_I4, i); - var reference = args[i]; + var argument = arguments[i]; + Reference dereferencedArgument = argument.Type.IsByRef ? new IndirectReference(argument) : argument; + var dereferencedArgumentType = dereferencedArgument.Type; #if FEATURE_BYREFLIKE - if (reference.Type.IsByRefLikeSafe()) + if (dereferencedArgumentType.IsByRefLikeSafe()) { // The by-ref-like argument value cannot be put into the `object[]` array, // because it cannot be boxed. We need to replace it with some other value. @@ -60,26 +60,21 @@ public void Emit(ILGenerator gen) } #endif - reference.Emit(gen); + dereferencedArgument.Emit(gen); - if (reference.Type.IsByRef) + if (dereferencedArgumentType.IsValueType) { - throw new NotSupportedException(); + gen.Emit(OpCodes.Box, dereferencedArgumentType); } - - if (reference.Type.IsValueType) - { - gen.Emit(OpCodes.Box, reference.Type); - } - else if (reference.Type.IsGenericParameter) + else if (dereferencedArgumentType.IsGenericParameter) { - gen.Emit(OpCodes.Box, reference.Type); + gen.Emit(OpCodes.Box, dereferencedArgumentType); } gen.Emit(OpCodes.Stelem_Ref); } - gen.Emit(OpCodes.Ldloc, local); + gen.Emit(OpCodes.Ldloc, argumentsArray); } } } \ No newline at end of file diff --git a/src/Castle.Core/DynamicProxy/Generators/GeneratorUtil.cs b/src/Castle.Core/DynamicProxy/Generators/GeneratorUtil.cs index 770e413f1..8d4922cdc 100644 --- a/src/Castle.Core/DynamicProxy/Generators/GeneratorUtil.cs +++ b/src/Castle.Core/DynamicProxy/Generators/GeneratorUtil.cs @@ -14,6 +14,7 @@ namespace Castle.DynamicProxy.Generators { + using System.Diagnostics; using System.Reflection; using Castle.DynamicProxy.Generators.Emitters; @@ -22,18 +23,22 @@ namespace Castle.DynamicProxy.Generators internal static class GeneratorUtil { - public static void CopyOutAndRefParameters(Reference[] dereferencedArguments, LocalReference argumentsArray, + public static void CopyOutAndRefParameters(Reference[] arguments, LocalReference argumentsArray, MethodInfo method, MethodEmitter emitter) { var parameters = method.GetParameters(); for (var i = 0; i < parameters.Length; i++) { + Debug.Assert(parameters[i].ParameterType == arguments[i].Type); + if (parameters[i].IsByRef && !parameters[i].IsReadOnly) { + var dereferencedArgument = new IndirectReference(arguments[i]); + var dereferencedArgumentType = dereferencedArgument.Type; + #if FEATURE_BYREFLIKE - var dereferencedParameterType = parameters[i].ParameterType.GetElementType(); - if (dereferencedParameterType.IsByRefLikeSafe()) + if (dereferencedArgumentType.IsByRefLikeSafe()) { // The argument value in the invocation `Arguments` array is an `object` // and cannot be converted back to its original by-ref-like type. @@ -42,7 +47,10 @@ public static void CopyOutAndRefParameters(Reference[] dereferencedArguments, Lo // For now, we just substitute the by-ref-like type's default value: if (parameters[i].IsOut) { - emitter.CodeBuilder.AddStatement(new AssignStatement(dereferencedArguments[i], new DefaultValueExpression(dereferencedParameterType))); + emitter.CodeBuilder.AddStatement( + new AssignStatement( + dereferencedArgument, + new DefaultValueExpression(dereferencedArgumentType))); } else { @@ -57,9 +65,9 @@ public static void CopyOutAndRefParameters(Reference[] dereferencedArguments, Lo { emitter.CodeBuilder.AddStatement( new AssignStatement( - dereferencedArguments[i], + dereferencedArgument, new ConvertExpression( - dereferencedArguments[i].Type, + dereferencedArgumentType, new ArrayElementReference(argumentsArray, i)))); } } diff --git a/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs b/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs index cdfa05ae3..b53fe672b 100644 --- a/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs +++ b/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs @@ -101,13 +101,13 @@ protected override MethodEmitter BuildProxiedMethodBody(MethodEmitter emitter, C var methodInterceptors = SetMethodInterceptors(@class, namingScope, emitter, proxiedMethodTokenExpression); - var dereferencedArguments = IndirectReference.WrapIfByRef(emitter.Arguments); + var arguments = emitter.Arguments; var argumentsArray = emitter.CodeBuilder.DeclareLocal(typeof(object[])); emitter.CodeBuilder.AddStatement( new AssignStatement( argumentsArray, - new ReferencesToObjectArrayExpression(dereferencedArguments))); + new ReferencesToObjectArrayExpression(arguments))); var hasByRefArguments = HasByRefArguments(emitter.Arguments); @@ -136,7 +136,7 @@ protected override MethodEmitter BuildProxiedMethodBody(MethodEmitter emitter, C emitter.CodeBuilder.AddStatement(FinallyStatement.Instance); } - GeneratorUtil.CopyOutAndRefParameters(dereferencedArguments, argumentsArray, MethodToOverride, emitter); + GeneratorUtil.CopyOutAndRefParameters(arguments, argumentsArray, MethodToOverride, emitter); if (hasByRefArguments) { From 0d025034d79553a78df9fc6fbd6a4b1aef147805 Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Sat, 24 Jan 2026 11:13:41 +0100 Subject: [PATCH 04/14] Move `CopyOutAndRefParameters` to new `ArgumentsMarshaller` --- .../DynamicProxy/Generators/GeneratorUtil.cs | 77 ------------------- .../MethodWithInvocationGenerator.cs | 66 +++++++++++++++- 2 files changed, 65 insertions(+), 78 deletions(-) delete mode 100644 src/Castle.Core/DynamicProxy/Generators/GeneratorUtil.cs diff --git a/src/Castle.Core/DynamicProxy/Generators/GeneratorUtil.cs b/src/Castle.Core/DynamicProxy/Generators/GeneratorUtil.cs deleted file mode 100644 index 8d4922cdc..000000000 --- a/src/Castle.Core/DynamicProxy/Generators/GeneratorUtil.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2004-2026 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.Generators -{ - using System.Diagnostics; - using System.Reflection; - - using Castle.DynamicProxy.Generators.Emitters; - using Castle.DynamicProxy.Generators.Emitters.SimpleAST; - using Castle.DynamicProxy.Internal; - - internal static class GeneratorUtil - { - public static void CopyOutAndRefParameters(Reference[] arguments, LocalReference argumentsArray, - MethodInfo method, MethodEmitter emitter) - { - var parameters = method.GetParameters(); - - for (var i = 0; i < parameters.Length; i++) - { - Debug.Assert(parameters[i].ParameterType == arguments[i].Type); - - if (parameters[i].IsByRef && !parameters[i].IsReadOnly) - { - var dereferencedArgument = new IndirectReference(arguments[i]); - var dereferencedArgumentType = dereferencedArgument.Type; - -#if FEATURE_BYREFLIKE - if (dereferencedArgumentType.IsByRefLikeSafe()) - { - // The argument value in the invocation `Arguments` array is an `object` - // and cannot be converted back to its original by-ref-like type. - // We need to replace it with some other value. - - // For now, we just substitute the by-ref-like type's default value: - if (parameters[i].IsOut) - { - emitter.CodeBuilder.AddStatement( - new AssignStatement( - dereferencedArgument, - new DefaultValueExpression(dereferencedArgumentType))); - } - else - { - // ... except when we're dealing with a `ref` parameter. Unlike with `out`, - // where we would be expected to definitely assign to it, we are free to leave - // the original incoming value untouched. For now, that's likely the better - // interim solution than unconditionally resetting. - } - } - else -#endif - { - emitter.CodeBuilder.AddStatement( - new AssignStatement( - dereferencedArgument, - new ConvertExpression( - dereferencedArgumentType, - new ArrayElementReference(argumentsArray, i)))); - } - } - } - } - } -} \ No newline at end of file diff --git a/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs b/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs index b53fe672b..2bf140bd5 100644 --- a/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs +++ b/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs @@ -101,6 +101,8 @@ protected override MethodEmitter BuildProxiedMethodBody(MethodEmitter emitter, C var methodInterceptors = SetMethodInterceptors(@class, namingScope, emitter, proxiedMethodTokenExpression); + var argumentsMarshaller = new ArgumentsMarshaller(emitter, MethodToOverride.GetParameters()); + var arguments = emitter.Arguments; var argumentsArray = emitter.CodeBuilder.DeclareLocal(typeof(object[])); @@ -136,7 +138,7 @@ protected override MethodEmitter BuildProxiedMethodBody(MethodEmitter emitter, C emitter.CodeBuilder.AddStatement(FinallyStatement.Instance); } - GeneratorUtil.CopyOutAndRefParameters(arguments, argumentsArray, MethodToOverride, emitter); + argumentsMarshaller.CopyOut(argumentsArray); if (hasByRefArguments) { @@ -273,5 +275,67 @@ private bool HasByRefArguments(ArgumentReference[] arguments) return false; } + + private struct ArgumentsMarshaller + { + private readonly MethodEmitter method; + private readonly ParameterInfo[] parameters; + + public ArgumentsMarshaller(MethodEmitter method, ParameterInfo[] parameters) + { + this.method = method; + this.parameters = parameters; + } + + public void CopyOut(LocalReference argumentsArray) + { + var arguments = method.Arguments; + + for (int i = 0, n = arguments.Length; i < n; ++i) + { + Debug.Assert(parameters[i].ParameterType == arguments[i].Type); + + if (parameters[i].IsByRef && !parameters[i].IsReadOnly) + { + var dereferencedArgument = new IndirectReference(arguments[i]); + var dereferencedArgumentType = dereferencedArgument.Type; + +#if FEATURE_BYREFLIKE + if (dereferencedArgumentType.IsByRefLikeSafe()) + { + // The argument value in the invocation `Arguments` array is an `object` + // and cannot be converted back to its original by-ref-like type. + // We need to replace it with some other value. + + // For now, we just substitute the by-ref-like type's default value: + if (parameters[i].IsOut) + { + method.CodeBuilder.AddStatement( + new AssignStatement( + dereferencedArgument, + new DefaultValueExpression(dereferencedArgumentType))); + } + else + { + // ... except when we're dealing with a `ref` parameter. Unlike with `out`, + // where we would be expected to definitely assign to it, we are free to leave + // the original incoming value untouched. For now, that's likely the better + // interim solution than unconditionally resetting. + } + } + else +#endif + { + method.CodeBuilder.AddStatement( + new AssignStatement( + dereferencedArgument, + new ConvertExpression( + dereferencedArgumentType, + new ArrayElementReference(argumentsArray, i)))); + } + } + } + } + } } } \ No newline at end of file From d9f1d5949ee7e9efa48635cccf536a3becff52fc Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Sat, 24 Jan 2026 11:16:40 +0100 Subject: [PATCH 05/14] Complement `CopyOut` with `CopyIn` counterpart --- .../MethodWithInvocationGenerator.cs | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs b/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs index 2bf140bd5..f87ccd0d4 100644 --- a/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs +++ b/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs @@ -103,13 +103,7 @@ protected override MethodEmitter BuildProxiedMethodBody(MethodEmitter emitter, C var argumentsMarshaller = new ArgumentsMarshaller(emitter, MethodToOverride.GetParameters()); - var arguments = emitter.Arguments; - - var argumentsArray = emitter.CodeBuilder.DeclareLocal(typeof(object[])); - emitter.CodeBuilder.AddStatement( - new AssignStatement( - argumentsArray, - new ReferencesToObjectArrayExpression(arguments))); + argumentsMarshaller.CopyIn(out var argumentsArray); var hasByRefArguments = HasByRefArguments(emitter.Arguments); @@ -287,6 +281,18 @@ public ArgumentsMarshaller(MethodEmitter method, ParameterInfo[] parameters) this.parameters = parameters; } + public void CopyIn(out LocalReference argumentsArray) + { + var arguments = method.Arguments; + + argumentsArray = method.CodeBuilder.DeclareLocal(typeof(object[])); + + method.CodeBuilder.AddStatement( + new AssignStatement( + argumentsArray, + new ReferencesToObjectArrayExpression(arguments))); + } + public void CopyOut(LocalReference argumentsArray) { var arguments = method.Arguments; From 5a987ec458359da9121ae467ba23fffe1fa7a61b Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Sat, 24 Jan 2026 11:31:13 +0100 Subject: [PATCH 06/14] Replace `ReferencesToObjectArrayExpression` ... by composing simpler node types in `CopyIn` instead. While this requires more allocations, it highlights the similarities between `Copy- In` and `CopyOut`, resulting in a cleaner interface. The newly introduced node type `ConvertArgumentToObjectExpression` may seem redundant right now (given the more general `ConvertExpression`), but it will allow us later to introduce conversions that are specific to copying arguments into & out of `IInvocation`. --- .../ConvertArgumentToObjectExpression.cs | 42 ++++++++++ .../ReferencesToObjectArrayExpression.cs | 80 ------------------- .../MethodWithInvocationGenerator.cs | 30 ++++++- 3 files changed, 71 insertions(+), 81 deletions(-) create mode 100644 src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ConvertArgumentToObjectExpression.cs delete mode 100644 src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ReferencesToObjectArrayExpression.cs diff --git a/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ConvertArgumentToObjectExpression.cs b/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ConvertArgumentToObjectExpression.cs new file mode 100644 index 000000000..62784570c --- /dev/null +++ b/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ConvertArgumentToObjectExpression.cs @@ -0,0 +1,42 @@ +// Copyright 2004-2026 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.Generators.Emitters.SimpleAST +{ + using System.Diagnostics; + using System.Reflection.Emit; + + internal class ConvertArgumentToObjectExpression : IExpression + { + private readonly Reference dereferencedArgument; + + public ConvertArgumentToObjectExpression(Reference dereferencedArgument) + { + Debug.Assert(dereferencedArgument.Type.IsByRef == false); + + this.dereferencedArgument = dereferencedArgument; + } + + public void Emit(ILGenerator gen) + { + dereferencedArgument.Emit(gen); + + var dereferencedArgumentType = dereferencedArgument.Type; + if (dereferencedArgumentType.IsValueType || dereferencedArgumentType.IsGenericParameter) + { + gen.Emit(OpCodes.Box, dereferencedArgumentType); + } + } + } +} diff --git a/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ReferencesToObjectArrayExpression.cs b/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ReferencesToObjectArrayExpression.cs deleted file mode 100644 index a573ac57f..000000000 --- a/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ReferencesToObjectArrayExpression.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2004-2026 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. - -#nullable enable - -namespace Castle.DynamicProxy.Generators.Emitters.SimpleAST -{ - using System.Reflection.Emit; - - using Castle.DynamicProxy.Internal; - - internal class ReferencesToObjectArrayExpression : IExpression - { - private readonly ArgumentReference[] arguments; - - public ReferencesToObjectArrayExpression(ArgumentReference[] arguments) - { - this.arguments = arguments; - } - - public void Emit(ILGenerator gen) - { - var argumentsArray = gen.DeclareLocal(typeof(object?[])); - - gen.Emit(OpCodes.Ldc_I4, arguments.Length); - gen.Emit(OpCodes.Newarr, typeof(object)); - gen.Emit(OpCodes.Stloc, argumentsArray); - - for (var i = 0; i < arguments.Length; i++) - { - gen.Emit(OpCodes.Ldloc, argumentsArray); - gen.Emit(OpCodes.Ldc_I4, i); - - var argument = arguments[i]; - Reference dereferencedArgument = argument.Type.IsByRef ? new IndirectReference(argument) : argument; - var dereferencedArgumentType = dereferencedArgument.Type; - -#if FEATURE_BYREFLIKE - if (dereferencedArgumentType.IsByRefLikeSafe()) - { - // The by-ref-like argument value cannot be put into the `object[]` array, - // because it cannot be boxed. We need to replace it with some other value. - - // For now, we just erase it by substituting `null`: - gen.Emit(OpCodes.Ldnull); - gen.Emit(OpCodes.Stelem_Ref); - - continue; - } -#endif - - dereferencedArgument.Emit(gen); - - if (dereferencedArgumentType.IsValueType) - { - gen.Emit(OpCodes.Box, dereferencedArgumentType); - } - else if (dereferencedArgumentType.IsGenericParameter) - { - gen.Emit(OpCodes.Box, dereferencedArgumentType); - } - - gen.Emit(OpCodes.Stelem_Ref); - } - - gen.Emit(OpCodes.Ldloc, argumentsArray); - } - } -} \ No newline at end of file diff --git a/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs b/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs index f87ccd0d4..37f1f6db7 100644 --- a/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs +++ b/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs @@ -290,7 +290,35 @@ public void CopyIn(out LocalReference argumentsArray) method.CodeBuilder.AddStatement( new AssignStatement( argumentsArray, - new ReferencesToObjectArrayExpression(arguments))); + new NewArrayExpression(arguments.Length, typeof(object)))); + + for (int i = 0, n = arguments.Length; i < n; ++i) + { + var argument = arguments[i]; + Reference dereferencedArgument = argument.Type.IsByRef ? new IndirectReference(argument) : argument; + var dereferencedArgumentType = dereferencedArgument.Type; + +#if FEATURE_BYREFLIKE + if (dereferencedArgumentType.IsByRefLikeSafe()) + { + // The by-ref-like argument value cannot be put into the `object[]` array, + // because it cannot be boxed. We need to replace it with some other value. + + // For now, we just erase it by substituting `null`: + method.CodeBuilder.AddStatement( + new AssignStatement( + new ArrayElementReference(argumentsArray, i), + NullExpression.Instance)); + } + else +#endif + { + method.CodeBuilder.AddStatement( + new AssignStatement( + new ArrayElementReference(argumentsArray, i), + new ConvertArgumentToObjectExpression(dereferencedArgument))); + } + } } public void CopyOut(LocalReference argumentsArray) From e52a9dad28313af1a191d540ac0f569354483a65 Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Sat, 24 Jan 2026 11:48:35 +0100 Subject: [PATCH 07/14] Let `ArgumentsMarshaller` handle return value, too --- .../MethodWithInvocationGenerator.cs | 87 +++++++++++-------- 1 file changed, 49 insertions(+), 38 deletions(-) diff --git a/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs b/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs index 37f1f6db7..168a0d101 100644 --- a/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs +++ b/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs @@ -139,44 +139,7 @@ protected override MethodEmitter BuildProxiedMethodBody(MethodEmitter emitter, C emitter.CodeBuilder.AddStatement(EndExceptionBlockStatement.Instance); } - if (MethodToOverride.ReturnType != typeof(void)) - { - IExpression retVal; - -#if FEATURE_BYREFLIKE - if (emitter.ReturnType.IsByRefLikeSafe()) - { - // The return value in the `ReturnValue` property is an `object` - // and cannot be converted back to the original by-ref-like return type. - // We need to replace it with some other value. - - // For now, we just substitute the by-ref-like type's default value: - retVal = new DefaultValueExpression(emitter.ReturnType); - } - else -#endif - { - retVal = new MethodInvocationExpression(invocationLocal, InvocationMethods.GetReturnValue); - - // Emit code to ensure a value type return type is not null, otherwise the cast will cause a null-deref - if (emitter.ReturnType.IsValueType && !emitter.ReturnType.IsNullableType()) - { - LocalReference returnValue = emitter.CodeBuilder.DeclareLocal(typeof(object)); - emitter.CodeBuilder.AddStatement(new AssignStatement(returnValue, retVal)); - - emitter.CodeBuilder.AddStatement(new IfNullExpression(returnValue, new ThrowStatement(typeof(InvalidOperationException), - "Interceptors failed to set a return value, or swallowed the exception thrown by the target"))); - } - - retVal = new ConvertExpression(emitter.ReturnType, retVal); - } - - emitter.CodeBuilder.AddStatement(new ReturnStatement(retVal)); - } - else - { - emitter.CodeBuilder.AddStatement(ReturnStatement.Instance); - } + argumentsMarshaller.Return(invocationLocal); return emitter; } @@ -370,6 +333,54 @@ public void CopyOut(LocalReference argumentsArray) } } } + + public void Return(LocalReference invocation) + { + var returnType = method.ReturnType; + + if (returnType == typeof(void)) + { + method.CodeBuilder.AddStatement(ReturnStatement.Instance); + return; + } + +#if FEATURE_BYREFLIKE + if (returnType.IsByRefLikeSafe()) + { + // The return value in the `ReturnValue` property is an `object` + // and cannot be converted back to the original by-ref-like return type. + // We need to replace it with some other value. + + // For now, we just substitute the by-ref-like type's default value: + method.CodeBuilder.AddStatement( + new ReturnStatement( + new DefaultValueExpression(returnType))); + } + else +#endif + { + var returnValue = method.CodeBuilder.DeclareLocal(typeof(object)); + method.CodeBuilder.AddStatement( + new AssignStatement( + returnValue, + new MethodInvocationExpression(invocation, InvocationMethods.GetReturnValue))); + + // Emit code to ensure a value type return type is not null, otherwise the cast will cause a null-deref + if (returnType.IsValueType && !returnType.IsNullableType()) + { + method.CodeBuilder.AddStatement( + new IfNullExpression( + returnValue, + new ThrowStatement( + typeof(InvalidOperationException), + "Interceptors failed to set a return value, or swallowed the exception thrown by the target"))); + } + + method.CodeBuilder.AddStatement( + new ReturnStatement( + new ConvertExpression(returnType, returnValue))); + } + } } } } \ No newline at end of file From eed779d42d1a5b519cfa4fdd2d9b1b69944836c5 Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Sat, 24 Jan 2026 12:04:08 +0100 Subject: [PATCH 08/14] Introduce `ConvertArgumentFromObjectExpression` ... as the counterpart to `ConvertArgumentToObjectExpression`. For now, this is just a simplified version of `ConvertExpression`, but it gives us a place to add arguments-specific marshalling logic later on. --- .../ConvertArgumentFromObjectExpression.cs | 77 +++++++++++++++++++ .../MethodWithInvocationGenerator.cs | 8 +- 2 files changed, 81 insertions(+), 4 deletions(-) create mode 100644 src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ConvertArgumentFromObjectExpression.cs diff --git a/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ConvertArgumentFromObjectExpression.cs b/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ConvertArgumentFromObjectExpression.cs new file mode 100644 index 000000000..ce8523f75 --- /dev/null +++ b/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ConvertArgumentFromObjectExpression.cs @@ -0,0 +1,77 @@ +// Copyright 2004-2026 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.Generators.Emitters.SimpleAST +{ + using System; + using System.Diagnostics; + using System.Reflection.Emit; + + internal class ConvertArgumentFromObjectExpression : IExpression + { + private readonly IExpression obj; + private Type dereferencedArgumentType; + + public ConvertArgumentFromObjectExpression(Reference obj, Type dereferencedArgumentType) + { + Debug.Assert(obj.Type == typeof(object)); + Debug.Assert(dereferencedArgumentType.IsByRef == false); + + this.obj = obj; + this.dereferencedArgumentType = dereferencedArgumentType; + } + + public void Emit(ILGenerator gen) + { + obj.Emit(gen); + + if (dereferencedArgumentType == typeof(object)) + { + return; + } + + if (dereferencedArgumentType.IsValueType) + { + // Unbox conversion + // Assumes fromType is a boxed value + // if we can, we emit a box and ldind, otherwise, we will use unbox.any + if (LdindOpCodesDictionary.Instance[dereferencedArgumentType] != LdindOpCodesDictionary.EmptyOpCode) + { + gen.Emit(OpCodes.Unbox, dereferencedArgumentType); + OpCodeUtil.EmitLoadIndirectOpCodeForType(gen, dereferencedArgumentType); + } + else + { + gen.Emit(OpCodes.Unbox_Any, dereferencedArgumentType); + } + } + else + { + // Possible down-cast + if (dereferencedArgumentType.IsGenericParameter) + { + gen.Emit(OpCodes.Unbox_Any, dereferencedArgumentType); + } + else if (dereferencedArgumentType.IsGenericType) + { + gen.Emit(OpCodes.Castclass, dereferencedArgumentType); + } + else if (dereferencedArgumentType.IsSubclassOf(typeof(object))) + { + gen.Emit(OpCodes.Castclass, dereferencedArgumentType); + } + } + } + } +} diff --git a/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs b/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs index 168a0d101..c84a8bcd9 100644 --- a/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs +++ b/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs @@ -326,9 +326,9 @@ public void CopyOut(LocalReference argumentsArray) method.CodeBuilder.AddStatement( new AssignStatement( dereferencedArgument, - new ConvertExpression( - dereferencedArgumentType, - new ArrayElementReference(argumentsArray, i)))); + new ConvertArgumentFromObjectExpression( + new ArrayElementReference(argumentsArray, i), + dereferencedArgumentType))); } } } @@ -378,7 +378,7 @@ public void Return(LocalReference invocation) method.CodeBuilder.AddStatement( new ReturnStatement( - new ConvertExpression(returnType, returnValue))); + new ConvertArgumentFromObjectExpression(returnValue, returnType))); } } } From b20cabf7d34573ef8b3149d51ecec2ae552c74d3 Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Sat, 24 Jan 2026 19:27:30 +0100 Subject: [PATCH 09/14] Set up a 2nd `ArgumentsMarshaller` in `InvocationTypeGenerator` --- .../Generators/InvocationTypeGenerator.cs | 281 ++++++++++-------- 1 file changed, 153 insertions(+), 128 deletions(-) diff --git a/src/Castle.Core/DynamicProxy/Generators/InvocationTypeGenerator.cs b/src/Castle.Core/DynamicProxy/Generators/InvocationTypeGenerator.cs index ca7ebe08e..c1da2b611 100644 --- a/src/Castle.Core/DynamicProxy/Generators/InvocationTypeGenerator.cs +++ b/src/Castle.Core/DynamicProxy/Generators/InvocationTypeGenerator.cs @@ -113,69 +113,9 @@ protected virtual void ImplementInvokeMethodOnTarget(ClassEmitter invocation, Pa return; } - var args = new IExpression[parameters.Length]; + var argumentsMarshaller = new ArgumentsMarshaller(invocation, invokeMethodOnTarget, parameters); - // Idea: instead of grab parameters one by one - // we should grab an array - var byRefArguments = new Dictionary(); - - for (var i = 0; i < parameters.Length; i++) - { - var param = parameters[i]; - - var paramType = invocation.GetClosedParameterType(param.ParameterType); - if (paramType.IsByRef) - { - var localReference = invokeMethodOnTarget.CodeBuilder.DeclareLocal(paramType.GetElementType()); - IExpression localValue; - -#if FEATURE_BYREFLIKE - if (paramType.GetElementType().IsByRefLikeSafe()) - { - // The argument value in the invocation `Arguments` array is an `object` - // and cannot be converted back to its original by-ref-like type. - // We need to replace it with some other value. - - // For now, we just substitute the by-ref-like type's default value: - localValue = new DefaultValueExpression(localReference.Type); - } - else -#endif - { - localValue = new ConvertExpression(paramType.GetElementType(), - new MethodInvocationExpression(ThisExpression.Instance, - InvocationMethods.GetArgumentValue, - new LiteralIntExpression(i))); - } - - invokeMethodOnTarget.CodeBuilder.AddStatement(new AssignStatement(localReference, localValue)); - var localByRef = new AddressOfExpression(localReference); - args[i] = localByRef; - byRefArguments[i] = localReference; - } - else - { -#if FEATURE_BYREFLIKE - if (paramType.IsByRefLikeSafe()) - { - // The argument value in the invocation `Arguments` array is an `object` - // and cannot be converted back to its original by-ref-like type. - // We need to replace it with some other value. - - // For now, we just substitute the by-ref-like type's default value: - args[i] = new DefaultValueExpression(paramType); - } - else -#endif - { - args[i] = - new ConvertExpression(paramType, - new MethodInvocationExpression(ThisExpression.Instance, - InvocationMethods.GetArgumentValue, - new LiteralIntExpression(i))); - } - } - } + argumentsMarshaller.CopyOut(out var args, out var byRefArguments); if (byRefArguments.Count > 0) { @@ -196,78 +136,19 @@ protected virtual void ImplementInvokeMethodOnTarget(ClassEmitter invocation, Pa invokeMethodOnTarget.CodeBuilder.AddStatement(methodOnTargetInvocationExpression); } - AssignBackByRefArguments(invokeMethodOnTarget, byRefArguments); - - if (callbackMethod.ReturnType != typeof(void)) + if (byRefArguments.Count > 0) { - IExpression retVal; - -#if FEATURE_BYREFLIKE - if (returnValue.Type.IsByRefLikeSafe()) - { - // The by-ref-like return value cannot be put into the `ReturnValue` property, - // because it cannot be boxed. We need to replace it with some other value. - - // For now, we just erase it by substituting `null`: - retVal = NullExpression.Instance; - } - else -#endif - { - retVal = new ConvertExpression(typeof(object), returnValue.Type, returnValue); - } - - var setRetVal = - new MethodInvocationExpression(ThisExpression.Instance, - InvocationMethods.SetReturnValue, - retVal); - - invokeMethodOnTarget.CodeBuilder.AddStatement(setRetVal); + invokeMethodOnTarget.CodeBuilder.AddStatement(FinallyStatement.Instance); + argumentsMarshaller.CopyIn(byRefArguments); + invokeMethodOnTarget.CodeBuilder.AddStatement(EndExceptionBlockStatement.Instance); } - invokeMethodOnTarget.CodeBuilder.AddStatement(ReturnStatement.Instance); - } - - private void AssignBackByRefArguments(MethodEmitter invokeMethodOnTarget, Dictionary byRefArguments) - { - if (byRefArguments.Count == 0) + if (callbackMethod.ReturnType != typeof(void)) { - return; + argumentsMarshaller.SetReturnValue(returnValue); } - invokeMethodOnTarget.CodeBuilder.AddStatement(FinallyStatement.Instance); - - foreach (var byRefArgument in byRefArguments) - { - var index = byRefArgument.Key; - var localReference = byRefArgument.Value; - IExpression localValue; - -#if FEATURE_BYREFLIKE - if (localReference.Type.IsByRefLikeSafe()) - { - // The by-ref-like value in the local buffer variable cannot be put back - // into the invocation `Arguments` array, because it cannot be boxed. - // We need to replace it with some other value. - - // For now, we just erase it by substituting `null`: - localValue = NullExpression.Instance; - } - else -#endif - { - localValue = new ConvertExpression(typeof(object), localReference.Type, localReference); - } - - invokeMethodOnTarget.CodeBuilder.AddStatement( - new MethodInvocationExpression( - ThisExpression.Instance, - InvocationMethods.SetArgumentValue, - new LiteralIntExpression(index), - localValue)); - } - - invokeMethodOnTarget.CodeBuilder.AddStatement(EndExceptionBlockStatement.Instance); + invokeMethodOnTarget.CodeBuilder.AddStatement(ReturnStatement.Instance); } private void CreateConstructor(ClassEmitter invocation) @@ -369,5 +250,149 @@ private void ImplementChangeProxyTargetInterface(ClassEmitter @class, ClassEmitt ImplementChangeProxyTarget(invocation, @class); } + + private struct ArgumentsMarshaller + { + private readonly ClassEmitter invocation; + private readonly MethodEmitter method; + private readonly ParameterInfo[] parameters; + + public ArgumentsMarshaller(ClassEmitter invocation, MethodEmitter method, ParameterInfo[] parameters) + { + this.invocation = invocation; + this.method = method; + this.parameters = parameters; + } + + public void CopyOut(out IExpression[] arguments, out Dictionary byRefArguments) + { + arguments = new IExpression[parameters.Length]; + + // Idea: instead of grab parameters one by one + // we should grab an array + byRefArguments = new Dictionary(); + + for (int i = 0, n = parameters.Length; i < n; ++i) + { + var param = parameters[i]; + + var paramType = invocation.GetClosedParameterType(param.ParameterType); + if (paramType.IsByRef) + { + var localReference = method.CodeBuilder.DeclareLocal(paramType.GetElementType()); + IExpression localValue; + +#if FEATURE_BYREFLIKE + if (paramType.GetElementType().IsByRefLikeSafe()) + { + // The argument value in the invocation `Arguments` array is an `object` + // and cannot be converted back to its original by-ref-like type. + // We need to replace it with some other value. + + // For now, we just substitute the by-ref-like type's default value: + localValue = new DefaultValueExpression(localReference.Type); + } + else +#endif + { + localValue = new ConvertExpression( + paramType.GetElementType(), + new MethodInvocationExpression( + ThisExpression.Instance, + InvocationMethods.GetArgumentValue, + new LiteralIntExpression(i))); + } + + method.CodeBuilder.AddStatement(new AssignStatement(localReference, localValue)); + var localByRef = new AddressOfExpression(localReference); + arguments[i] = localByRef; + byRefArguments[i] = localReference; + } + else + { +#if FEATURE_BYREFLIKE + if (paramType.IsByRefLikeSafe()) + { + // The argument value in the invocation `Arguments` array is an `object` + // and cannot be converted back to its original by-ref-like type. + // We need to replace it with some other value. + + // For now, we just substitute the by-ref-like type's default value: + arguments[i] = new DefaultValueExpression(paramType); + } + else +#endif + { + arguments[i] = new ConvertExpression( + paramType, + new MethodInvocationExpression( + ThisExpression.Instance, + InvocationMethods.GetArgumentValue, + new LiteralIntExpression(i))); + } + } + } + } + + public void CopyIn(Dictionary byRefArguments) + { + foreach (var byRefArgument in byRefArguments) + { + var index = byRefArgument.Key; + var localReference = byRefArgument.Value; + IExpression localValue; + +#if FEATURE_BYREFLIKE + if (localReference.Type.IsByRefLikeSafe()) + { + // The by-ref-like value in the local buffer variable cannot be put back + // into the invocation `Arguments` array, because it cannot be boxed. + // We need to replace it with some other value. + + // For now, we just erase it by substituting `null`: + localValue = NullExpression.Instance; + } + else +#endif + { + localValue = new ConvertExpression(typeof(object), localReference.Type, localReference); + } + + method.CodeBuilder.AddStatement( + new MethodInvocationExpression( + ThisExpression.Instance, + InvocationMethods.SetArgumentValue, + new LiteralIntExpression(index), + localValue)); + } + } + + public void SetReturnValue(LocalReference returnValue) + { + IExpression retVal; + +#if FEATURE_BYREFLIKE + if (returnValue.Type.IsByRefLikeSafe()) + { + // The by-ref-like return value cannot be put into the `ReturnValue` property, + // because it cannot be boxed. We need to replace it with some other value. + + // For now, we just erase it by substituting `null`: + retVal = NullExpression.Instance; + } + else +#endif + { + retVal = new ConvertExpression(typeof(object), returnValue.Type, returnValue); + } + + var setRetVal = new MethodInvocationExpression( + ThisExpression.Instance, + InvocationMethods.SetReturnValue, + retVal); + + method.CodeBuilder.AddStatement(setRetVal); + } + } } } \ No newline at end of file From a6b90f84d23ff357a075438c454440d1dbc8bc7a Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Sat, 24 Jan 2026 19:53:20 +0100 Subject: [PATCH 10/14] Refactor/deduplicate logic in new marshaller type --- .../Generators/InvocationTypeGenerator.cs | 129 ++++++++---------- 1 file changed, 57 insertions(+), 72 deletions(-) diff --git a/src/Castle.Core/DynamicProxy/Generators/InvocationTypeGenerator.cs b/src/Castle.Core/DynamicProxy/Generators/InvocationTypeGenerator.cs index c1da2b611..d8ab60a1d 100644 --- a/src/Castle.Core/DynamicProxy/Generators/InvocationTypeGenerator.cs +++ b/src/Castle.Core/DynamicProxy/Generators/InvocationTypeGenerator.cs @@ -274,62 +274,42 @@ public void CopyOut(out IExpression[] arguments, out Dictionary byRefArguments) foreach (var byRefArgument in byRefArguments) { var index = byRefArgument.Key; - var localReference = byRefArgument.Value; - IExpression localValue; + var localCopy = byRefArgument.Value; #if FEATURE_BYREFLIKE - if (localReference.Type.IsByRefLikeSafe()) + if (localCopy.Type.IsByRefLikeSafe()) { // The by-ref-like value in the local buffer variable cannot be put back // into the invocation `Arguments` array, because it cannot be boxed. // We need to replace it with some other value. // For now, we just erase it by substituting `null`: - localValue = NullExpression.Instance; + method.CodeBuilder.AddStatement( + new MethodInvocationExpression( + ThisExpression.Instance, + InvocationMethods.SetArgumentValue, + new LiteralIntExpression(index), + NullExpression.Instance)); } else #endif { - localValue = new ConvertExpression(typeof(object), localReference.Type, localReference); + method.CodeBuilder.AddStatement( + new MethodInvocationExpression( + ThisExpression.Instance, + InvocationMethods.SetArgumentValue, + new LiteralIntExpression(index), + new ConvertExpression( + typeof(object), + localCopy.Type, + localCopy))); } - - method.CodeBuilder.AddStatement( - new MethodInvocationExpression( - ThisExpression.Instance, - InvocationMethods.SetArgumentValue, - new LiteralIntExpression(index), - localValue)); } } public void SetReturnValue(LocalReference returnValue) { - IExpression retVal; - #if FEATURE_BYREFLIKE if (returnValue.Type.IsByRefLikeSafe()) { @@ -378,20 +361,22 @@ public void SetReturnValue(LocalReference returnValue) // because it cannot be boxed. We need to replace it with some other value. // For now, we just erase it by substituting `null`: - retVal = NullExpression.Instance; + method.CodeBuilder.AddStatement(new MethodInvocationExpression( + ThisExpression.Instance, + InvocationMethods.SetReturnValue, + NullExpression.Instance)); } else #endif { - retVal = new ConvertExpression(typeof(object), returnValue.Type, returnValue); + method.CodeBuilder.AddStatement(new MethodInvocationExpression( + ThisExpression.Instance, + InvocationMethods.SetReturnValue, + new ConvertExpression( + typeof(object), + returnValue.Type, + returnValue))); } - - var setRetVal = new MethodInvocationExpression( - ThisExpression.Instance, - InvocationMethods.SetReturnValue, - retVal); - - method.CodeBuilder.AddStatement(setRetVal); } } } From e80c1f5f3e2968ecff2d4712cc1db219f5f78041 Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Sat, 24 Jan 2026 19:59:23 +0100 Subject: [PATCH 11/14] Implement 'we should grab an array' idea Using a sparse dictionary instead of a non-sparse plain array for track- ing mutable by-ref parameters is likely only more efficient for methods with a large number of parameters. While we're at it, prevent unnecessary allocations for parameter-less methods. --- .../Generators/InvocationTypeGenerator.cs | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/Castle.Core/DynamicProxy/Generators/InvocationTypeGenerator.cs b/src/Castle.Core/DynamicProxy/Generators/InvocationTypeGenerator.cs index d8ab60a1d..32dd256a2 100644 --- a/src/Castle.Core/DynamicProxy/Generators/InvocationTypeGenerator.cs +++ b/src/Castle.Core/DynamicProxy/Generators/InvocationTypeGenerator.cs @@ -115,9 +115,9 @@ protected virtual void ImplementInvokeMethodOnTarget(ClassEmitter invocation, Pa var argumentsMarshaller = new ArgumentsMarshaller(invocation, invokeMethodOnTarget, parameters); - argumentsMarshaller.CopyOut(out var args, out var byRefArguments); + argumentsMarshaller.CopyOut(out var args, out var byRefArguments, out var hasByRefArguments); - if (byRefArguments.Count > 0) + if (hasByRefArguments) { invokeMethodOnTarget.CodeBuilder.AddStatement(TryStatement.Instance); } @@ -136,7 +136,7 @@ protected virtual void ImplementInvokeMethodOnTarget(ClassEmitter invocation, Pa invokeMethodOnTarget.CodeBuilder.AddStatement(methodOnTargetInvocationExpression); } - if (byRefArguments.Count > 0) + if (hasByRefArguments) { invokeMethodOnTarget.CodeBuilder.AddStatement(FinallyStatement.Instance); argumentsMarshaller.CopyIn(byRefArguments); @@ -264,13 +264,19 @@ public ArgumentsMarshaller(ClassEmitter invocation, MethodEmitter method, Parame this.parameters = parameters; } - public void CopyOut(out IExpression[] arguments, out Dictionary byRefArguments) + public void CopyOut(out IExpression[] arguments, out LocalReference[] byRefArguments, out bool hasByRefArguments) { - arguments = new IExpression[parameters.Length]; + if (parameters.Length == 0) + { + arguments = []; + byRefArguments = []; + hasByRefArguments = false; + return; + } - // Idea: instead of grab parameters one by one - // we should grab an array - byRefArguments = new Dictionary(); + arguments = new IExpression[parameters.Length]; + byRefArguments = new LocalReference[parameters.Length]; + hasByRefArguments = false; for (int i = 0, n = parameters.Length; i < n; ++i) { @@ -306,6 +312,7 @@ public void CopyOut(out IExpression[] arguments, out Dictionary byRefArguments) + public void CopyIn(LocalReference[] byRefArguments) { - foreach (var byRefArgument in byRefArguments) + for (int i = 0, n = byRefArguments.Length; i < n; ++i) { - var index = byRefArgument.Key; - var localCopy = byRefArgument.Value; + var localCopy = byRefArguments[i]; + if (localCopy == null) continue; #if FEATURE_BYREFLIKE if (localCopy.Type.IsByRefLikeSafe()) @@ -333,7 +340,7 @@ public void CopyIn(Dictionary byRefArguments) new MethodInvocationExpression( ThisExpression.Instance, InvocationMethods.SetArgumentValue, - new LiteralIntExpression(index), + new LiteralIntExpression(i), NullExpression.Instance)); } else @@ -343,7 +350,7 @@ public void CopyIn(Dictionary byRefArguments) new MethodInvocationExpression( ThisExpression.Instance, InvocationMethods.SetArgumentValue, - new LiteralIntExpression(index), + new LiteralIntExpression(i), new ConvertExpression( typeof(object), localCopy.Type, From f25ffe41d2a0ca3ef50dc8ef9fe6de2a1258a565 Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Sat, 24 Jan 2026 20:04:42 +0100 Subject: [PATCH 12/14] Enable nullable ref type annotations in `InvocationTypeGenerator` --- .../Generators/InvocationTypeGenerator.cs | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/Castle.Core/DynamicProxy/Generators/InvocationTypeGenerator.cs b/src/Castle.Core/DynamicProxy/Generators/InvocationTypeGenerator.cs index 32dd256a2..1d8a85036 100644 --- a/src/Castle.Core/DynamicProxy/Generators/InvocationTypeGenerator.cs +++ b/src/Castle.Core/DynamicProxy/Generators/InvocationTypeGenerator.cs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#nullable enable + namespace Castle.DynamicProxy.Generators { using System; @@ -96,7 +98,7 @@ protected virtual MethodInvocationExpression GetCallbackMethodInvocation(ClassEm return contributor.GetCallbackMethodInvocation(invocation, args, targetField, invokeMethodOnTarget); } var methodOnTargetInvocationExpression = new MethodInvocationExpression( - new AsTypeExpression(targetField, callbackMethod.DeclaringType), + new AsTypeExpression(targetField, callbackMethod.DeclaringType!), callbackMethod, args) { VirtualCall = true }; return methodOnTargetInvocationExpression; @@ -124,7 +126,7 @@ protected virtual void ImplementInvokeMethodOnTarget(ClassEmitter invocation, Pa var methodOnTargetInvocationExpression = GetCallbackMethodInvocation(invocation, args, callbackMethod, targetField, invokeMethodOnTarget); - LocalReference returnValue = null; + LocalReference? returnValue = null; if (callbackMethod.ReturnType != typeof(void)) { var returnType = invocation.GetClosedParameterType(callbackMethod.ReturnType); @@ -145,7 +147,7 @@ protected virtual void ImplementInvokeMethodOnTarget(ClassEmitter invocation, Pa if (callbackMethod.ReturnType != typeof(void)) { - argumentsMarshaller.SetReturnValue(returnValue); + argumentsMarshaller.SetReturnValue(returnValue!); } invokeMethodOnTarget.CodeBuilder.AddStatement(ReturnStatement.Instance); @@ -178,7 +180,7 @@ private void EmitCallThrowOnNoTarget(MethodEmitter invokeMethodOnTarget) invokeMethodOnTarget.CodeBuilder.AddStatement(ReturnStatement.Instance); } - private MethodInfo GetCallbackMethod(ClassEmitter invocation) + private MethodInfo? GetCallbackMethod(ClassEmitter invocation) { if (contributor != null) { @@ -200,7 +202,7 @@ private MethodInfo GetCallbackMethod(ClassEmitter invocation) private ClassEmitter GetEmitter(ClassEmitter @class, Type[] interfaces, INamingScope namingScope, MethodInfo methodInfo) { - var suggestedName = string.Format("Castle.Proxies.Invocations.{0}_{1}", methodInfo.DeclaringType.Name, + var suggestedName = string.Format("Castle.Proxies.Invocations.{0}_{1}", methodInfo.DeclaringType!.Name, methodInfo.Name); var uniqueName = namingScope.ParentScope.GetUniqueName(suggestedName); return new ClassEmitter(@class.ModuleScope, uniqueName, GetBaseType(), interfaces, ClassEmitter.DefaultTypeAttributes, forceUnsigned: @class.InStrongNamedModule == false); @@ -232,7 +234,7 @@ private void ImplementChangeProxyTarget(ClassEmitter invocation, ClassEmitter @c new AssignStatement(localProxy, new ConvertExpression(localProxy.Type, proxyObject))); - var dynSetProxy = typeof(IProxyTargetAccessor).GetMethod(nameof(IProxyTargetAccessor.DynProxySetTarget)); + var dynSetProxy = typeof(IProxyTargetAccessor).GetMethod(nameof(IProxyTargetAccessor.DynProxySetTarget))!; changeProxyTarget.CodeBuilder.AddStatement( new MethodInvocationExpression(localProxy, dynSetProxy, changeProxyTarget.Arguments[0]) @@ -264,7 +266,7 @@ public ArgumentsMarshaller(ClassEmitter invocation, MethodEmitter method, Parame this.parameters = parameters; } - public void CopyOut(out IExpression[] arguments, out LocalReference[] byRefArguments, out bool hasByRefArguments) + public void CopyOut(out IExpression[] arguments, out LocalReference?[] byRefArguments, out bool hasByRefArguments) { if (parameters.Length == 0) { @@ -275,13 +277,13 @@ public void CopyOut(out IExpression[] arguments, out LocalReference[] byRefArgum } arguments = new IExpression[parameters.Length]; - byRefArguments = new LocalReference[parameters.Length]; + byRefArguments = new LocalReference?[parameters.Length]; hasByRefArguments = false; for (int i = 0, n = parameters.Length; i < n; ++i) { var argumentType = invocation.GetClosedParameterType(parameters[i].ParameterType); - var dereferencedArgumentType = argumentType.IsByRef ? argumentType.GetElementType() : argumentType; + var dereferencedArgumentType = argumentType.IsByRef ? argumentType.GetElementType()! : argumentType; IExpression dereferencedArgument; @@ -321,7 +323,7 @@ public void CopyOut(out IExpression[] arguments, out LocalReference[] byRefArgum } } - public void CopyIn(LocalReference[] byRefArguments) + public void CopyIn(LocalReference?[] byRefArguments) { for (int i = 0, n = byRefArguments.Length; i < n; ++i) { From 283308b1d99f5c36781971e450179917dc55c3b5 Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Sat, 24 Jan 2026 20:12:57 +0100 Subject: [PATCH 13/14] Replace `HasByRefArguments` in `MethodWithInvocationGenerator` --- .../MethodWithInvocationGenerator.cs | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs b/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs index c84a8bcd9..624d241d1 100644 --- a/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs +++ b/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs @@ -103,9 +103,7 @@ protected override MethodEmitter BuildProxiedMethodBody(MethodEmitter emitter, C var argumentsMarshaller = new ArgumentsMarshaller(emitter, MethodToOverride.GetParameters()); - argumentsMarshaller.CopyIn(out var argumentsArray); - - var hasByRefArguments = HasByRefArguments(emitter.Arguments); + argumentsMarshaller.CopyIn(out var argumentsArray, out var hasByRefArguments); var ctorArguments = GetCtorArguments(@class, proxiedMethodTokenExpression, argumentsArray, methodInterceptors); ctorArguments = ModifyArguments(@class, ctorArguments); @@ -220,19 +218,6 @@ private IExpression[] ModifyArguments(ClassEmitter @class, IExpression[] argumen return contributor.GetConstructorInvocationArguments(arguments, @class); } - private bool HasByRefArguments(ArgumentReference[] arguments) - { - for (int i = 0; i < arguments.Length; i++ ) - { - if (arguments[i].Type.IsByRef) - { - return true; - } - } - - return false; - } - private struct ArgumentsMarshaller { private readonly MethodEmitter method; @@ -244,11 +229,12 @@ public ArgumentsMarshaller(MethodEmitter method, ParameterInfo[] parameters) this.parameters = parameters; } - public void CopyIn(out LocalReference argumentsArray) + public void CopyIn(out LocalReference argumentsArray, out bool hasByRefArguments) { var arguments = method.Arguments; argumentsArray = method.CodeBuilder.DeclareLocal(typeof(object[])); + hasByRefArguments = false; method.CodeBuilder.AddStatement( new AssignStatement( @@ -258,7 +244,16 @@ public void CopyIn(out LocalReference argumentsArray) for (int i = 0, n = arguments.Length; i < n; ++i) { var argument = arguments[i]; - Reference dereferencedArgument = argument.Type.IsByRef ? new IndirectReference(argument) : argument; + Reference dereferencedArgument; + if (argument.Type.IsByRef) + { + dereferencedArgument = new IndirectReference(argument); + hasByRefArguments = true; + } + else + { + dereferencedArgument = argument; + } var dereferencedArgumentType = dereferencedArgument.Type; #if FEATURE_BYREFLIKE From 1de62ba02d3e39f5ea0d98f33da78c865a406662 Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Sat, 24 Jan 2026 20:20:01 +0100 Subject: [PATCH 14/14] Use `ConvertArgument...` AST node types in 2nd marshaller --- .../ConvertArgumentFromObjectExpression.cs | 3 +-- .../Generators/InvocationTypeGenerator.cs | 16 +++++----------- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ConvertArgumentFromObjectExpression.cs b/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ConvertArgumentFromObjectExpression.cs index ce8523f75..209a8bb72 100644 --- a/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ConvertArgumentFromObjectExpression.cs +++ b/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ConvertArgumentFromObjectExpression.cs @@ -23,9 +23,8 @@ internal class ConvertArgumentFromObjectExpression : IExpression private readonly IExpression obj; private Type dereferencedArgumentType; - public ConvertArgumentFromObjectExpression(Reference obj, Type dereferencedArgumentType) + public ConvertArgumentFromObjectExpression(IExpression obj, Type dereferencedArgumentType) { - Debug.Assert(obj.Type == typeof(object)); Debug.Assert(dereferencedArgumentType.IsByRef == false); this.obj = obj; diff --git a/src/Castle.Core/DynamicProxy/Generators/InvocationTypeGenerator.cs b/src/Castle.Core/DynamicProxy/Generators/InvocationTypeGenerator.cs index 1d8a85036..04958beaf 100644 --- a/src/Castle.Core/DynamicProxy/Generators/InvocationTypeGenerator.cs +++ b/src/Castle.Core/DynamicProxy/Generators/InvocationTypeGenerator.cs @@ -300,12 +300,12 @@ public void CopyOut(out IExpression[] arguments, out LocalReference?[] byRefArgu else #endif { - dereferencedArgument = new ConvertExpression( - dereferencedArgumentType, + dereferencedArgument = new ConvertArgumentFromObjectExpression( new MethodInvocationExpression( ThisExpression.Instance, InvocationMethods.GetArgumentValue, - new LiteralIntExpression(i))); + new LiteralIntExpression(i)), + dereferencedArgumentType); } if (argumentType.IsByRef) @@ -353,10 +353,7 @@ public void CopyIn(LocalReference?[] byRefArguments) ThisExpression.Instance, InvocationMethods.SetArgumentValue, new LiteralIntExpression(i), - new ConvertExpression( - typeof(object), - localCopy.Type, - localCopy))); + new ConvertArgumentToObjectExpression(localCopy))); } } } @@ -381,10 +378,7 @@ public void SetReturnValue(LocalReference returnValue) method.CodeBuilder.AddStatement(new MethodInvocationExpression( ThisExpression.Instance, InvocationMethods.SetReturnValue, - new ConvertExpression( - typeof(object), - returnValue.Type, - returnValue))); + new ConvertArgumentToObjectExpression(returnValue))); } } }