using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Reflection.Emit;
+using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
{
internal static class DelegateHelpers
{
- // This can be flipped to true using feature switches at publishing time
+ // This can be flipped to false using feature switches at publishing time
internal static bool CanEmitObjectArrayDelegate => true;
// Separate class so that the it can be trimmed away and doesn't get conflated
public static Func<Type, Func<object?[], object?>, Delegate> CreateObjectArrayDelegate { get; }
= CreateObjectArrayDelegateInternal();
- [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern",
- Justification = "Works around https://github.com/dotnet/linker/issues/2392")]
private static Func<Type, Func<object?[], object?>, Delegate> CreateObjectArrayDelegateInternal()
=> Type.GetType("Internal.Runtime.Augments.DynamicDelegateAugments")!
.GetMethod("CreateObjectArrayDelegate")!
.CreateDelegate<Func<Type, Func<object?[], object?>, Delegate>>();
}
+ private static class ForceAllowDynamicCodeLightup
+ {
+ public static Func<IDisposable>? ForceAllowDynamicCodeDelegate { get; }
+ = ForceAllowDynamicCodeDelegateInternal();
+
+ private static Func<IDisposable>? ForceAllowDynamicCodeDelegateInternal()
+ => typeof(AssemblyBuilder)
+ .GetMethod("ForceAllowDynamicCode", BindingFlags.NonPublic | BindingFlags.Static)
+ ?.CreateDelegate<Func<IDisposable>>();
+ }
+
internal static Delegate CreateObjectArrayDelegate(Type delegateType, Func<object?[], object?> handler)
{
if (CanEmitObjectArrayDelegate)
if (thunkMethod == null)
{
+ static IDisposable? CreateForceAllowDynamicCodeScope()
+ {
+ if (!RuntimeFeature.IsDynamicCodeSupported)
+ {
+ // Force 'new DynamicMethod' to not throw even though RuntimeFeature.IsDynamicCodeSupported is false.
+ // If we are running on a runtime that supports dynamic code, even though the feature switch is off,
+ // for example when running on CoreClr with PublishAot=true, this will allow IL to be emitted.
+ // If we are running on a runtime that really doesn't support dynamic code, like NativeAOT,
+ // CanEmitObjectArrayDelegate will be flipped to 'false', and this method won't be invoked.
+ return ForceAllowDynamicCodeLightup.ForceAllowDynamicCodeDelegate?.Invoke();
+ }
+
+ return null;
+ }
+
+ using IDisposable? forceAllowDynamicCodeScope = CreateForceAllowDynamicCodeScope();
+
int thunkIndex = Interlocked.Increment(ref s_ThunksCreated);
Type[] paramTypes = new Type[parameters.Length + 1];
paramTypes[0] = typeof(Func<object[], object>);
ilgen.BeginFinallyBlock();
for (int i = 0; i < parameters.Length; i++)
{
- if (parameters[i].ParameterType.IsByRef)
- {
+ if (parameters[i].ParameterType.IsByRef)
+ {
Type byrefToType = parameters[i].ParameterType.GetElementType()!;
// update parameter
/// </summary>
public abstract int ArgumentCount { get; }
- private static bool CanCreateArbitraryDelegates => true;
+ private static bool CanCreateArbitraryDelegates => RuntimeFeature.IsDynamicCodeSupported;
#region Construction
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using Microsoft.DotNet.RemoteExecutor;
using RemoteInvokeHandle remoteHandle = RemoteExecutor.Invoke(static () =>
{
- ParameterExpression param = Expression.Parameter(typeof(int));
+ CompileWorksWhenDynamicCodeNotSupportedInner();
+ }, options);
+ }
- Func<int, int> typedDel =
- Expression.Lambda<Func<int, int>>(Expression.Add(param, Expression.Constant(4)), param).Compile();
- Assert.Equal(304, typedDel(300));
+ // run the same test code as the above CompileWorksWhenDynamicCodeNotSupported test
+ // to ensure this test works correctly on all platforms - even if RemoteExecutor is not supported
+ [Fact]
+ public static void CompileWorksWhenDynamicCodeNotSupportedInner()
+ {
+ ParameterExpression param = Expression.Parameter(typeof(int));
- Delegate del =
- Expression.Lambda(Expression.Add(param, Expression.Constant(5)), param).Compile();
- Assert.Equal(305, del.DynamicInvoke(300));
- }, options);
+ Func<int, int> typedDel =
+ Expression.Lambda<Func<int, int>>(Expression.Add(param, Expression.Constant(4)), param).Compile();
+ Assert.Equal(304, typedDel(300));
+
+ Delegate del =
+ Expression.Lambda(Expression.Add(param, Expression.Constant(5)), param).Compile();
+ Assert.Equal(305, del.DynamicInvoke(300));
+
+ // testing more than 2 parameters is important because because it follows a different code path in Compile.
+ Expression<Func<int, int, int, int, int, int>> fiveParameterExpression = (a, b, c, d, e) => a + b + c + d + e;
+ Func<int, int, int, int, int, int> fiveParameterFunc = fiveParameterExpression.Compile();
+ Assert.Equal(6, fiveParameterFunc(2, 2, 1, 1, 0));
+
+ Expression<Func<int, int, int>> callExpression = (a, b) => Add(a, b);
+ Func<int, int, int> callFunc = callExpression.Compile();
+ Assert.Equal(29, callFunc(20, 9));
+
+ MethodCallExpression methodCallExpression = Expression.Call(
+ instance: null,
+ typeof(CompilerTests).GetMethod("Add4", BindingFlags.NonPublic | BindingFlags.Static),
+ Expression.Constant(5), Expression.Constant(6), Expression.Constant(7), Expression.Constant(8));
+
+ Func<int> methodCallDelegate = Expression.Lambda<Func<int>>(methodCallExpression).Compile();
+ Assert.Equal(26, methodCallDelegate());
+ }
+
+ private static int Add(int a, int b)
+ {
+ return a + b;
+ }
+
+ private static int Add4(int a, int b, int c, int d)
+ {
+ return a + b + c + d;
}
}
<!-- Used by VS4Mac via reflection to symbolize stack traces -->
<method name="GetMethodFromNativeIP" />
</type>
+ <type fullname="System.Reflection.Emit.AssemblyBuilder">
+ <!-- Used by System.Linq.Expressions via reflection -->
+ <method name="ForceAllowDynamicCode" />
+ </type>
<type fullname="System.Runtime.Serialization.SerializationInfo">
<!-- Used by System.Runtime.Serialization.Formatters via reflection -->
<method name="UpdateValue" />
{
public abstract partial class AssemblyBuilder : Assembly
{
+ [ThreadStatic]
+ private static bool t_allowDynamicCode;
+
protected AssemblyBuilder()
{
}
internal static void EnsureDynamicCodeSupported()
{
- if (!RuntimeFeature.IsDynamicCodeSupported)
+ if (!RuntimeFeature.IsDynamicCodeSupported && !t_allowDynamicCode)
{
ThrowDynamicCodeNotSupported();
}
}
+ /// <summary>
+ /// Allows dynamic code even though RuntimeFeature.IsDynamicCodeSupported is false.
+ /// </summary>
+ /// <returns>An object that, when disposed, will revert allowing dynamic code back to its initial state.</returns>
+ /// <remarks>
+ /// This is useful for cases where RuntimeFeature.IsDynamicCodeSupported returns false, but
+ /// the runtime is still capable of emitting dynamic code. For example, when generating delegates
+ /// in System.Linq.Expressions while PublishAot=true is set in the project. At debug time, the app
+ /// uses the non-AOT runtime with the IsDynamicCodeSupported feature switch set to false.
+ /// </remarks>
+ internal static IDisposable ForceAllowDynamicCode() => new ForceAllowDynamicCodeScope();
+
+ private sealed class ForceAllowDynamicCodeScope : IDisposable
+ {
+ private readonly bool _previous;
+
+ public ForceAllowDynamicCodeScope()
+ {
+ _previous = t_allowDynamicCode;
+ t_allowDynamicCode = true;
+ }
+
+ public void Dispose()
+ {
+ t_allowDynamicCode = _previous;
+ }
+ }
+
private static void ThrowDynamicCodeNotSupported() =>
throw new PlatformNotSupportedException(SR.PlatformNotSupported_ReflectionEmit);
}