diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj
index 626f0eec24b22..97671f56e5dc0 100644
--- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj
+++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj
@@ -189,6 +189,7 @@
+
diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Metadata/MetadataUpdater.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Metadata/MetadataUpdater.cs
new file mode 100644
index 0000000000000..22e6ceccd9052
--- /dev/null
+++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Metadata/MetadataUpdater.cs
@@ -0,0 +1,63 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace System.Reflection.Metadata
+{
+ public static class MetadataUpdater
+ {
+ [DllImport(RuntimeHelpers.QCall)]
+ private static extern unsafe void ApplyUpdate(QCallAssembly assembly, byte* metadataDelta, int metadataDeltaLength, byte* ilDelta, int ilDeltaLength, byte* pdbDelta, int pdbDeltaLength);
+
+ ///
+ /// Updates the specified assembly using the provided metadata, IL and PDB deltas.
+ ///
+ ///
+ /// Currently executing methods will continue to use the existing IL. New executions of modified methods will
+ /// use the new IL. Different runtimes may have different limitations on what kinds of changes are supported,
+ /// and runtimes make no guarantees as to the state of the assembly and process if the delta includes
+ /// unsupported changes.
+ ///
+ /// The assembly to update.
+ /// The metadata changes to be applied.
+ /// The IL changes to be applied.
+ /// The PDB changes to be applied.
+ /// The assembly argument is null.
+ /// The update could not be applied.
+ public static void ApplyUpdate(Assembly assembly, ReadOnlySpan metadataDelta, ReadOnlySpan ilDelta, ReadOnlySpan pdbDelta)
+ {
+ if (assembly == null)
+ {
+ throw new ArgumentNullException(nameof(assembly));
+ }
+
+ RuntimeAssembly? runtimeAssembly = assembly as RuntimeAssembly;
+ if (runtimeAssembly == null)
+ {
+ throw new ArgumentException(SR.Argument_MustBeRuntimeAssembly);
+ }
+
+ unsafe
+ {
+ RuntimeAssembly rtAsm = runtimeAssembly;
+ fixed (byte* metadataDeltaPtr = metadataDelta, ilDeltaPtr = ilDelta, pdbDeltaPtr = pdbDelta)
+ {
+ ApplyUpdate(new QCallAssembly(ref rtAsm), metadataDeltaPtr, metadataDelta.Length, ilDeltaPtr, ilDelta.Length, pdbDeltaPtr, pdbDelta.Length);
+ }
+ }
+ }
+
+ ///
+ /// Returns the metadata update capabilities.
+ ///
+ public static string GetCapabilities() => "Baseline AddMethodToExistingType AddStaticFieldToExistingType AddInstanceFieldToExistingType NewTypeDefinition";
+
+ ///
+ /// Returns true if the apply assembly update is enabled and available.
+ ///
+ public static bool IsSupported => Debugger.IsAttached || Environment.GetEnvironmentVariable("DOTNET_MODIFIABLE_ASSEMBLIES") != "" || Environment.GetEnvironmentVariable("COMPlus_ForceEnc") == "1";
+ }
+}
diff --git a/src/libraries/System.Runtime.Loader/ref/System.Runtime.Loader.cs b/src/libraries/System.Runtime.Loader/ref/System.Runtime.Loader.cs
index 3aaa591f62411..2d70d94551b13 100644
--- a/src/libraries/System.Runtime.Loader/ref/System.Runtime.Loader.cs
+++ b/src/libraries/System.Runtime.Loader/ref/System.Runtime.Loader.cs
@@ -12,6 +12,12 @@ public static partial class AssemblyExtensions
public unsafe static bool TryGetRawMetadata(this System.Reflection.Assembly assembly, out byte* blob, out int length) { throw null; }
public static void ApplyUpdate(Assembly assembly, ReadOnlySpan metadataDelta, ReadOnlySpan ilDelta, ReadOnlySpan pdbDelta) { throw null; }
}
+ public static partial class MetadataUpdater
+ {
+ public static void ApplyUpdate(Assembly assembly, ReadOnlySpan metadataDelta, ReadOnlySpan ilDelta, ReadOnlySpan pdbDelta) { throw null; }
+ public static string GetCapabilities() { throw null; }
+ public static bool IsSupported { get { throw null; } }
+ }
[System.AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple = true)]
public sealed class MetadataUpdateHandlerAttribute : System.Attribute
{
diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs
index 2a953d19eece8..b4b79202605a2 100644
--- a/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs
+++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs
@@ -83,5 +83,43 @@ void ClassWithCustomAttributes()
Assert.Equal(attrType, cattrs[0].GetType());
});
}
+
+ class NonRuntimeAssembly : Assembly
+ {
+ }
+
+ [Fact]
+ public static void ApplyUpdateInvalidParameters()
+ {
+ // Dummy delta arrays
+ var metadataDelta = new byte[20];
+ var ilDelta = new byte[20];
+
+ // Assembly can't be null
+ Assert.Throws("assembly", () =>
+ MetadataUpdater.ApplyUpdate(null, new ReadOnlySpan(metadataDelta), new ReadOnlySpan(ilDelta), ReadOnlySpan.Empty));
+
+ // Tests fail on non-runtime assemblies
+ Assert.Throws(() =>
+ MetadataUpdater.ApplyUpdate(new NonRuntimeAssembly(), new ReadOnlySpan(metadataDelta), new ReadOnlySpan(ilDelta), ReadOnlySpan.Empty));
+
+ // Tests that this assembly isn't not editable
+ Assert.Throws(() =>
+ MetadataUpdater.ApplyUpdate(typeof(AssemblyExtensions).Assembly, new ReadOnlySpan(metadataDelta), new ReadOnlySpan(ilDelta), ReadOnlySpan.Empty));
+ }
+
+ [Fact]
+ public static void GetCapabilities()
+ {
+ string result = MetadataUpdater.GetCapabilities();
+ Assert.NotNull(result);
+ }
+
+ [Fact]
+ public static void IsSupported()
+ {
+ bool result = MetadataUpdater.IsSupported;
+ Assert.False(result);
+ }
}
}
diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdateUtil.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdateUtil.cs
index ac734d35456aa..49e14a58e8d83 100644
--- a/src/libraries/System.Runtime.Loader/tests/ApplyUpdateUtil.cs
+++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdateUtil.cs
@@ -48,16 +48,10 @@ internal static bool CheckSupportedMonoConfiguration()
internal static bool HasApplyUpdateCapabilities()
{
- var ty = typeof(AssemblyExtensions);
- var mi = ty.GetMethod("GetApplyUpdateCapabilities", BindingFlags.NonPublic | BindingFlags.Static, Array.Empty());
-
- if (mi == null)
- return false;
-
- var caps = mi.Invoke(null, null);
+ string caps = MetadataUpdater.GetCapabilities();
// any non-empty string, assumed to be at least "baseline"
- return caps is string {Length: > 0};
+ return caps.Length > 0;
}
private static System.Collections.Generic.Dictionary assembly_count = new();
@@ -83,15 +77,15 @@ internal static void ApplyUpdate (System.Reflection.Assembly assm)
byte[] dil_data = System.IO.File.ReadAllBytes(dil_name);
byte[] dpdb_data = null; // TODO also use the dpdb data
- AssemblyExtensions.ApplyUpdate(assm, dmeta_data, dil_data, dpdb_data);
+ MetadataUpdater.ApplyUpdate(assm, dmeta_data, dil_data, dpdb_data);
}
internal static bool UseRemoteExecutor => !IsModifiableAssembliesSet;
internal static void AddRemoteInvokeOptions (ref RemoteInvokeOptions options)
{
- options = options ?? new RemoteInvokeOptions();
- options.StartInfo.EnvironmentVariables.Add(DotNetModifiableAssembliesSwitch, DotNetModifiableAssembliesValue);
+ options = options ?? new RemoteInvokeOptions();
+ options.StartInfo.EnvironmentVariables.Add(DotNetModifiableAssembliesSwitch, DotNetModifiableAssembliesValue);
}
/// Run the given test case, which applies updates to the given assembly.
diff --git a/src/libraries/System.Runtime.Loader/tests/AssemblyExtensionsTest.cs b/src/libraries/System.Runtime.Loader/tests/AssemblyExtensionsTest.cs
deleted file mode 100644
index 5208b573c75d2..0000000000000
--- a/src/libraries/System.Runtime.Loader/tests/AssemblyExtensionsTest.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using Xunit;
-
-namespace System.Reflection.Metadata
-{
- public class AssemblyExtensionsTest
- {
- class NonRuntimeAssembly : Assembly
- {
- }
-
- [Fact]
- public static void ApplyUpdateInvalidParameters()
- {
- // Dummy delta arrays
- var metadataDelta = new byte[20];
- var ilDelta = new byte[20];
-
- // Assembly can't be null
- Assert.Throws("assembly", () =>
- AssemblyExtensions.ApplyUpdate(null, new ReadOnlySpan(metadataDelta), new ReadOnlySpan(ilDelta), ReadOnlySpan.Empty));
-
- // Tests fail on non-runtime assemblies
- Assert.Throws(() =>
- AssemblyExtensions.ApplyUpdate(new NonRuntimeAssembly(), new ReadOnlySpan(metadataDelta), new ReadOnlySpan(ilDelta), ReadOnlySpan.Empty));
-
- // Tests that this assembly isn't not editable
- Assert.Throws(() =>
- AssemblyExtensions.ApplyUpdate(typeof(AssemblyExtensions).Assembly, new ReadOnlySpan(metadataDelta), new ReadOnlySpan(ilDelta), ReadOnlySpan.Empty));
- }
-
- [Fact]
- public void GetApplyUpdateCapabilitiesIsCallable()
- {
- var ty = typeof(System.Reflection.Metadata.AssemblyExtensions);
- var mi = ty.GetMethod("GetApplyUpdateCapabilities", BindingFlags.NonPublic | BindingFlags.Static, Array.Empty());
-
- Assert.NotNull(mi);
-
- var result = mi.Invoke(null, null);
-
- Assert.NotNull(result);
- Assert.Equal(typeof(string), result.GetType());
- }
- }
-}
diff --git a/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csproj b/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csproj
index dc1a2cccd21a5..eeae81e75cc68 100644
--- a/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csproj
+++ b/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csproj
@@ -12,7 +12,6 @@
-
diff --git a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj
index 61ad598697e04..803d83464ea12 100644
--- a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj
+++ b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj
@@ -239,6 +239,7 @@
+
diff --git a/src/mono/System.Private.CoreLib/src/System/Reflection/Metadata/MetadataUpdater.cs b/src/mono/System.Private.CoreLib/src/System/Reflection/Metadata/MetadataUpdater.cs
new file mode 100644
index 0000000000000..6dbcdc783caa0
--- /dev/null
+++ b/src/mono/System.Private.CoreLib/src/System/Reflection/Metadata/MetadataUpdater.cs
@@ -0,0 +1,65 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace System.Reflection.Metadata
+{
+ public static class MetadataUpdater
+ {
+ ///
+ /// Updates the specified assembly using the provided metadata, IL and PDB deltas.
+ ///
+ ///
+ /// Currently executing methods will continue to use the existing IL. New executions of modified methods will
+ /// use the new IL. Different runtimes may have different limitations on what kinds of changes are supported,
+ /// and runtimes make no guarantees as to the state of the assembly and process if the delta includes
+ /// unsupported changes.
+ ///
+ /// The assembly to update.
+ /// The metadata changes to be applied.
+ /// The IL changes to be applied.
+ /// The PDB changes to be applied.
+ /// The assembly argument is null.
+ /// The update could not be applied.
+ public static void ApplyUpdate(Assembly assembly, ReadOnlySpan metadataDelta, ReadOnlySpan ilDelta, ReadOnlySpan pdbDelta)
+ {
+ if (assembly is not RuntimeAssembly runtimeAssembly)
+ {
+ if (assembly is null) throw new ArgumentNullException(nameof(assembly));
+ throw new ArgumentException(SR.Argument_MustBeRuntimeAssembly);
+ }
+
+ // System.Private.CoreLib is not editable
+ if (runtimeAssembly == typeof(AssemblyExtensions).Assembly)
+ throw new InvalidOperationException (SR.InvalidOperation_AssemblyNotEditable);
+
+ unsafe
+ {
+ IntPtr monoAssembly = runtimeAssembly.GetUnderlyingNativeHandle ();
+ fixed (byte* metadataDeltaPtr = metadataDelta, ilDeltaPtr = ilDelta, pdbDeltaPtr = pdbDelta)
+ {
+ ApplyUpdate_internal(monoAssembly, metadataDeltaPtr, metadataDelta.Length, ilDeltaPtr, ilDelta.Length, pdbDeltaPtr, pdbDelta.Length);
+ }
+ }
+ }
+
+ private static Lazy s_ApplyUpdateCapabilities = new Lazy(() => InitializeApplyUpdateCapabilities());
+
+ public static string GetCapabilities() => s_ApplyUpdateCapabilities.Value;
+
+ private static string InitializeApplyUpdateCapabilities()
+ {
+ return ApplyUpdateEnabled() != 0 ? "Baseline" : string.Empty ;
+ }
+
+ public static bool IsSupported => Debugger.IsAttached || Environment.GetEnvironmentVariable("DOTNET_MODIFIABLE_ASSEMBLIES") != "";
+
+ [MethodImpl (MethodImplOptions.InternalCall)]
+ private static extern int ApplyUpdateEnabled ();
+
+ [MethodImpl (MethodImplOptions.InternalCall)]
+ private static unsafe extern void ApplyUpdate_internal (IntPtr base_assm, byte* dmeta_bytes, int dmeta_length, byte *dil_bytes, int dil_length, byte *dpdb_bytes, int dpdb_length);
+ }
+}