Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: Prevent disallowed conversions between object and by-ref-like parameter and return types #665

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
Enhancements:
- Two new generic method overloads `proxyGenerator.CreateClassProxy<TClass>([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<T>`: 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)

Expand Down
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>` and `ReadOnlySpan<T>`.
* `FEATURE_SERIALIZATION` - enables support for serialization of dynamic proxies and other types.
* `FEATURE_SYSTEM_CONFIGURATION` - enables features that use System.Configuration and the ConfigurationManager.
12 changes: 10 additions & 2 deletions buildscripts/common.props
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
<PropertyGroup>
<DiagnosticsConstants>DEBUG</DiagnosticsConstants>
<NetStandard20Constants>TRACE</NetStandard20Constants>
<NetStandard21Constants>TRACE</NetStandard21Constants>
<NetStandard21Constants>TRACE;FEATURE_BYREFLIKE</NetStandard21Constants>
<DesktopClrConstants>TRACE;FEATURE_APPDOMAIN;FEATURE_ASSEMBLYBUILDER_SAVE;FEATURE_SERIALIZATION;FEATURE_SYSTEM_CONFIGURATION</DesktopClrConstants>
</PropertyGroup>

Expand Down Expand Up @@ -89,7 +89,15 @@
<PropertyGroup Condition="'$(TargetFramework)|$(Configuration)'=='netcoreapp3.1|Release'">
<DefineConstants>$(NetStandard21Constants)</DefineConstants>
</PropertyGroup>


<PropertyGroup Condition="'$(TargetFramework)|$(Configuration)'=='net6.0|Debug'">
<DefineConstants>$(DiagnosticsConstants);$(NetStandard21Constants)</DefineConstants>
</PropertyGroup>

<PropertyGroup Condition="'$(TargetFramework)|$(Configuration)'=='net6.0|Release'">
<DefineConstants>$(NetStandard21Constants)</DefineConstants>
</PropertyGroup>

<ItemGroup>
<None Include="$(SolutionDir)docs\images\castle-logo.png" Pack="true" PackagePath=""/>
</ItemGroup>
Expand Down
190 changes: 190 additions & 0 deletions src/Castle.Core.Tests/DynamicProxy.Tests/ByRefLikeTestCase.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Tests for by-ref-like (<see langword="ref"/> <see langword="struct"/>) method parameter and return types.
/// </summary>
[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<IHaveMethodWithByRefLikeParameter>(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<IHaveMethodWithByRefLikeInParameter>(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<IHaveMethodWithByRefLikeRefParameter>(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<IHaveMethodWithByRefLikeOutParameter>(new DoNothingInterceptor());
proxy.Method(out _);
}

[Test]
public void Can_invoke_method_with_by_ref_like_return_type()
{
var proxy = generator.CreateInterfaceProxyWithoutTarget<IHaveMethodWithByRefLikeReturnType>(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<IHaveMethodWithByRefLikeParameter>(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<IHaveMethodWithByRefLikeInParameter>(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<IHaveMethodWithByRefLikeRefParameter>(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<IHaveMethodWithByRefLikeOutParameter>(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<IHaveMethodWithByRefLikeReturnType>(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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -42,6 +44,20 @@ public void Emit(ILGenerator gen)

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

ArgumentsUtil.EmitLoadOwnerAndReference(reference, gen);

if (reference.Type.IsByRef)
Expand Down
18 changes: 17 additions & 1 deletion src/Castle.Core/DynamicProxy/Generators/GeneratorUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -41,7 +42,22 @@ internal static class GeneratorUtil
arguments = StoreInvocationArgumentsInLocal(emitter, invocation);
}

emitter.CodeBuilder.AddStatement(AssignArgument(dereferencedArguments, i, arguments));
#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:
emitter.CodeBuilder.AddStatement(new AssignStatement(dereferencedArguments[i], new DefaultValueExpression(dereferencedParameterType)));
}
else
#endif
{
emitter.CodeBuilder.AddStatement(AssignArgument(dereferencedArguments, i, arguments));
}
}
}

Expand Down