From 904397f664a9a5733bdac00de1b6fe5393629f71 Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Sun, 3 Sep 2023 15:10:20 +0200 Subject: [PATCH 1/5] New cond. compilation symbol `FEATURE_BYREFLIKE` --- README.md | 14 ++++++++------ buildscripts/common.props | 12 ++++++++++-- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 9756c3ef1f..1000f131df 100644 --- a/README.md +++ b/README.md @@ -60,14 +60,16 @@ For known Mono defects, check [our issue tracker](https://github.com/castleproje The following conditional compilation symbols (vertical) are currently defined for each of the build configurations (horizontal): -Symbol | .NET 4.6.2 | .NET Standard 2.x and .NET 6 ------------------------------------ | ------------------ | ---------------------------- -`FEATURE_APPDOMAIN` | :white_check_mark: | :no_entry_sign: -`FEATURE_ASSEMBLYBUILDER_SAVE` | :white_check_mark: | :no_entry_sign: -`FEATURE_SERIALIZATION` | :white_check_mark: | :no_entry_sign: -`FEATURE_SYSTEM_CONFIGURATION` | :white_check_mark: | :no_entry_sign: +Symbol | .NET 4.6.2 | .NET Standard 2.0 | .NET Standard 2.1 and .NET 6 +----------------------------------- | ------------------ | ------------------| ---------------------------- +`FEATURE_APPDOMAIN` | :white_check_mark: | :no_entry_sign: | :no_entry_sign: +`FEATURE_ASSEMBLYBUILDER_SAVE` | :white_check_mark: | :no_entry_sign: | :no_entry_sign: +`FEATURE_BYREFLIKE` | :no_entry_sign: | :no_entry_sign: | :white_check_mark: +`FEATURE_SERIALIZATION` | :white_check_mark: | :no_entry_sign: | :no_entry_sign: +`FEATURE_SYSTEM_CONFIGURATION` | :white_check_mark: | :no_entry_sign: | :no_entry_sign: * `FEATURE_APPDOMAIN` - enables support for features that make use of an AppDomain in the host. * `FEATURE_ASSEMBLYBUILDER_SAVE` - enabled support for saving the dynamically generated proxy assembly. +* `FEATURE_BYREFLIKE` - enables support for by-ref-like (`ref struct`) types such as `Span` and `ReadOnlySpan`. * `FEATURE_SERIALIZATION` - enables support for serialization of dynamic proxies and other types. * `FEATURE_SYSTEM_CONFIGURATION` - enables features that use System.Configuration and the ConfigurationManager. diff --git a/buildscripts/common.props b/buildscripts/common.props index 157f3f4433..b24dec8f99 100644 --- a/buildscripts/common.props +++ b/buildscripts/common.props @@ -46,7 +46,7 @@ DEBUG TRACE - TRACE + TRACE;FEATURE_BYREFLIKE TRACE;FEATURE_APPDOMAIN;FEATURE_ASSEMBLYBUILDER_SAVE;FEATURE_SERIALIZATION;FEATURE_SYSTEM_CONFIGURATION @@ -89,7 +89,15 @@ $(NetStandard21Constants) - + + + $(DiagnosticsConstants);$(NetStandard21Constants) + + + + $(NetStandard21Constants) + + From 375fe40dcc02dfd000e150fc2b684d4eb7e2142f Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Sun, 3 Sep 2023 15:02:46 +0200 Subject: [PATCH 2/5] Add failing tests for by-ref-like types * Most tests fail with a `InvalidProgramException`: "Cannot create boxed ByRef-like values.". * One test fails with a `InvalidOperationException`: "Interceptors failed to set a return value [...]." --- .../DynamicProxy.Tests/ByRefLikeTestCase.cs | 190 ++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 src/Castle.Core.Tests/DynamicProxy.Tests/ByRefLikeTestCase.cs diff --git a/src/Castle.Core.Tests/DynamicProxy.Tests/ByRefLikeTestCase.cs b/src/Castle.Core.Tests/DynamicProxy.Tests/ByRefLikeTestCase.cs new file mode 100644 index 0000000000..eb4d366248 --- /dev/null +++ b/src/Castle.Core.Tests/DynamicProxy.Tests/ByRefLikeTestCase.cs @@ -0,0 +1,190 @@ +// Copyright 2004-2023 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. + +#if FEATURE_BYREFLIKE + +namespace Castle.DynamicProxy.Tests +{ + using System; + + using Castle.DynamicProxy.Tests.Interceptors; + + using NUnit.Framework; + + /// + /// Tests for by-ref-like ( ) method parameter and return types. + /// + [TestFixture] + public class ByRefLikeTestCase : BasePEVerifyTestCase + { + [TestCase(typeof(IHaveMethodWithByRefLikeParameter))] + [TestCase(typeof(IHaveMethodWithByRefLikeInParameter))] + [TestCase(typeof(IHaveMethodWithByRefLikeRefParameter))] + [TestCase(typeof(IHaveMethodWithByRefLikeOutParameter))] + [TestCase(typeof(IHaveMethodWithByRefLikeReturnType))] + public void Can_proxy_type(Type interfaceType) + { + _ = generator.CreateInterfaceProxyWithoutTarget(interfaceType); + } + + [Test] + public void Can_invoke_method_with_by_ref_like_parameter() + { + var proxy = generator.CreateInterfaceProxyWithoutTarget(new DoNothingInterceptor()); + var arg = default(ByRefLike); + proxy.Method(arg); + } + + [Test] + public void Can_invoke_method_with_by_ref_like_in_parameter() + { + var proxy = generator.CreateInterfaceProxyWithoutTarget(new DoNothingInterceptor()); + ByRefLike arg = default; + proxy.Method(in arg); + } + + [Test] + public void Can_invoke_method_with_by_ref_like_ref_parameter() + { + var proxy = generator.CreateInterfaceProxyWithoutTarget(new DoNothingInterceptor()); + ByRefLike arg = default; + proxy.Method(ref arg); + } + + [Test] + public void Can_invoke_method_with_by_ref_like_out_parameter() + { + var proxy = generator.CreateInterfaceProxyWithoutTarget(new DoNothingInterceptor()); + proxy.Method(out _); + } + + [Test] + public void Can_invoke_method_with_by_ref_like_return_type() + { + var proxy = generator.CreateInterfaceProxyWithoutTarget(new DoNothingInterceptor()); + _ = proxy.Method(); + } + + [Test] + public void Can_proceed_to_target_method_with_by_ref_like_parameter() + { + var target = new HasMethodWithByRefLikeParameter(); + var proxy = generator.CreateInterfaceProxyWithTarget(target, new StandardInterceptor()); + ByRefLike arg = default; + proxy.Method(arg); + } + + [Test] + public void Can_proceed_to_target_method_with_by_ref_like_in_parameter() + { + var target = new HasMethodWithByRefLikeInParameter(); + var proxy = generator.CreateInterfaceProxyWithTarget(target, new StandardInterceptor()); + ByRefLike arg = default; + proxy.Method(in arg); + } + + [Test] + public void Can_proceed_to_target_method_with_by_ref_like_ref_parameter() + { + var target = new HasMethodWithByRefLikeRefParameter(); + var proxy = generator.CreateInterfaceProxyWithTarget(target, new StandardInterceptor()); + ByRefLike arg = default; + proxy.Method(ref arg); + } + + [Test] + public void Can_proceed_to_target_method_with_by_ref_like_out_parameter() + { + var target = new HasMethodWithByRefLikeOutParameter(); + var proxy = generator.CreateInterfaceProxyWithTarget(target, new StandardInterceptor()); + proxy.Method(out _); + } + + [Test] + public void Can_proceed_to_target_method_with_by_ref_like_return_type() + { + var target = new HasMethodWithByRefLikeReturnType(); + var proxy = generator.CreateInterfaceProxyWithTarget(target, new StandardInterceptor()); + _ = proxy.Method(); + } + + public ref struct ByRefLike + { + } + + public interface IHaveMethodWithByRefLikeParameter + { + void Method(ByRefLike arg); + } + + public class HasMethodWithByRefLikeParameter : IHaveMethodWithByRefLikeParameter + { + public virtual void Method(ByRefLike arg) + { + } + } + + public interface IHaveMethodWithByRefLikeInParameter + { + void Method(in ByRefLike arg); + } + + public class HasMethodWithByRefLikeInParameter : IHaveMethodWithByRefLikeInParameter + { + public virtual void Method(in ByRefLike arg) + { + } + } + + public interface IHaveMethodWithByRefLikeRefParameter + { + void Method(ref ByRefLike arg); + } + + public class HasMethodWithByRefLikeRefParameter : IHaveMethodWithByRefLikeRefParameter + { + public virtual void Method(ref ByRefLike arg) + { + } + } + + public interface IHaveMethodWithByRefLikeOutParameter + { + void Method(out ByRefLike arg); + } + + public class HasMethodWithByRefLikeOutParameter : IHaveMethodWithByRefLikeOutParameter + { + public virtual void Method(out ByRefLike arg) + { + arg = default; + } + } + + public interface IHaveMethodWithByRefLikeReturnType + { + ByRefLike Method(); + } + + public class HasMethodWithByRefLikeReturnType : IHaveMethodWithByRefLikeReturnType + { + public virtual ByRefLike Method() + { + return default; + } + } + } +} + +#endif From d99a22f1c1fd46a8474db419ab7bb362143a7a4d Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Sun, 3 Sep 2023 23:15:55 +0200 Subject: [PATCH 3/5] Avoid `object` <-> by-ref-like type conversions * Wherever we are currently converting a by-ref-like argument or return value to `object`, we substitute `null` (because boxing is disallowed for by-ref-like types). * Wherever we convert from `object`, we substitute the default value for the by-ref-like type (because unboxing likewise doesn't work). --- .../ReferencesToObjectArrayExpression.cs | 14 +++ .../DynamicProxy/Generators/GeneratorUtil.cs | 17 +++- .../Generators/InvocationTypeGenerator.cs | 95 +++++++++++++++---- .../MethodWithInvocationGenerator.cs | 35 +++++-- 4 files changed, 134 insertions(+), 27 deletions(-) diff --git a/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ReferencesToObjectArrayExpression.cs b/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ReferencesToObjectArrayExpression.cs index dea87ef9c7..968faca247 100644 --- a/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ReferencesToObjectArrayExpression.cs +++ b/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ReferencesToObjectArrayExpression.cs @@ -42,6 +42,20 @@ public void Emit(ILGenerator gen) var reference = args[i]; +#if FEATURE_BYREFLIKE + if (reference.Type.IsByRefLike) + { + // 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 + ArgumentsUtil.EmitLoadOwnerAndReference(reference, gen); if (reference.Type.IsByRef) diff --git a/src/Castle.Core/DynamicProxy/Generators/GeneratorUtil.cs b/src/Castle.Core/DynamicProxy/Generators/GeneratorUtil.cs index eb9ff841a4..3280e11a87 100644 --- a/src/Castle.Core/DynamicProxy/Generators/GeneratorUtil.cs +++ b/src/Castle.Core/DynamicProxy/Generators/GeneratorUtil.cs @@ -41,7 +41,22 @@ public static void CopyOutAndRefParameters(TypeReference[] dereferencedArguments arguments = StoreInvocationArgumentsInLocal(emitter, invocation); } - emitter.CodeBuilder.AddStatement(AssignArgument(dereferencedArguments, i, arguments)); +#if FEATURE_BYREFLIKE + var dereferencedParameterType = parameters[i].ParameterType.GetElementType(); + if (dereferencedParameterType.IsByRefLike) + { + // 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: + emitter.CodeBuilder.AddStatement(new AssignStatement(dereferencedArguments[i], new DefaultValueExpression(dereferencedParameterType))); + } + else +#endif + { + emitter.CodeBuilder.AddStatement(AssignArgument(dereferencedArguments, i, arguments)); + } } } diff --git a/src/Castle.Core/DynamicProxy/Generators/InvocationTypeGenerator.cs b/src/Castle.Core/DynamicProxy/Generators/InvocationTypeGenerator.cs index c4e022de3a..5d2253a895 100644 --- a/src/Castle.Core/DynamicProxy/Generators/InvocationTypeGenerator.cs +++ b/src/Castle.Core/DynamicProxy/Generators/InvocationTypeGenerator.cs @@ -127,24 +127,53 @@ protected virtual void ImplementInvokeMethodOnTarget(AbstractTypeEmitter invocat if (paramType.IsByRef) { var localReference = invokeMethodOnTarget.CodeBuilder.DeclareLocal(paramType.GetElementType()); - invokeMethodOnTarget.CodeBuilder - .AddStatement( - new AssignStatement(localReference, - new ConvertExpression(paramType.GetElementType(), - new MethodInvocationExpression(SelfReference.Self, - InvocationMethods.GetArgumentValue, - new LiteralIntExpression(i))))); + IExpression localValue; + +#if FEATURE_BYREFLIKE + if (paramType.GetElementType().IsByRefLike) + { + // 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(SelfReference.Self, + InvocationMethods.GetArgumentValue, + new LiteralIntExpression(i))); + } + + invokeMethodOnTarget.CodeBuilder.AddStatement(new AssignStatement(localReference, localValue)); var byRefReference = new ByRefReference(localReference); args[i] = byRefReference; byRefArguments[i] = localReference; } else { - args[i] = - new ConvertExpression(paramType, - new MethodInvocationExpression(SelfReference.Self, - InvocationMethods.GetArgumentValue, - new LiteralIntExpression(i))); +#if FEATURE_BYREFLIKE + if (paramType.IsByRefLike) + { + // 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(SelfReference.Self, + InvocationMethods.GetArgumentValue, + new LiteralIntExpression(i))); + } } } @@ -171,10 +200,27 @@ protected virtual void ImplementInvokeMethodOnTarget(AbstractTypeEmitter invocat if (callbackMethod.ReturnType != typeof(void)) { + IExpression retVal; + +#if FEATURE_BYREFLIKE + if (returnValue.Type.IsByRefLike) + { + // 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(SelfReference.Self, InvocationMethods.SetReturnValue, - new ConvertExpression(typeof(object), returnValue.Type, returnValue)); + retVal); invokeMethodOnTarget.CodeBuilder.AddStatement(setRetVal); } @@ -194,15 +240,30 @@ private void AssignBackByRefArguments(MethodEmitter invokeMethodOnTarget, Dictio { var index = byRefArgument.Key; var localReference = byRefArgument.Value; + IExpression localValue; + +#if FEATURE_BYREFLIKE + if (localReference.Type.IsByRefLike) + { + // 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( SelfReference.Self, InvocationMethods.SetArgumentValue, new LiteralIntExpression(index), - new ConvertExpression( - typeof(object), - localReference.Type, - localReference))); + localValue)); } invokeMethodOnTarget.CodeBuilder.AddStatement(new EndExceptionBlockStatement()); } diff --git a/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs b/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs index ccf636a39e..f1f7382527 100644 --- a/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs +++ b/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs @@ -138,20 +138,37 @@ protected override MethodEmitter BuildProxiedMethodBody(MethodEmitter emitter, C if (MethodToOverride.ReturnType != typeof(void)) { - var getRetVal = new MethodInvocationExpression(invocationLocal, InvocationMethods.GetReturnValue); + IExpression retVal; - // 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()) +#if FEATURE_BYREFLIKE + if (emitter.ReturnType.IsByRefLike) { - LocalReference returnValue = emitter.CodeBuilder.DeclareLocal(typeof(object)); - emitter.CodeBuilder.AddStatement(new AssignStatement(returnValue, getRetVal)); + // 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. - 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"))); + // 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); } - // Emit code to return with cast from ReturnValue - emitter.CodeBuilder.AddStatement(new ReturnStatement(new ConvertExpression(emitter.ReturnType, getRetVal))); + emitter.CodeBuilder.AddStatement(new ReturnStatement(retVal)); } else { From f61fad589987b61a06e7cb1691119e8560766498 Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Sun, 3 Sep 2023 23:19:31 +0200 Subject: [PATCH 4/5] `Type.IsByRefLike` is not always implemented ... which might be a bug in the runtime. We need a workaround for that. --- .../ReferencesToObjectArrayExpression.cs | 4 +++- .../DynamicProxy/Generators/GeneratorUtil.cs | 3 ++- .../Generators/InvocationTypeGenerator.cs | 8 ++++---- .../Generators/MethodWithInvocationGenerator.cs | 2 +- .../DynamicProxy/Internal/TypeUtil.cs | 16 ++++++++++++++++ 5 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ReferencesToObjectArrayExpression.cs b/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ReferencesToObjectArrayExpression.cs index 968faca247..a611fd54f4 100644 --- a/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ReferencesToObjectArrayExpression.cs +++ b/src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ReferencesToObjectArrayExpression.cs @@ -18,6 +18,8 @@ namespace Castle.DynamicProxy.Generators.Emitters.SimpleAST using System.Reflection; using System.Reflection.Emit; + using Castle.DynamicProxy.Internal; + internal class ReferencesToObjectArrayExpression : IExpression { private readonly TypeReference[] args; @@ -43,7 +45,7 @@ public void Emit(ILGenerator gen) var reference = args[i]; #if FEATURE_BYREFLIKE - if (reference.Type.IsByRefLike) + 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. diff --git a/src/Castle.Core/DynamicProxy/Generators/GeneratorUtil.cs b/src/Castle.Core/DynamicProxy/Generators/GeneratorUtil.cs index 3280e11a87..7e36e0c354 100644 --- a/src/Castle.Core/DynamicProxy/Generators/GeneratorUtil.cs +++ b/src/Castle.Core/DynamicProxy/Generators/GeneratorUtil.cs @@ -20,6 +20,7 @@ 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 @@ -43,7 +44,7 @@ public static void CopyOutAndRefParameters(TypeReference[] dereferencedArguments #if FEATURE_BYREFLIKE var dereferencedParameterType = parameters[i].ParameterType.GetElementType(); - if (dereferencedParameterType.IsByRefLike) + 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. diff --git a/src/Castle.Core/DynamicProxy/Generators/InvocationTypeGenerator.cs b/src/Castle.Core/DynamicProxy/Generators/InvocationTypeGenerator.cs index 5d2253a895..463b013d41 100644 --- a/src/Castle.Core/DynamicProxy/Generators/InvocationTypeGenerator.cs +++ b/src/Castle.Core/DynamicProxy/Generators/InvocationTypeGenerator.cs @@ -130,7 +130,7 @@ protected virtual void ImplementInvokeMethodOnTarget(AbstractTypeEmitter invocat IExpression localValue; #if FEATURE_BYREFLIKE - if (paramType.GetElementType().IsByRefLike) + 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. @@ -156,7 +156,7 @@ protected virtual void ImplementInvokeMethodOnTarget(AbstractTypeEmitter invocat else { #if FEATURE_BYREFLIKE - if (paramType.IsByRefLike) + 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. @@ -203,7 +203,7 @@ protected virtual void ImplementInvokeMethodOnTarget(AbstractTypeEmitter invocat IExpression retVal; #if FEATURE_BYREFLIKE - if (returnValue.Type.IsByRefLike) + 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. @@ -243,7 +243,7 @@ private void AssignBackByRefArguments(MethodEmitter invokeMethodOnTarget, Dictio IExpression localValue; #if FEATURE_BYREFLIKE - if (localReference.Type.IsByRefLike) + 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. diff --git a/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs b/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs index f1f7382527..184c0ce484 100644 --- a/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs +++ b/src/Castle.Core/DynamicProxy/Generators/MethodWithInvocationGenerator.cs @@ -141,7 +141,7 @@ protected override MethodEmitter BuildProxiedMethodBody(MethodEmitter emitter, C IExpression retVal; #if FEATURE_BYREFLIKE - if (emitter.ReturnType.IsByRefLike) + 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. diff --git a/src/Castle.Core/DynamicProxy/Internal/TypeUtil.cs b/src/Castle.Core/DynamicProxy/Internal/TypeUtil.cs index 1074b69bbc..392138d5be 100644 --- a/src/Castle.Core/DynamicProxy/Internal/TypeUtil.cs +++ b/src/Castle.Core/DynamicProxy/Internal/TypeUtil.cs @@ -144,6 +144,22 @@ internal static Type[] AsTypeArray(this GenericTypeParameterBuilder[] typeInfos) return types; } +#if FEATURE_BYREFLIKE + internal static bool IsByRefLikeSafe(this Type type) + { + try + { + return type.IsByRefLike; + } + catch (NotSupportedException) + { + // Certain System.Reflection.Emit implementations of `Type.IsByRefLike` throw this exception + // with the message "Derived classes must provide an implementation." This might be a bug in the runtime. + return false; + } + } +#endif + internal static bool IsFinalizer(this MethodInfo methodInfo) { return string.Equals("Finalize", methodInfo.Name) && methodInfo.GetBaseDefinition().DeclaringType == typeof(object); From 9f72f39e9ea8ad6453552b9e624fc448f0443342 Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Sun, 3 Sep 2023 23:39:46 +0200 Subject: [PATCH 5/5] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7c205052b..52d9cbbe35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,10 @@ Enhancements: - Two new generic method overloads `proxyGenerator.CreateClassProxy([options], constructorArguments, interceptors)` (@backstromjoel, #636) - Allow specifying which attributes should always be copied to proxy class by adding attribute type to `AttributesToAlwaysReplicate`. Previously only non-inherited, with `Inherited=false`, attributes were copied. (@shoaibshakeel381, #633) +- Minimally improved support for methods having `ref struct` parameter and return types such as `Span`: Intercepting such methods caused the runtime to throw `InvalidProgramException` and `NullReferenceException` due to forbidden conversions of `ref struct` values when transferring them into & out of `IInvocation` instances. To prevent these exceptions from being thrown, such values now get replaced with `null` in `IInvocation`, and with `default` values in return values and `out` arguments. (@stakx, #665) Bugfixes: +- `InvalidProgramException` when proxying `MemoryStream` with .NET 7 (@stakx, #651) - `ArgumentException`: "Could not find method overriding method" with overridden class method having generic by-ref parameter (@stakx, #657) - `ArgumentException`: "Cannot create an instance of `TEnum` because `Type.ContainsGenericParameters` is true" caused by `Enum` constraint on method `out` parameter (@stakx, #658)