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..209a8bb72 --- /dev/null +++ b/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ConvertArgumentFromObjectExpression.cs @@ -0,0 +1,76 @@ +// 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(IExpression obj, Type dereferencedArgumentType) + { + 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/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/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 deleted file mode 100644 index 67cb7b714..000000000 --- a/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ReferencesToObjectArrayExpression.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2004-2025 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; - using System.Reflection; - using System.Reflection.Emit; - - using Castle.DynamicProxy.Internal; - - internal class ReferencesToObjectArrayExpression : IExpression - { - private readonly Reference[] args; - - public ReferencesToObjectArrayExpression(params Reference[] args) - { - this.args = args; - } - - public void Emit(ILGenerator gen) - { - var local = gen.DeclareLocal(typeof(object?[])); - - gen.Emit(OpCodes.Ldc_I4, args.Length); - gen.Emit(OpCodes.Newarr, typeof(object)); - gen.Emit(OpCodes.Stloc, local); - - for (var i = 0; i < args.Length; i++) - { - gen.Emit(OpCodes.Ldloc, local); - gen.Emit(OpCodes.Ldc_I4, i); - - var reference = args[i]; - -#if FEATURE_BYREFLIKE - if (reference.Type.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 - - reference.Emit(gen); - - if (reference.Type.IsByRef) - { - throw new NotSupportedException(); - } - - if (reference.Type.IsValueType) - { - gen.Emit(OpCodes.Box, reference.Type); - } - else if (reference.Type.IsGenericParameter) - { - gen.Emit(OpCodes.Box, reference.Type); - } - - gen.Emit(OpCodes.Stelem_Ref); - } - - gen.Emit(OpCodes.Ldloc, local); - } - } -} \ No newline at end of file diff --git a/src/Castle.Core/DynamicProxy/Generators/GeneratorUtil.cs b/src/Castle.Core/DynamicProxy/Generators/GeneratorUtil.cs deleted file mode 100644 index a9847d664..000000000 --- a/src/Castle.Core/DynamicProxy/Generators/GeneratorUtil.cs +++ /dev/null @@ -1,96 +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.Reflection; - - 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, - 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 = StoreInvocationArgumentsInLocal(emitter, invocation); - } - -#if FEATURE_BYREFLIKE - var dereferencedParameterType = parameters[i].ParameterType.GetElementType(); - if (dereferencedParameterType.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(dereferencedArguments[i], new DefaultValueExpression(dereferencedParameterType))); - } - 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(AssignArgument(dereferencedArguments, i, arguments)); - } - } - } - } - - 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 diff --git a/src/Castle.Core/DynamicProxy/Generators/InvocationTypeGenerator.cs b/src/Castle.Core/DynamicProxy/Generators/InvocationTypeGenerator.cs index ca7ebe08e..04958beaf 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; @@ -113,78 +115,18 @@ protected virtual void ImplementInvokeMethodOnTarget(ClassEmitter invocation, Pa return; } - var args = new IExpression[parameters.Length]; - - // 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. + var argumentsMarshaller = new ArgumentsMarshaller(invocation, invokeMethodOnTarget, parameters); - // 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, out var hasByRefArguments); - if (byRefArguments.Count > 0) + if (hasByRefArguments) { invokeMethodOnTarget.CodeBuilder.AddStatement(TryStatement.Instance); } 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); @@ -196,78 +138,19 @@ protected virtual void ImplementInvokeMethodOnTarget(ClassEmitter invocation, Pa invokeMethodOnTarget.CodeBuilder.AddStatement(methodOnTargetInvocationExpression); } - AssignBackByRefArguments(invokeMethodOnTarget, byRefArguments); - - if (callbackMethod.ReturnType != typeof(void)) + if (hasByRefArguments) { - 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) @@ -297,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) { @@ -319,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); @@ -351,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]) @@ -369,5 +252,135 @@ 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 LocalReference?[] byRefArguments, out bool hasByRefArguments) + { + if (parameters.Length == 0) + { + arguments = []; + byRefArguments = []; + hasByRefArguments = false; + return; + } + + arguments = new IExpression[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; + + IExpression dereferencedArgument; + +#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: + dereferencedArgument = new DefaultValueExpression(dereferencedArgumentType); + } + else +#endif + { + dereferencedArgument = new ConvertArgumentFromObjectExpression( + new MethodInvocationExpression( + ThisExpression.Instance, + InvocationMethods.GetArgumentValue, + new LiteralIntExpression(i)), + dereferencedArgumentType); + } + + if (argumentType.IsByRef) + { + var localCopy = method.CodeBuilder.DeclareLocal(dereferencedArgumentType); + method.CodeBuilder.AddStatement(new AssignStatement(localCopy, dereferencedArgument)); + arguments[i] = new AddressOfExpression(localCopy); + byRefArguments[i] = localCopy; + hasByRefArguments = true; + } + else + { + arguments[i] = dereferencedArgument; + } + } + } + + public void CopyIn(LocalReference?[] byRefArguments) + { + for (int i = 0, n = byRefArguments.Length; i < n; ++i) + { + var localCopy = byRefArguments[i]; + if (localCopy == null) continue; + +#if FEATURE_BYREFLIKE + 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`: + method.CodeBuilder.AddStatement( + new MethodInvocationExpression( + ThisExpression.Instance, + InvocationMethods.SetArgumentValue, + new LiteralIntExpression(i), + NullExpression.Instance)); + } + else +#endif + { + method.CodeBuilder.AddStatement( + new MethodInvocationExpression( + ThisExpression.Instance, + InvocationMethods.SetArgumentValue, + new LiteralIntExpression(i), + new ConvertArgumentToObjectExpression(localCopy))); + } + } + } + + public void SetReturnValue(LocalReference returnValue) + { +#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`: + method.CodeBuilder.AddStatement(new MethodInvocationExpression( + ThisExpression.Instance, + InvocationMethods.SetReturnValue, + NullExpression.Instance)); + } + else +#endif + { + method.CodeBuilder.AddStatement(new MethodInvocationExpression( + ThisExpression.Instance, + InvocationMethods.SetReturnValue, + new ConvertArgumentToObjectExpression(returnValue))); + } + } + } } } \ 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 1e8592b10..624d241d1 100644 --- a/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs +++ b/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs @@ -101,11 +101,12 @@ protected override MethodEmitter BuildProxiedMethodBody(MethodEmitter emitter, C var methodInterceptors = SetMethodInterceptors(@class, namingScope, emitter, proxiedMethodTokenExpression); - var dereferencedArguments = IndirectReference.WrapIfByRef(emitter.Arguments); - var hasByRefArguments = HasByRefArguments(emitter.Arguments); + var argumentsMarshaller = new ArgumentsMarshaller(emitter, MethodToOverride.GetParameters()); - var arguments = GetCtorArguments(@class, proxiedMethodTokenExpression, dereferencedArguments, methodInterceptors); - var ctorArguments = ModifyArguments(@class, arguments); + argumentsMarshaller.CopyIn(out var argumentsArray, out var hasByRefArguments); + + var ctorArguments = GetCtorArguments(@class, proxiedMethodTokenExpression, argumentsArray, methodInterceptors); + ctorArguments = ModifyArguments(@class, ctorArguments); var invocationLocal = emitter.CodeBuilder.DeclareLocal(invocationType); emitter.CodeBuilder.AddStatement(new AssignStatement(invocationLocal, @@ -129,51 +130,14 @@ protected override MethodEmitter BuildProxiedMethodBody(MethodEmitter emitter, C emitter.CodeBuilder.AddStatement(FinallyStatement.Instance); } - GeneratorUtil.CopyOutAndRefParameters(dereferencedArguments, invocationLocal, MethodToOverride, emitter); + argumentsMarshaller.CopyOut(argumentsArray); if (hasByRefArguments) { 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; } @@ -232,7 +196,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 +204,7 @@ private IExpression[] GetCtorArguments(ClassEmitter @class, IExpression proxiedM ThisExpression.Instance, methodInterceptors ?? interceptors, proxiedMethodTokenExpression, - new ReferencesToObjectArrayExpression(dereferencedArguments) + argumentsArray, }; } @@ -254,17 +218,164 @@ private IExpression[] ModifyArguments(ClassEmitter @class, IExpression[] argumen return contributor.GetConstructorInvocationArguments(arguments, @class); } - private bool HasByRefArguments(ArgumentReference[] arguments) + private struct ArgumentsMarshaller { - for (int i = 0; i < arguments.Length; i++ ) + private readonly MethodEmitter method; + private readonly ParameterInfo[] parameters; + + public ArgumentsMarshaller(MethodEmitter method, ParameterInfo[] parameters) + { + this.method = method; + this.parameters = parameters; + } + + 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( + argumentsArray, + new NewArrayExpression(arguments.Length, typeof(object)))); + + for (int i = 0, n = arguments.Length; i < n; ++i) + { + var argument = arguments[i]; + Reference dereferencedArgument; + if (argument.Type.IsByRef) + { + dereferencedArgument = new IndirectReference(argument); + hasByRefArguments = true; + } + else + { + dereferencedArgument = 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) { - if (arguments[i].Type.IsByRef) + var arguments = method.Arguments; + + for (int i = 0, n = arguments.Length; i < n; ++i) { - return true; + 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 ConvertArgumentFromObjectExpression( + new ArrayElementReference(argumentsArray, i), + dereferencedArgumentType))); + } + } } } - return false; + 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 ConvertArgumentFromObjectExpression(returnValue, returnType))); + } + } } } } \ No newline at end of file