Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

[hot reload][mono] Implement support for adding static and instance fields to generic classes #87285

Merged
merged 12 commits into from
Jun 16, 2023
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,7 @@ public double FireEvents() {

return Accumulator;
}

public DateTime GetDateTime() => default(DateTime);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ public double FireEvents() {
return Accumulator;
}

public DateTime GetDateTime() => default(DateTime);

public double AddedFirstProp {get => 0.0; set { Console.WriteLine (value); } }

public DateTime AddedDateTime;

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,12 @@ public double FireEvents() {
return Accumulator;
}

public DateTime GetDateTime() => AddedDateTime;

public double AddedFirstProp {get => 0.0; set { Console.WriteLine (value+value); } }
public short AddedSecondProp {get; set; }

public DateTime AddedDateTime;

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;


namespace System.Reflection.Metadata.ApplyUpdate.Test
{
public class GenericAddInstanceField<T>
{
public GenericAddInstanceField (T p) {
}

public T GetIt()
{
return default(T);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;


namespace System.Reflection.Metadata.ApplyUpdate.Test
{
public class GenericAddInstanceField<T>
{
public GenericAddInstanceField (T p) {
}

T myAddedField;

public T GetIt()
{
return default(T);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;


namespace System.Reflection.Metadata.ApplyUpdate.Test
{
public class GenericAddInstanceField<T>
{
public GenericAddInstanceField (T p) {
myAddedField = p;
}

T myAddedField;

public T GetIt()
{
return myAddedField;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>System.Runtime.Loader.Tests</RootNamespace>
<TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
<TestRuntime>true</TestRuntime>
<DeltaScript>deltascript.json</DeltaScript>
</PropertyGroup>
<ItemGroup>
<Compile Include="GenericAddInstanceField.cs" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"changes": [
{"document": "GenericAddInstanceField.cs", "update": "GenericAddInstanceField_v1.cs"},
{"document": "GenericAddInstanceField.cs", "update": "GenericAddInstanceField_v2.cs"},
]
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;


namespace System.Reflection.Metadata.ApplyUpdate.Test
{
public class GenericAddStaticField<T>
{
public GenericAddStaticField () {
}

public T GetField () => s_field;

private static T s_field;

public void TestMethod () {
s_field = (T)(object)"abcd";
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;


namespace System.Reflection.Metadata.ApplyUpdate.Test
{
public class GenericAddStaticField<T>
{
public GenericAddStaticField () {
}

public T GetField () => s_field;

private static T s_field;

public static T s_field2;

public void TestMethod () {
s_field = (T)(object)"spqr";
//s_field2 = (T)(object)"4567";
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;


namespace System.Reflection.Metadata.ApplyUpdate.Test
{
public class GenericAddStaticField<T>
{
public GenericAddStaticField () {
}

public T GetField () => s_field2;

private static T s_field;

public static T s_field2;

public void TestMethod () {
s_field = (T)(object)"spqr";
s_field2 = (T)(object)"4567";
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>System.Runtime.Loader.Tests</RootNamespace>
<TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
<TestRuntime>true</TestRuntime>
<DeltaScript>deltascript.json</DeltaScript>
</PropertyGroup>
<ItemGroup>
<Compile Include="GenericAddStaticField.cs" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"capabilities": ["Baseline", "AddStaticFieldToExistingType", "AddInstanceFieldToExistingType", "GenericUpdateMethod", "GenericAddFieldToExistingType"],
"changes": [
{"document": "GenericAddStaticField.cs", "update": "GenericAddStaticField_v1.cs"},
{"document": "GenericAddStaticField.cs", "update": "GenericAddStaticField_v2.cs"},
]
}

141 changes: 118 additions & 23 deletions src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,11 @@ public static void TestAddInstanceField()

Assert.True ((addedEventToken & 0x00ffffff) < 4);

fi = x2.GetType().GetField("AddedDateTime");
Assert.NotNull(fi);
var dt = DateTime.Now;
fi.SetValue(x2, dt);
Assert.Equal(dt, fi.GetValue(x2));

ApplyUpdateUtil.ApplyUpdate(assm);

Expand All @@ -419,6 +424,8 @@ public static void TestAddInstanceField()
var secondPropGetter = addedSecondPropInfo.GetGetMethod();
Assert.NotNull (secondPropGetter);

Assert.Equal(dt, x2.GetDateTime());

});
}

Expand Down Expand Up @@ -753,7 +760,7 @@ public static void TestReflectionAddNewMethod()
var ty = typeof(System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewMethod);
var assm = ty.Assembly;

var bindingFlags = BindingFlags.Instance | BindingFlags.Public;
var bindingFlags = BindingFlags.Instance | BindingFlags.Public;
var allMethods = ty.GetMethods(bindingFlags);

int objectMethods = typeof(object).GetMethods(bindingFlags).Length;
Expand Down Expand Up @@ -799,32 +806,120 @@ public static void TestReflectionAddNewMethod()
parmPos++;
}

var parmAttrs = parms[4].GetCustomAttributes(false);
var parmAttrs = parms[4].GetCustomAttributes(false);
Assert.Equal (2, parmAttrs.Length);
bool foundCallerMemberName = false;
bool foundOptional = false;
foreach (var pa in parmAttrs) {
if (typeof (CallerMemberNameAttribute).Equals(pa.GetType()))
{
foundCallerMemberName = true;
}
if (typeof (OptionalAttribute).Equals(pa.GetType()))
{
foundOptional = true;
}
}
Assert.True(foundCallerMemberName);
Assert.True(foundOptional);

// n.b. this typeof() also makes the rest of the test work on Wasm with aggressive trimming.
Assert.Equal (typeof(System.Threading.CancellationToken), parms[3].ParameterType);
bool foundCallerMemberName = false;
bool foundOptional = false;
foreach (var pa in parmAttrs) {
if (typeof (CallerMemberNameAttribute).Equals(pa.GetType()))
{
foundCallerMemberName = true;
}
if (typeof (OptionalAttribute).Equals(pa.GetType()))
{
foundOptional = true;
}
}
Assert.True(foundCallerMemberName);
Assert.True(foundOptional);

// n.b. this typeof() also makes the rest of the test work on Wasm with aggressive trimming.
Assert.Equal (typeof(System.Threading.CancellationToken), parms[3].ParameterType);

Assert.True(parms[3].HasDefaultValue);
Assert.True(parms[4].HasDefaultValue);
Assert.True(parms[4].HasDefaultValue);

Assert.Null(parms[3].DefaultValue);
Assert.Equal(string.Empty, parms[4].DefaultValue);
});
}

[ActiveIssue("https://github.com/dotnet/runtime/issues/87574", TestRuntimes.CoreCLR)]
[ConditionalFact(typeof(ApplyUpdateUtil), nameof(ApplyUpdateUtil.IsSupported))]
public static void TestGenericAddStaticField()
{
ApplyUpdateUtil.TestCase(static () =>
{
var assm = typeof(System.Reflection.Metadata.ApplyUpdate.Test.GenericAddStaticField<>).Assembly;

var x = new System.Reflection.Metadata.ApplyUpdate.Test.GenericAddStaticField<string>();

x.TestMethod();

Assert.Equal ("abcd", x.GetField());

var y = new System.Reflection.Metadata.ApplyUpdate.Test.GenericAddStaticField<double>();

Assert.Equal (0.0, y.GetField());

ApplyUpdateUtil.ApplyUpdate(assm);

// there are two updates - the first adds the fields, the second one updates the
// methods to use the new fields
ApplyUpdateUtil.ApplyUpdate(assm);

x.TestMethod();

string result = x.GetField();
Assert.Equal("4567", result);

Assert.Null(parms[3].DefaultValue);
Assert.Equal(string.Empty, parms[4].DefaultValue);
Assert.Equal(0.0, y.GetField());
});
}
}

[ActiveIssue("https://github.com/dotnet/runtime/issues/87574", TestRuntimes.CoreCLR)]
[ConditionalFact(typeof(ApplyUpdateUtil), nameof(ApplyUpdateUtil.IsSupported))]
public static void TestGenericAddInstanceField()
{
ApplyUpdateUtil.TestCase(static () =>
{
var assm = typeof(System.Reflection.Metadata.ApplyUpdate.Test.GenericAddInstanceField<>).Assembly;

var x = new System.Reflection.Metadata.ApplyUpdate.Test.GenericAddInstanceField<string>("abcd");

Assert.Null (x.GetIt());

var y = new System.Reflection.Metadata.ApplyUpdate.Test.GenericAddInstanceField<double>(45.0);

Assert.Equal (0.0, y.GetIt());

ApplyUpdateUtil.ApplyUpdate(assm);

var fi = x.GetType().GetField("myAddedField", BindingFlags.Instance | BindingFlags.NonPublic);

Assert.NotNull(fi);

Assert.Equal ("myAddedField", fi.Name);

Assert.Equal (typeof(string), fi.FieldType);

var fi2 = y.GetType().GetField("myAddedField", BindingFlags.Instance | BindingFlags.NonPublic);

Assert.NotNull(fi2);

Assert.Equal ("myAddedField", fi2.Name);

Assert.Equal (typeof(double), fi2.FieldType);

// there are two updates - the first adds the fields, the second one updates the
// methods to use the new fields
ApplyUpdateUtil.ApplyUpdate(assm);

Assert.Null (x.GetIt());
Assert.Equal (0.0, y.GetIt());

x = new System.Reflection.Metadata.ApplyUpdate.Test.GenericAddInstanceField<string>("spqr");

string result = x.GetIt();
Assert.Equal("spqr", result);

y = new System.Reflection.Metadata.ApplyUpdate.Test.GenericAddInstanceField<double>(2.717);
Assert.Equal(2.717, y.GetIt());

var dt = DateTime.Now;
var z = new System.Reflection.Metadata.ApplyUpdate.Test.GenericAddInstanceField<DateTime>(dt);
Assert.Equal(dt, z.GetIt());
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@
<ProjectReference Include="ApplyUpdate\System.Reflection.Metadata.ApplyUpdate.Test.StaticLambdaRegression\System.Reflection.Metadata.ApplyUpdate.Test.StaticLambdaRegression.csproj" />
<ProjectReference Include="ApplyUpdate\System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewType\System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewType.csproj" />
<ProjectReference Include="ApplyUpdate\System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewMethod\System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewMethod.csproj" />
<ProjectReference Include="ApplyUpdate\System.Reflection.Metadata.ApplyUpdate.Test.GenericAddStaticField\System.Reflection.Metadata.ApplyUpdate.Test.GenericAddStaticField.csproj" />
<ProjectReference Include="ApplyUpdate\System.Reflection.Metadata.ApplyUpdate.Test.GenericAddInstanceField\System.Reflection.Metadata.ApplyUpdate.Test.GenericAddInstanceField.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(TargetOS)' == 'browser'">
<WasmFilesToIncludeFromPublishDir Include="$(AssemblyName).dll" />
Expand Down
Loading