Skip to content

Commit 6b67caa

Browse files
authored
Fix calling existing ctor with MethodInvoker; share tests with invokers (#90796)
1 parent 096b249 commit 6b67caa

12 files changed

+574
-451
lines changed

src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ public object Invoke(object? arg1, object? arg2, object? arg3, object? arg4)
155155

156156
private object InvokeImpl(object? arg1, object? arg2, object? arg3, object? arg4)
157157
{
158-
if ((_invocationFlags & (InvocationFlags.NoInvoke | InvocationFlags.ContainsStackPointers)) != 0)
158+
if ((_invocationFlags & (InvocationFlags.NoInvoke | InvocationFlags.ContainsStackPointers | InvocationFlags.NoConstructorInvoke)) != 0)
159159
{
160160
_method.ThrowNoInvokeException();
161161
}

src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,12 @@ public static MethodInvoker Create(MethodBase method)
6666
{
6767
// This is useful for calling a constructor on an already-initialized object
6868
// such as created from RuntimeHelpers.GetUninitializedObject(Type).
69-
return new MethodInvoker(rci);
69+
MethodInvoker invoker = new MethodInvoker(rci);
70+
71+
// Use the interpreted version to avoid having to generate a new method that doesn't allocate.
72+
invoker._strategy = GetStrategyForUsingInterpreted();
73+
74+
return invoker;
7075
}
7176

7277
throw new ArgumentException(SR.Argument_MustBeRuntimeMethod, nameof(method));
@@ -181,7 +186,7 @@ private MethodInvoker(MethodBase method, RuntimeType[] argumentTypes)
181186

182187
private object? InvokeImpl(object? obj, object? arg1, object? arg2, object? arg3, object? arg4)
183188
{
184-
if ((_invocationFlags & (InvocationFlags.NoInvoke | InvocationFlags.ContainsStackPointers)) != 0)
189+
if ((_invocationFlags & (InvocationFlags.NoInvoke | InvocationFlags.ContainsStackPointers | InvocationFlags.NoConstructorInvoke)) != 0)
185190
{
186191
ThrowForBadInvocationFlags();
187192
}

src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvokerCommon.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,14 @@ internal static void Initialize(
1818
{
1919
if (LocalAppContextSwitches.ForceInterpretedInvoke && !LocalAppContextSwitches.ForceEmitInvoke)
2020
{
21-
// Always use the native invoke; useful for testing.
22-
strategy = InvokerStrategy.StrategyDetermined_Obj4Args | InvokerStrategy.StrategyDetermined_ObjSpanArgs | InvokerStrategy.StrategyDetermined_RefArgs;
21+
// Always use the native interpreted invoke.
22+
// Useful for testing, to avoid startup overhead of emit, or for calling a ctor on already initialized object.
23+
strategy = GetStrategyForUsingInterpreted();
2324
}
2425
else if (LocalAppContextSwitches.ForceEmitInvoke && !LocalAppContextSwitches.ForceInterpretedInvoke)
2526
{
2627
// Always use emit invoke (if IsDynamicCodeSupported == true); useful for testing.
27-
strategy = InvokerStrategy.HasBeenInvoked_Obj4Args | InvokerStrategy.HasBeenInvoked_ObjSpanArgs | InvokerStrategy.HasBeenInvoked_RefArgs;
28+
strategy = GetStrategyForUsingEmit();
2829
}
2930
else
3031
{
@@ -69,6 +70,18 @@ internal static void Initialize(
6970
}
7071
}
7172

73+
internal static InvokerStrategy GetStrategyForUsingInterpreted()
74+
{
75+
// This causes the default strategy, which is interpreted, to always be used.
76+
return InvokerStrategy.StrategyDetermined_Obj4Args | InvokerStrategy.StrategyDetermined_ObjSpanArgs | InvokerStrategy.StrategyDetermined_RefArgs;
77+
}
78+
79+
private static InvokerStrategy GetStrategyForUsingEmit()
80+
{
81+
// This causes the emit strategy, if supported, to be used on the first call as well as subsequent calls.
82+
return InvokerStrategy.HasBeenInvoked_Obj4Args | InvokerStrategy.HasBeenInvoked_ObjSpanArgs | InvokerStrategy.HasBeenInvoked_RefArgs;
83+
}
84+
7285
/// <summary>
7386
/// Confirm member invocation has an instance and is of the correct type
7487
/// </summary>
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Linq;
5+
using Xunit;
6+
7+
namespace System.Reflection.Tests
8+
{
9+
/// <summary>
10+
/// These tests are shared with ConstructorInfo.Invoke and ConstructorInvoker.Invoke by using
11+
/// the abstract Invoke(...) methods below.
12+
/// </summary>
13+
public abstract class ConstructorCommonTests
14+
{
15+
public abstract object Invoke(ConstructorInfo constructorInfo, object?[]? parameters);
16+
17+
protected abstract bool IsExceptionWrapped { get; }
18+
19+
/// <summary>
20+
/// Invoke constructor on an existing instance. Should return null.
21+
/// </summary>
22+
public abstract object? Invoke(ConstructorInfo constructorInfo, object obj, object?[]? parameters);
23+
24+
public static ConstructorInfo[] GetConstructors(Type type)
25+
{
26+
return type.GetTypeInfo().DeclaredConstructors.ToArray();
27+
}
28+
29+
[Fact]
30+
public void SimpleInvoke()
31+
{
32+
ConstructorInfo[] constructors = GetConstructors(typeof(ClassWith3Constructors));
33+
Assert.Equal(3, constructors.Length);
34+
ClassWith3Constructors obj = (ClassWith3Constructors)Invoke(constructors[0], null);
35+
Assert.NotNull(obj);
36+
}
37+
38+
[Fact]
39+
[ActiveIssue("https://github.com/mono/mono/issues/15024", TestRuntimes.Mono)]
40+
public void Invoke_StaticConstructor_ThrowsMemberAccessException()
41+
{
42+
ConstructorInfo[] constructors = GetConstructors(typeof(ClassWithStaticConstructor));
43+
Assert.Equal(1, constructors.Length);
44+
Assert.Throws<MemberAccessException>(() => Invoke(constructors[0], new object[0]));
45+
}
46+
47+
[Fact]
48+
public void Invoke_OneDimensionalArray()
49+
{
50+
ConstructorInfo[] constructors = GetConstructors(typeof(object[]));
51+
int[] arraylength = { 1, 2, 99, 65535 };
52+
53+
// Try to invoke Array ctors with different lengths
54+
foreach (int length in arraylength)
55+
{
56+
// Create big Array with elements
57+
object[] arr = (object[])Invoke(constructors[0], new object[] { length });
58+
Assert.Equal(arr.Length, length);
59+
}
60+
}
61+
62+
[Fact]
63+
public void Invoke_OneDimensionalArray_NegativeLengths_ThrowsOverflowException()
64+
{
65+
ConstructorInfo[] constructors = GetConstructors(typeof(object[]));
66+
int[] arraylength = new int[] { -1, -2, -99 };
67+
// Try to invoke Array ctors with different lengths
68+
foreach (int length in arraylength)
69+
{
70+
// Create big Array with elements
71+
if (IsExceptionWrapped)
72+
{
73+
Exception ex = Assert.Throws<TargetInvocationException>(() => Invoke(constructors[0], new object[] { length }));
74+
Assert.IsType<OverflowException>(ex.InnerException);
75+
}
76+
else
77+
{
78+
Assert.Throws<OverflowException>(() => Invoke(constructors[0], new object[] { length }));
79+
}
80+
}
81+
}
82+
83+
[Fact]
84+
public void Invoke_OneParameter()
85+
{
86+
ConstructorInfo[] constructors = GetConstructors(typeof(ClassWith3Constructors));
87+
ClassWith3Constructors obj = (ClassWith3Constructors)Invoke(constructors[1], new object[] { 100 });
88+
Assert.Equal(100, obj.intValue);
89+
}
90+
91+
[Fact]
92+
public void Invoke_TwoParameters()
93+
{
94+
ConstructorInfo[] constructors = GetConstructors(typeof(ClassWith3Constructors));
95+
ClassWith3Constructors obj = (ClassWith3Constructors)Invoke(constructors[2], new object[] { 101, "hello" });
96+
Assert.Equal(101, obj.intValue);
97+
Assert.Equal("hello", obj.stringValue);
98+
}
99+
100+
[Fact]
101+
public void Invoke_NoParameters_ThowsTargetParameterCountException()
102+
{
103+
ConstructorInfo[] constructors = GetConstructors(typeof(ClassWith3Constructors));
104+
Assert.Throws<TargetParameterCountException>(() => Invoke(constructors[2], new object[0]));
105+
}
106+
107+
[Fact]
108+
public void Invoke_ParameterMismatch_ThrowsTargetParameterCountException()
109+
{
110+
ConstructorInfo[] constructors = GetConstructors(typeof(ClassWith3Constructors));
111+
Assert.Throws<TargetParameterCountException>(() => (ClassWith3Constructors)Invoke(constructors[2], new object[] { 121 }));
112+
}
113+
114+
[Fact]
115+
public void Invoke_ParameterWrongType_ThrowsArgumentException()
116+
{
117+
ConstructorInfo[] constructors = GetConstructors(typeof(ClassWith3Constructors));
118+
AssertExtensions.Throws<ArgumentException>(null, () => (ClassWith3Constructors)Invoke(constructors[1], new object[] { "hello" }));
119+
}
120+
121+
[Fact]
122+
public void Invoke_ExistingInstance()
123+
{
124+
// Should not produce a second object.
125+
ConstructorInfo[] constructors = GetConstructors(typeof(ClassWith3Constructors));
126+
ClassWith3Constructors obj1 = new ClassWith3Constructors(100, "hello");
127+
ClassWith3Constructors obj2 = (ClassWith3Constructors)Invoke(constructors[2], obj1, new object[] { 999, "initialized" });
128+
Assert.Null(obj2);
129+
Assert.Equal(999, obj1.intValue);
130+
Assert.Equal("initialized", obj1.stringValue);
131+
}
132+
133+
[Fact]
134+
public void Invoke_NullForObj()
135+
{
136+
ConstructorInfo[] constructors = GetConstructors(typeof(ClassWith3Constructors));
137+
Assert.Throws<TargetException>(() => Invoke(constructors[2], obj: null, new object[] { 999, "initialized" }));
138+
}
139+
140+
[Fact]
141+
[ActiveIssue("https://github.com/mono/mono/issues/15026", TestRuntimes.Mono)]
142+
public void Invoke_AbstractClass_ThrowsMemberAccessException()
143+
{
144+
ConstructorInfo[] constructors = GetConstructors(typeof(ConstructorInfoAbstractBase));
145+
Assert.Throws<MemberAccessException>(() => (ConstructorInfoAbstractBase)Invoke(constructors[0], new object[0]));
146+
}
147+
148+
[Fact]
149+
public void Invoke_SubClass()
150+
{
151+
ConstructorInfo[] constructors = GetConstructors(typeof(ConstructorInfoDerived));
152+
ConstructorInfoDerived obj = null;
153+
obj = (ConstructorInfoDerived)Invoke(constructors[0], new object[] { });
154+
Assert.NotNull(obj);
155+
}
156+
157+
[Fact]
158+
public void Invoke_Struct()
159+
{
160+
ConstructorInfo[] constructors = GetConstructors(typeof(StructWith1Constructor));
161+
StructWith1Constructor obj;
162+
obj = (StructWith1Constructor)Invoke(constructors[0], new object[] { 1, 2 });
163+
Assert.Equal(1, obj.x);
164+
Assert.Equal(2, obj.y);
165+
}
166+
}
167+
}

0 commit comments

Comments
 (0)