Skip to content

Commit 362a95d

Browse files
ivdiazsajkotas
andauthored
Add SetEntryAssembly() API to System.Reflection (#102271)
* Trying out JanK's approach to override the entry assembly... * Fixed what was missing for this reflection scenario to work correctly. * Reverted the compatibility suppressions added by the build script. * Forgot to revert also the nativeaot part of the suppressions. * Addressed PR comments: Updated the tests to use the new "s_overriddenEntryAssembly", added an API doc to SetEntryAssembly(), added validation for it to be a Runtime Assembly, and changed the type to allow a null entry assembly. * Added tests and addressed more comments on the PR. * Added exception test case for SetEntryAssembly, and wrapped all its test cases in a RemoteExecutor.Invoke() call, in order to avoid potentially interferring with the GetEntryAssembly tests. * Update src/libraries/System.Private.CoreLib/src/System/Reflection/Assembly.cs Co-authored-by: Jan Kotas <jkotas@microsoft.com> * Refactored further the tests that force a null entry assembly, and fixed the issue with the remote executor. * Apply Jan's suggestions Co-authored-by: Jan Kotas <jkotas@microsoft.com> * Fixed a sneaky trailing whitespace that was messing up with the code analyzers. * Changed ConditionalTheory to ConditionalFact in the tests because we're not passing parameters to it. * Disabled building the CustomHostTests test file when .NET Framework --------- Co-authored-by: Jan Kotas <jkotas@microsoft.com>
1 parent 153a94b commit 362a95d

File tree

7 files changed

+62
-31
lines changed

7 files changed

+62
-31
lines changed

src/libraries/System.Configuration.ConfigurationManager/tests/System.Configuration.ConfigurationManager.Tests.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
<Compile Include="System\Configuration\ConfigurationElementTests.cs" />
6060
<Compile Include="System\Configuration\ConfigurationPropertyAttributeTests.cs" />
6161
<Compile Include="System\Configuration\ConfigurationPathTests.cs" />
62-
<Compile Include="System\Configuration\CustomHostTests.cs" />
62+
<Compile Include="System\Configuration\CustomHostTests.cs" Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'" />
6363
<Compile Include="System\Configuration\ConfigurationPropertyTests.cs" />
6464
<Compile Include="System\Configuration\ConfigurationTests.cs" />
6565
<Compile Include="System\Configuration\BasicCustomSectionTests.cs" />

src/libraries/System.Configuration.ConfigurationManager/tests/System/Configuration/CustomHostTests.cs

+2-13
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ public void FilePathIsPopulatedCorrectly()
1919
{
2020
RemoteExecutor.Invoke(() =>
2121
{
22-
MakeAssemblyGetEntryAssemblyReturnNull();
22+
Assembly.SetEntryAssembly(null);
23+
Assert.Null(Assembly.GetEntryAssembly());
2324

2425
string expectedFilePathEnding = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
2526
"dotnet.exe.config" :
@@ -29,17 +30,5 @@ public void FilePathIsPopulatedCorrectly()
2930
Assert.EndsWith(expectedFilePathEnding, config.FilePath);
3031
}).Dispose();
3132
}
32-
33-
/// <summary>
34-
/// Makes Assembly.GetEntryAssembly() return null using private reflection.
35-
/// </summary>
36-
private static void MakeAssemblyGetEntryAssemblyReturnNull()
37-
{
38-
typeof(Assembly)
39-
.GetField("s_forceNullEntryPoint", BindingFlags.NonPublic | BindingFlags.Static)
40-
.SetValue(null, true);
41-
42-
Assert.Null(Assembly.GetEntryAssembly());
43-
}
4433
}
4534
}

src/libraries/System.Diagnostics.TraceSource/tests/System.Diagnostics.TraceSource.Tests/DefaultTraceListenerClassTests.cs

+2-13
Original file line numberDiff line numberDiff line change
@@ -159,25 +159,14 @@ public void EntryAssemblyName_Null_NotIncludedInTrace()
159159
{
160160
RemoteExecutor.Invoke(() =>
161161
{
162-
MakeAssemblyGetEntryAssemblyReturnNull();
162+
Assembly.SetEntryAssembly(null);
163+
Assert.Null(Assembly.GetEntryAssembly());
163164

164165
var listener = new TestDefaultTraceListener();
165166
Trace.Listeners.Add(listener);
166167
Trace.TraceError("hello world");
167168
Assert.Equal("Error: 0 : hello world", listener.Output.Trim());
168169
}).Dispose();
169170
}
170-
171-
/// <summary>
172-
/// Makes Assembly.GetEntryAssembly() return null using private reflection.
173-
/// </summary>
174-
private static void MakeAssemblyGetEntryAssemblyReturnNull()
175-
{
176-
typeof(Assembly)
177-
.GetField("s_forceNullEntryPoint", BindingFlags.NonPublic | BindingFlags.Static)
178-
.SetValue(null, true);
179-
180-
Assert.Null(Assembly.GetEntryAssembly());
181-
}
182171
}
183172
}

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

+27-4
Original file line numberDiff line numberDiff line change
@@ -217,13 +217,36 @@ public override string ToString()
217217
return type.Module?.Assembly;
218218
}
219219

220-
// internal test hook
221-
private static bool s_forceNullEntryPoint;
220+
private static object? s_overriddenEntryAssembly;
221+
222+
/// <summary>
223+
/// Sets the application's entry assembly to the provided assembly object.
224+
/// </summary>
225+
/// <param name="assembly">
226+
/// Assembly object that represents the application's new entry assembly.
227+
/// </param>
228+
/// <remarks>
229+
/// The assembly passed to this function has to be a runtime defined Assembly
230+
/// type object. Otherwise, an exception will be thrown.
231+
/// </remarks>
232+
public static void SetEntryAssembly(Assembly? assembly)
233+
{
234+
if (assembly is null)
235+
{
236+
s_overriddenEntryAssembly = string.Empty;
237+
return;
238+
}
239+
240+
if (assembly is not RuntimeAssembly)
241+
throw new ArgumentException(SR.Argument_MustBeRuntimeAssembly);
242+
243+
s_overriddenEntryAssembly = assembly;
244+
}
222245

223246
public static Assembly? GetEntryAssembly()
224247
{
225-
if (s_forceNullEntryPoint)
226-
return null;
248+
if (s_overriddenEntryAssembly is not null)
249+
return s_overriddenEntryAssembly as Assembly;
227250

228251
return GetEntryAssemblyInternal();
229252
}

src/libraries/System.Runtime/ref/System.Runtime.cs

+1
Original file line numberDiff line numberDiff line change
@@ -11176,6 +11176,7 @@ public virtual void GetObjectData(System.Runtime.Serialization.SerializationInfo
1117611176
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("Types and members the loaded assembly depends on might be removed")]
1117711177
[System.ObsoleteAttribute("ReflectionOnly loading is not supported and throws PlatformNotSupportedException.", DiagnosticId="SYSLIB0018", UrlFormat="https://aka.ms/dotnet-warnings/{0}")]
1117811178
public static System.Reflection.Assembly ReflectionOnlyLoadFrom(string assemblyFile) { throw null; }
11179+
public static void SetEntryAssembly(System.Reflection.Assembly? assembly) { throw null; }
1117911180
public override string ToString() { throw null; }
1118011181
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("Types and members the loaded assembly depends on might be removed")]
1118111182
public static System.Reflection.Assembly UnsafeLoadFrom(string assemblyFile) { throw null; }

src/libraries/System.Runtime/tests/System.Reflection.Tests/AssemblyTests.cs

+28
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77
using System.Globalization;
88
using System.IO;
99
using System.Linq;
10+
using System.Reflection.Emit;
1011
using System.Reflection.Tests;
1112
using System.Runtime.CompilerServices;
1213
using System.Security;
1314
using System.Text;
15+
using Microsoft.DotNet.RemoteExecutor;
1416
using Xunit;
1517

1618
[assembly:
@@ -181,6 +183,32 @@ public void GetEntryAssembly()
181183
Assert.True(correct, $"Unexpected assembly name {assembly}");
182184
}
183185

186+
[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
187+
public void SetEntryAssembly()
188+
{
189+
Assert.NotNull(Assembly.GetEntryAssembly());
190+
191+
RemoteExecutor.Invoke(() =>
192+
{
193+
Assembly.SetEntryAssembly(null);
194+
Assert.Null(Assembly.GetEntryAssembly());
195+
196+
Assembly testAssembly = typeof(AssemblyTests).Assembly;
197+
198+
Assembly.SetEntryAssembly(testAssembly);
199+
Assert.Equal(Assembly.GetEntryAssembly(), testAssembly);
200+
201+
var invalidAssembly = new PersistedAssemblyBuilder(
202+
new AssemblyName("NotaRuntimeAssemblyTest"),
203+
typeof(object).Assembly
204+
);
205+
206+
Assert.Throws<ArgumentException>(
207+
() => Assembly.SetEntryAssembly(invalidAssembly)
208+
);
209+
}).Dispose();
210+
}
211+
184212
[Fact]
185213
public void GetFile()
186214
{

src/libraries/System.Runtime/tests/System.Reflection.Tests/System.Reflection.Tests.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<PropertyGroup>
33
<TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
44
<TestRuntime>true</TestRuntime>
5+
<IncludeRemoteExecutor>true</IncludeRemoteExecutor>
56
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
67
<!-- SYSLIB0013: Uri.EscapeUriString is obsolete
78
SYSLIB0037: AssemblyName members HashAlgorithm, ProcessorArchitecture, and VersionCompatibility are obsolete. -->

0 commit comments

Comments
 (0)