Skip to content
New issue

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

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

Already on GitHub? Sign in 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