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

Modify tests to distinguish between emit- and interpreted-based invoke #69081

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Globalization;
using System.Runtime.CompilerServices;
using Xunit;

namespace System.Reflection.Tests
{
/// <summary>
/// Base class for invoke-based tests that support invoking both emit- and interpreter-based runtime implementations.
/// </summary>
public abstract class InvokeStrategy
{
public bool UseEmit { get; }

public InvokeStrategy(bool useEmit)
{
UseEmit = useEmit;
}

#if NETCOREAPP
public static bool AreTestingBindingFlagsSupported => !PlatformDetection.IsReleaseRuntime && RuntimeFeature.IsDynamicCodeCompiled
&& !PlatformDetection.IsNotMonoRuntime; // Temporary until Mono is updated.

[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMonoRuntime))] // Temporary until Mono is updated.
public void MethodInvoker_AutoEmitOrInterpreted()
{
Exception e;
MethodInfo method = typeof(ExceptionThrower).GetMethod(nameof(ExceptionThrower.ThrowForStackTrace), BindingFlags.Static | BindingFlags.Public)!;

// The first time emit may or may not be used depending if it was already called and whether emit is available.
e = Assert.Throws<TargetInvocationException>(() => method.Invoke(obj: null, parameters: null));
Assert.Contains("LocateMe", e.InnerException.Message);

// The second time uses emit if available.
e = Assert.Throws<TargetInvocationException>(() => method.Invoke(obj: null, parameters: null));
Assert.Contains("LocateMe", e.InnerException.Message);

if (RuntimeFeature.IsDynamicCodeCompiled)
{
Assert.Contains("InvokeStub_", e.InnerException.StackTrace);
}
else
{
Assert.DoesNotContain("InvokeStub_", e.InnerException.StackTrace);
}
}

[ConditionalFact(typeof(InvokeStrategy), nameof(InvokeStrategy.AreTestingBindingFlagsSupported))]
public void MethodInvoker_ExplicitEmitAndInterpreted()
{
Exception e;
MethodInfo method = typeof(ExceptionThrower).GetMethod(nameof(ExceptionThrower.ThrowForStackTrace), BindingFlags.Static | BindingFlags.Public)!;

e = Assert.Throws<TargetInvocationException>(() =>
method.Invoke(obj: null, (BindingFlags)TestingBindingFlags.InvokeWithEmit, binder: null, parameters: null, culture: null));
Assert.Contains("LocateMe", e.InnerException.Message);
Assert.Contains("InvokeStub_", e.InnerException.StackTrace);

e = Assert.Throws<TargetInvocationException>(() =>
method.Invoke(obj: null, (BindingFlags)TestingBindingFlags.InvokeWithInterpreter, binder: null, parameters: null, culture: null));
Assert.Contains("LocateMe", e.InnerException.Message);
Assert.DoesNotContain("InvokeStub_", e.InnerException.StackTrace);
}

[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMonoRuntime))] // Temporary until Mono is updated.
public void ConstructorInvoker_AutoEmitOrInterpreted()
{
Exception e;
ConstructorInfo method = typeof(ExceptionThrower).GetConstructor(Type.EmptyTypes)!;

// The first time emit may or may not be used depending if it was already called and whether emit is available.
e = Assert.Throws<TargetInvocationException>(() => method.Invoke(parameters: null));
Assert.Contains("LocateMe", e.InnerException.Message);

// The second time uses emit if available.
e = Assert.Throws<TargetInvocationException>(() => method.Invoke(parameters: null));
Assert.Contains("LocateMe", e.InnerException.Message);

if (RuntimeFeature.IsDynamicCodeCompiled)
{
Assert.Contains("InvokeStub_", e.InnerException.StackTrace);
}
else
{
Assert.DoesNotContain("InvokeStub_", e.InnerException.StackTrace);
}
}

[ConditionalFact(typeof(InvokeStrategy), nameof(InvokeStrategy.AreTestingBindingFlagsSupported))]
public void ConstructorInvoker_ExplicitEmitAndInterpreted()
{
Exception e;
ConstructorInfo method = typeof(ExceptionThrower).GetConstructor(Type.EmptyTypes)!;

e = Assert.Throws<TargetInvocationException>(() =>
method.Invoke((BindingFlags)TestingBindingFlags.InvokeWithEmit, binder: null, parameters: null, culture: null));
Assert.Contains("LocateMe", e.InnerException.Message);
Assert.Contains("InvokeStub_", e.InnerException.StackTrace);

e = Assert.Throws<TargetInvocationException>(() =>
method.Invoke((BindingFlags)TestingBindingFlags.InvokeWithInterpreter, binder: null, parameters: null, culture: null));
Assert.Contains("LocateMe", e.InnerException.Message);
Assert.DoesNotContain("InvokeStub_", e.InnerException.StackTrace);
}
#endif

public object? Invoke(MethodBase method, object? obj, object?[]? parameters)
{
return method.Invoke(
obj,
(BindingFlags)(UseEmit ? TestingBindingFlags.InvokeWithEmit : TestingBindingFlags.InvokeWithInterpreter),
binder: null,
parameters,
culture: null);
}

public object? Invoke(
MethodBase method,
object? obj,
BindingFlags invokeAttr,
Binder? binder,
object?[]? parameters,
CultureInfo? culture)
{
return method.Invoke(
obj,
invokeAttr | (BindingFlags)(UseEmit ? TestingBindingFlags.InvokeWithEmit : TestingBindingFlags.InvokeWithInterpreter),
binder,
parameters,
culture);
}

public object? Invoke(ConstructorInfo method, object?[]? parameters)
{
return method.Invoke(
(BindingFlags)(UseEmit ? TestingBindingFlags.InvokeWithEmit : TestingBindingFlags.InvokeWithInterpreter),
binder: null,
parameters,
culture: null);
}

public object? Invoke(
ConstructorInfo method,
BindingFlags invokeAttr,
Binder? binder,
object?[]? parameters,
CultureInfo? culture)
{
return method.Invoke(
invokeAttr | (BindingFlags)(UseEmit ? TestingBindingFlags.InvokeWithEmit : TestingBindingFlags.InvokeWithInterpreter),
binder,
parameters,
culture);
}

public object? InvokeMember(
Type type,
string name,
BindingFlags invokeAttr,
Binder? binder,
object? target,
object?[]? args,
ParameterModifier[]? modifiers,
CultureInfo? culture,
string[]? namedParameters)
{
return type.InvokeMember(
name,
invokeAttr | (BindingFlags)(UseEmit ? TestingBindingFlags.InvokeWithEmit : TestingBindingFlags.InvokeWithInterpreter),
binder,
target,
args,
modifiers,
culture,
namedParameters);
}

public object? GetValue(
PropertyInfo property,
object? obj)
{
return property.GetValue(
obj,
(BindingFlags)(UseEmit ? TestingBindingFlags.InvokeWithEmit : TestingBindingFlags.InvokeWithInterpreter),
binder : null,
index : null,
culture : null);
}

public object? GetValue(
PropertyInfo property,
object? obj,
object?[]? index)
{
return property.GetValue(
obj,
(BindingFlags)(UseEmit ? TestingBindingFlags.InvokeWithEmit : TestingBindingFlags.InvokeWithInterpreter),
binder: null,
index: index,
culture: null);
}

public object? GetValue(
PropertyInfo property,
object? obj,
BindingFlags invokeAttr,
Binder? binder,
object?[]? index,
CultureInfo? culture)
{
return property.GetValue(
obj,
invokeAttr | (BindingFlags)(UseEmit ? TestingBindingFlags.InvokeWithEmit : TestingBindingFlags.InvokeWithInterpreter),
binder,
index,
culture);
}

public void SetValue(
PropertyInfo property,
object? obj,
object? value)
{
property.SetValue(
obj,
value,
(BindingFlags)(UseEmit ? TestingBindingFlags.InvokeWithEmit : TestingBindingFlags.InvokeWithInterpreter),
binder: null,
index: null,
culture: null);
}

public void SetValue(
PropertyInfo property,
object? obj,
object? value,
object?[]? index)
{
property.SetValue(
obj,
value,
(BindingFlags)(UseEmit ? TestingBindingFlags.InvokeWithEmit : TestingBindingFlags.InvokeWithInterpreter),
binder: null,
index: index,
culture: null);
}

public void SetValue(
PropertyInfo property,
object? obj,
object? value,
BindingFlags invokeAttr,
Binder? binder,
object?[]? index,
CultureInfo? culture)
{
property.SetValue(
obj,
value,
invokeAttr | (BindingFlags)(UseEmit ? TestingBindingFlags.InvokeWithEmit : TestingBindingFlags.InvokeWithInterpreter),
binder,
index,
culture);
}

/// <summary>
/// Internal enums for testing only. These are subject to change depending on test strategy.
/// If changed, also change BindingFlags.cs.
/// </summary>
[Flags]
internal enum TestingBindingFlags
{
/// <summary>
/// Use IL Emit (if available) for Invoke()
/// </summary>
InvokeWithEmit = 0x20000000,

/// <summary>
/// Use the native interpreter for Invoke()
/// </summary>
InvokeWithInterpreter = 0x40000000
}

private class ExceptionThrower
{
public ExceptionThrower() => throw new Exception("LocateMe");
public static void ThrowForStackTrace() => throw new Exception("LocateMe");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<Compile Include="System\PlatformDetection.cs" />
<Compile Include="System\PlatformDetection.Unix.cs" />
<Compile Include="System\PlatformDetection.Windows.cs" />
<Compile Include="System\Reflection\InvokeStrategy.cs" />
<Compile Include="System\WindowsIdentityFixture.cs" />

<Compile Include="$(CommonPath)System\Text\ValueStringBuilder.cs"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,27 @@ public enum BindingFlags
// These are a couple of misc attributes used
IgnoreReturn = 0x01000000, // This is used in COM Interop
DoNotWrapExceptions = 0x02000000, // Disables wrapping exceptions in TargetInvocationException

// Softly reserved for test reasons; see TestingBindingFlags.
// InvokeWithEmit = 0x20000000,
// InvokeWithInterpreter = 0x40000000
}

/// <summary>
/// Internal enums for testing only. These are subject to change depending on test strategy.
/// If changed, also change InvokeStrategy.cs.
/// </summary>
[Flags]
internal enum TestingBindingFlags
{
/// <summary>
/// Use IL Emit (if available) for Invoke()
/// </summary>
InvokeWithEmit = 0x20000000,

/// <summary>
/// Use the native interpreter for Invoke()
/// </summary>
InvokeWithInterpreter = 0x40000000
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ public ConstructorInvoker(RuntimeConstructorInfo constructorInfo)
{
if (!_strategyDetermined)
{
if (!_invoked)
if (!_invoked
#if !RELEASE
// For testing reasons, determine the strategy on the first invoke.
&& (invokeAttr & (BindingFlags)(TestingBindingFlags.InvokeWithEmit | TestingBindingFlags.InvokeWithInterpreter)) == 0
#endif
)
{
// The first time, ignoring race conditions, use the slow path.
_invoked = true;
Expand All @@ -72,7 +77,11 @@ public ConstructorInvoker(RuntimeConstructorInfo constructorInfo)
{
// For the rarely used scenario of calling the constructor directly through MethodBase.Invoke()
// with a non-null 'obj', we use the slow path to avoid having two emit-based delegates.
if (_invokeFunc != null && obj == null)
if (_invokeFunc != null
#if !RELEASE
&& obj == null && (invokeAttr & (BindingFlags)TestingBindingFlags.InvokeWithInterpreter) == 0
#endif
)
{
ret = _invokeFunc(target: null, args);
}
Expand All @@ -86,7 +95,11 @@ public ConstructorInvoker(RuntimeConstructorInfo constructorInfo)
throw new TargetInvocationException(e);
}
}
else if (_invokeFunc != null && obj == null)
else if (_invokeFunc != null && obj == null
#if !RELEASE
&& (invokeAttr & (BindingFlags)TestingBindingFlags.InvokeWithInterpreter) == 0
#endif
)
{
ret = _invokeFunc(target: null, args);
}
Expand Down
Loading