From ee51f98f04a0134e1a09f9b4d7b246b1287bba0c Mon Sep 17 00:00:00 2001 From: Steve Pfister Date: Wed, 19 Jun 2024 15:40:28 -0400 Subject: [PATCH 1/7] [release/8.0-staging] Add non-public SetEntryAssembly() API to System.Reflection Implements the API proposal detailed and approved in issue #101616. This new API will allow developers to change the entry assembly of their .NET apps on the fly, should they require it. One important scenario is app launchers. With the usage of this API, then functions like `GetEntryAssembly()` will return the right value, and thus we will be able to ensure the information is consistent and correct. --- ...guration.ConfigurationManager.Tests.csproj | 2 +- .../System/Configuration/CustomHostTests.cs | 4 +-- .../DefaultTraceListenerClassTests.cs | 4 +-- .../src/System/Reflection/Assembly.cs | 30 ++++++++++++++-- .../System.Reflection/tests/AssemblyTests.cs | 34 +++++++++++++++++++ .../tests/System.Reflection.Tests.csproj | 1 + 6 files changed, 68 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Configuration.ConfigurationManager/tests/System.Configuration.ConfigurationManager.Tests.csproj b/src/libraries/System.Configuration.ConfigurationManager/tests/System.Configuration.ConfigurationManager.Tests.csproj index e7881df77eb922..d4f8b92692a2db 100644 --- a/src/libraries/System.Configuration.ConfigurationManager/tests/System.Configuration.ConfigurationManager.Tests.csproj +++ b/src/libraries/System.Configuration.ConfigurationManager/tests/System.Configuration.ConfigurationManager.Tests.csproj @@ -59,7 +59,7 @@ - + diff --git a/src/libraries/System.Configuration.ConfigurationManager/tests/System/Configuration/CustomHostTests.cs b/src/libraries/System.Configuration.ConfigurationManager/tests/System/Configuration/CustomHostTests.cs index e8f69c198cf82d..7d103225cd5e51 100644 --- a/src/libraries/System.Configuration.ConfigurationManager/tests/System/Configuration/CustomHostTests.cs +++ b/src/libraries/System.Configuration.ConfigurationManager/tests/System/Configuration/CustomHostTests.cs @@ -36,8 +36,8 @@ public void FilePathIsPopulatedCorrectly() private static void MakeAssemblyGetEntryAssemblyReturnNull() { typeof(Assembly) - .GetField("s_forceNullEntryPoint", BindingFlags.NonPublic | BindingFlags.Static) - .SetValue(null, true); + .GetMethod("SetEntryAssembly", BindingFlags.Public | BindingFlags.Static) + .Invoke(null, new object[]{ null }); Assert.Null(Assembly.GetEntryAssembly()); } diff --git a/src/libraries/System.Diagnostics.TraceSource/tests/System.Diagnostics.TraceSource.Tests/DefaultTraceListenerClassTests.cs b/src/libraries/System.Diagnostics.TraceSource/tests/System.Diagnostics.TraceSource.Tests/DefaultTraceListenerClassTests.cs index efbb26a53a7a9e..d449a90e6dead8 100644 --- a/src/libraries/System.Diagnostics.TraceSource/tests/System.Diagnostics.TraceSource.Tests/DefaultTraceListenerClassTests.cs +++ b/src/libraries/System.Diagnostics.TraceSource/tests/System.Diagnostics.TraceSource.Tests/DefaultTraceListenerClassTests.cs @@ -174,8 +174,8 @@ public void EntryAssemblyName_Null_NotIncludedInTrace() private static void MakeAssemblyGetEntryAssemblyReturnNull() { typeof(Assembly) - .GetField("s_forceNullEntryPoint", BindingFlags.NonPublic | BindingFlags.Static) - .SetValue(null, true); + .GetMethod("SetEntryAssembly", BindingFlags.Public | BindingFlags.Static) + .Invoke(null, new object[]{ null }); Assert.Null(Assembly.GetEntryAssembly()); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/Assembly.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/Assembly.cs index 15e753aaee3792..f3716f4e345161 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/Assembly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/Assembly.cs @@ -217,13 +217,39 @@ public override string ToString() return type.Module?.Assembly; } + private static object? s_overriddenEntryAssembly; + + /// + /// Sets the application's entry assembly to the provided assembly object. + /// + /// + /// Assembly object that represents the application's new entry assembly. + /// + /// + /// The assembly passed to this function has to be a runtime defined Assembly + /// type object. Otherwise, an exception will be thrown. + /// + public static void SetEntryAssembly(Assembly? assembly) + { + if (assembly is null) + { + s_overriddenEntryAssembly = string.Empty; + return; + } + + if (assembly is not RuntimeAssembly) + throw new ArgumentException(SR.Argument_MustBeRuntimeAssembly); + + s_overriddenEntryAssembly = assembly; + } + // internal test hook private static bool s_forceNullEntryPoint; public static Assembly? GetEntryAssembly() { - if (s_forceNullEntryPoint) - return null; + if (s_overriddenEntryAssembly is not null) + return s_overriddenEntryAssembly as Assembly; return GetEntryAssemblyInternal(); } diff --git a/src/libraries/System.Reflection/tests/AssemblyTests.cs b/src/libraries/System.Reflection/tests/AssemblyTests.cs index 600a4733b40c9c..3472d50b955abf 100644 --- a/src/libraries/System.Reflection/tests/AssemblyTests.cs +++ b/src/libraries/System.Reflection/tests/AssemblyTests.cs @@ -7,10 +7,12 @@ using System.Globalization; using System.IO; using System.Linq; +using System.Reflection.Emit; using System.Reflection.Tests; using System.Runtime.CompilerServices; using System.Security; using System.Text; +using Microsoft.DotNet.RemoteExecutor; using Xunit; [assembly: @@ -181,6 +183,38 @@ public void GetEntryAssembly() Assert.True(correct, $"Unexpected assembly name {assembly}"); } + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void SetEntryAssembly() + { + Assert.NotNull(Assembly.GetEntryAssembly()); + + RemoteExecutor.Invoke(() => + { + SetEntryAssembly(null); + Assert.Null(Assembly.GetEntryAssembly()); + + Assembly testAssembly = typeof(AssemblyTests).Assembly; + + SetEntryAssembly(testAssembly); + Assert.Equal(Assembly.GetEntryAssembly(), testAssembly); + + var invalidAssembly = new PersistedAssemblyBuilder( + new AssemblyName("NotaRuntimeAssemblyTest"), + typeof(object).Assembly + ); + Assert.Throws( + () => Assembly.SetEntryAssembly(invalidAssembly) + ); + }).Dispose(); + } + + private static SetEntryAssembly(Assembly assembly) + { + typeof(Assembly) + .GetMethod("SetEntryAssembly", BindingFlags.Public | BindingFlags.Static) + .Invoke(null, new object[]{ assembly }); + } + [Fact] public void GetFile() { diff --git a/src/libraries/System.Reflection/tests/System.Reflection.Tests.csproj b/src/libraries/System.Reflection/tests/System.Reflection.Tests.csproj index 85de60a759bf85..a5cf23579908ec 100644 --- a/src/libraries/System.Reflection/tests/System.Reflection.Tests.csproj +++ b/src/libraries/System.Reflection/tests/System.Reflection.Tests.csproj @@ -2,6 +2,7 @@ $(NetCoreAppCurrent) true + true true From 4050eac28a189d06d0746576ceba2689326658df Mon Sep 17 00:00:00 2001 From: Steve Pfister Date: Wed, 19 Jun 2024 17:56:45 -0400 Subject: [PATCH 2/7] Remove unused field --- .../System.Private.CoreLib/src/System/Reflection/Assembly.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/Assembly.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/Assembly.cs index f3716f4e345161..ec817ea84a6afe 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/Assembly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/Assembly.cs @@ -243,9 +243,6 @@ public static void SetEntryAssembly(Assembly? assembly) s_overriddenEntryAssembly = assembly; } - // internal test hook - private static bool s_forceNullEntryPoint; - public static Assembly? GetEntryAssembly() { if (s_overriddenEntryAssembly is not null) From 499804217a20936ec9251c5d4fa4daefd3bdf03e Mon Sep 17 00:00:00 2001 From: Steve Pfister Date: Wed, 19 Jun 2024 19:09:50 -0400 Subject: [PATCH 3/7] Add suppressions --- .../System.Private.CoreLib/CompatibilitySuppressions.xml | 7 +++++++ .../src/CompatibilitySuppressions.xml | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/coreclr/System.Private.CoreLib/CompatibilitySuppressions.xml b/src/coreclr/System.Private.CoreLib/CompatibilitySuppressions.xml index 276be3a543e343..ed348d066b9961 100644 --- a/src/coreclr/System.Private.CoreLib/CompatibilitySuppressions.xml +++ b/src/coreclr/System.Private.CoreLib/CompatibilitySuppressions.xml @@ -1,5 +1,12 @@  + + + CP0002 + M:System.Reflection.Assembly.SetEntryAssembly(System.Reflection.Assembly) + ref/net8.0/System.Private.CoreLib.dll + lib/net8.0/System.Private.CoreLib.dll + CP0015 M:System.Diagnostics.Tracing.EventSource.Write``1(System.String,``0):[T:System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute] diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml index 229085a10afaa0..78543b6e04a4e0 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml @@ -1,4 +1,5 @@  + CP0001 @@ -960,6 +961,12 @@ CP0002 M:System.TypedReference.get_IsNull + + CP0002 + M:System.Reflection.Assembly.SetEntryAssembly(System.Reflection.Assembly) + ref/net8.0/System.Private.CoreLib.dll + lib/net8.0/System.Private.CoreLib.dll + CP0014 M:System.Runtime.CompilerServices.RuntimeHelpers.GetObjectValue(System.Object)->object?:[T:System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute] From 265bb10a0613c8f870a74571c9f6dd8abbc878ba Mon Sep 17 00:00:00 2001 From: Steve Pfister Date: Wed, 19 Jun 2024 20:20:30 -0400 Subject: [PATCH 4/7] Test tweak --- src/libraries/System.Reflection/tests/AssemblyTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Reflection/tests/AssemblyTests.cs b/src/libraries/System.Reflection/tests/AssemblyTests.cs index 3472d50b955abf..f1907cae523b2e 100644 --- a/src/libraries/System.Reflection/tests/AssemblyTests.cs +++ b/src/libraries/System.Reflection/tests/AssemblyTests.cs @@ -208,7 +208,7 @@ public void SetEntryAssembly() }).Dispose(); } - private static SetEntryAssembly(Assembly assembly) + private static void SetEntryAssembly(Assembly assembly) { typeof(Assembly) .GetMethod("SetEntryAssembly", BindingFlags.Public | BindingFlags.Static) From 764ed465f57dd3fce1adbba2f658b68a9801dcdc Mon Sep 17 00:00:00 2001 From: Steve Pfister Date: Wed, 19 Jun 2024 21:35:05 -0400 Subject: [PATCH 5/7] Remove a .net 9 specific part of a test --- src/libraries/System.Reflection/tests/AssemblyTests.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/libraries/System.Reflection/tests/AssemblyTests.cs b/src/libraries/System.Reflection/tests/AssemblyTests.cs index f1907cae523b2e..107156d351905a 100644 --- a/src/libraries/System.Reflection/tests/AssemblyTests.cs +++ b/src/libraries/System.Reflection/tests/AssemblyTests.cs @@ -197,14 +197,6 @@ public void SetEntryAssembly() SetEntryAssembly(testAssembly); Assert.Equal(Assembly.GetEntryAssembly(), testAssembly); - - var invalidAssembly = new PersistedAssemblyBuilder( - new AssemblyName("NotaRuntimeAssemblyTest"), - typeof(object).Assembly - ); - Assert.Throws( - () => Assembly.SetEntryAssembly(invalidAssembly) - ); }).Dispose(); } From 024e34f771f7edfe01aac6c2b678ac603fa3ae5e Mon Sep 17 00:00:00 2001 From: Steve Pfister Date: Thu, 20 Jun 2024 09:08:11 -0400 Subject: [PATCH 6/7] Rename test to not clash --- src/libraries/System.Reflection/tests/AssemblyTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Reflection/tests/AssemblyTests.cs b/src/libraries/System.Reflection/tests/AssemblyTests.cs index 107156d351905a..be2dd954f393bc 100644 --- a/src/libraries/System.Reflection/tests/AssemblyTests.cs +++ b/src/libraries/System.Reflection/tests/AssemblyTests.cs @@ -184,7 +184,7 @@ public void GetEntryAssembly() } [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] - public void SetEntryAssembly() + public void TestSetEntryAssembly() { Assert.NotNull(Assembly.GetEntryAssembly()); From 7cf3beed606ae115784b288233af89609882eb46 Mon Sep 17 00:00:00 2001 From: Steve Pfister Date: Thu, 20 Jun 2024 12:04:34 -0400 Subject: [PATCH 7/7] Add api suppression for mono --- .../System.Private.CoreLib/CompatibilitySuppressions.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/mono/System.Private.CoreLib/CompatibilitySuppressions.xml b/src/mono/System.Private.CoreLib/CompatibilitySuppressions.xml index ed9aefadda0057..cc3ab5d098e221 100644 --- a/src/mono/System.Private.CoreLib/CompatibilitySuppressions.xml +++ b/src/mono/System.Private.CoreLib/CompatibilitySuppressions.xml @@ -1,5 +1,12 @@  + + + CP0002 + M:System.Reflection.Assembly.SetEntryAssembly(System.Reflection.Assembly) + ref/net8.0/System.Private.CoreLib.dll + lib/net8.0/System.Private.CoreLib.dll + CP0014 M:System.Runtime.CompilerServices.RuntimeHelpers.GetObjectValue(System.Object)->object?:[T:System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute]