Skip to content

[hot_reload] Add instance fields #76462

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

Merged
merged 55 commits into from
Nov 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
74ac7d4
[hot_reload] Add new AddInstanceField test
lambdageek Sep 27, 2022
0841c40
WIP builds doesn't link; need impl for mono_metadata_update_ldflda
lambdageek Sep 29, 2022
f244218
WIP builds
lambdageek Sep 29, 2022
b13e6ff
WIP test runs, fails on string load/stores
lambdageek Sep 29, 2022
a404ee3
Simple test works
lambdageek Sep 30, 2022
c40cb12
Add AddInstanceFieldToExistingType capability to runtime
lambdageek Sep 30, 2022
0c7ab0b
fix comment; remove unused var
lambdageek Sep 30, 2022
ceaee7c
Add instance field: reflection and nested struct
lambdageek Oct 3, 2022
d9d8827
make TypedReference work with added fields
lambdageek Oct 4, 2022
cfc91d3
Add FieldInfo.SetValue testcase
lambdageek Oct 4, 2022
d423ae8
make FieldInfo.SetValue work
lambdageek Oct 4, 2022
95318ff
fix whitespace
lambdageek Oct 4, 2022
b05d541
more whitespace
lambdageek Oct 4, 2022
afdaabf
remove TODO
lambdageek Oct 4, 2022
9582cb3
fix Windows build error
lambdageek Oct 5, 2022
042b431
review feedback
lambdageek Oct 5, 2022
a5a8815
Implement LDFLDA opcode for added fields; add test
lambdageek Oct 5, 2022
afe37d6
fix build
lambdageek Oct 5, 2022
17a9927
rm TODO
lambdageek Oct 6, 2022
62ab2e6
Disable test on CoreCLR
lambdageek Oct 6, 2022
69de824
WIP start adding a wasm debugger test
lambdageek Oct 6, 2022
2c7b8e7
WIP XXX - semi-broken debugger test
lambdageek Oct 18, 2022
dadda4e
OK - test is failing in the debugger agent (see details)
lambdageek Oct 24, 2022
b9b0c6b
make the debugger test a bit more interesting
lambdageek Oct 25, 2022
18f440f
implement a debugger test that gets/sets instance fields
lambdageek Oct 25, 2022
ef7805d
Clear the debugger type cache on EnC update
lambdageek Oct 25, 2022
79f5abc
[sdb] implement get/set field for added instance fields
lambdageek Oct 25, 2022
13a4b25
[handles] remove the field getter/setter macros
lambdageek Oct 26, 2022
88a466a
[metadata-update] comments on asserts
lambdageek Oct 26, 2022
4d7ee0b
implement mono_field_get_addr added field support
lambdageek Oct 26, 2022
67e02a0
add TODO for initializers for added literals
lambdageek Oct 26, 2022
8d1a90e
remove last use of MONO_HANDLE_SET_FIELD_REF
lambdageek Nov 10, 2022
2a01071
WeakAttribute is not picked up from added fields
lambdageek Nov 14, 2022
8fabc0d
Add a test for an array with RVA
lambdageek Nov 14, 2022
ca9c3c9
Impl mono_metadata_field_info_full
lambdageek Nov 14, 2022
49a907e
mono_class_get_field_token
lambdageek Nov 14, 2022
54f4f2a
mono_field_get_rva
lambdageek Nov 14, 2022
78eeee2
Add SetValueDirect/GetValueDirect test (failing)
lambdageek Nov 14, 2022
afe88ec
Implement GetValueDirect - hot reload test passes now
lambdageek Nov 14, 2022
6e9811d
fix init
lambdageek Nov 14, 2022
d90d17c
Added test for auto property; crashes in ves_icall_RuntimePropertyInf…
lambdageek Nov 15, 2022
517978e
fix rebase whitespace
lambdageek Nov 15, 2022
619d956
WIP checkpoint add_props
lambdageek Nov 16, 2022
9b6503a
fixup comment - repeated MethodSemantics rows can happen
lambdageek Nov 17, 2022
21dad8b
added properties iteration
lambdageek Nov 17, 2022
2bc5e5a
protect callers of mono_class_get_property_token
lambdageek Nov 17, 2022
ac34462
fix build warnings
lambdageek Nov 18, 2022
fbcd1b2
fix dynamic component builds
lambdageek Nov 18, 2022
2934993
make mono_class_get_property_token work for added props
lambdageek Nov 18, 2022
9c4c18a
add test for adding instance event
lambdageek Nov 18, 2022
3216155
basic event reflection works
lambdageek Nov 18, 2022
a6dadc1
Fire the new event, too
lambdageek Nov 18, 2022
bd571f9
make reflection MetadataToken work
lambdageek Nov 18, 2022
b14f565
fix dynamic component build
lambdageek Nov 18, 2022
0a849ac
remove unused ifdefs and fix whitespace
lambdageek Nov 21, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// 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 AddInstanceField
{
public AddInstanceField () {
}

public string GetStringField => _stringField;
public double GetDoubleField => _doubleField;

private string _stringField;
private double _doubleField;

private int[] _intArrayFieldWithInit = new[] { 2, 4, 6, 8, 10, 12 };

public void TestMethod () {
_stringField = "abcd";
_doubleField = 3.14159;
}

public int GetIntArrayLength() => _intArrayFieldWithInit?.Length ?? -1;
public int GetIntArrayElt(int i) => _intArrayFieldWithInit[i];

public void IncRefDouble (ref double d)
{
d += 1.0;
}

public string GetStringProp => string.Empty;

public event EventHandler<double> ExistingEvent;

public double Accumulator;

private void AccumHandler (object sender, double value) => Accumulator += value;

public double FireEvents() {
Accumulator = 0.0;
ExistingEvent += AccumHandler;
ExistingEvent(this, 123.0);
ExistingEvent -= AccumHandler;

return Accumulator;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// 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 AddInstanceField
{
public AddInstanceField () {
_doubleField2 = 5.5;
_stringField2 = "New Initial Value";
NewStructField = new NewStruct {
D = -1985.0,
O = new int[2] { 15, 17 },
};
// a little bit ldflda testing
IncRefDouble (ref NewStructField.D);
IncRefDouble (ref _doubleField2);

AddedStringAutoProp = "abcd";

AddedEvent += MyHandler;

void MyHandler (object sender, double data) {
}
}

public void IncRefDouble (ref double d)
{
d += 1.0;
}

public string GetStringField => _stringField2;
public double GetDoubleField => _doubleField2;

private string _stringField;
private string _stringField2;
private double _doubleField;
private double _doubleField2;

private int[] _intArrayFieldWithInit = new[] { 2, 4, 6, 8, 10, 12 };
private int[] _intArrayFieldWithInit2 = new[] { 1, 3, 5, 7, 9, 11 };

public void TestMethod () {
_stringField = "spqr";
_stringField2 = "4567";
_doubleField = 2.71828;
_doubleField2 = 0.707106;
AddedStringAutoProp = AddedStringAutoProp + "Test";
}

public int GetIntArrayLength() => _intArrayFieldWithInit2?.Length ?? -1;
public int GetIntArrayElt(int i) => _intArrayFieldWithInit2[i];

public struct NewStruct
{
public double D;
public object O;
}

public NewStruct NewStructField;

public string GetStringProp => AddedStringAutoProp;

public string AddedStringAutoProp { get; set; }

public event EventHandler<double> ExistingEvent;
public event EventHandler<double> AddedEvent;

public double Accumulator;

private void AccumHandler (object sender, double value) => Accumulator += value;

public double FireEvents() {
Accumulator = 0.0;
ExistingEvent += AccumHandler;
ExistingEvent(this, 123.0);
ExistingEvent -= AccumHandler;

AddedEvent += AccumHandler;
AddedEvent(this, 123.0);
AddedEvent -= AccumHandler;

return Accumulator;
}

}
}
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="AddInstanceField.cs" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"changes": [
{"document": "AddInstanceField.cs", "update": "AddInstanceField_v1.cs"},
]
}

107 changes: 107 additions & 0 deletions src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,113 @@ public static void TestAddStaticField()
});
}

[ActiveIssue("https://github.com/dotnet/runtime/issues/76702", TestRuntimes.CoreCLR)]
[ConditionalFact(typeof(ApplyUpdateUtil), nameof(ApplyUpdateUtil.IsSupported))]
public static void TestAddInstanceField()
{
// Test that adding a new instance field to an existing class is supported
ApplyUpdateUtil.TestCase(static () =>
{
var assm = typeof(System.Reflection.Metadata.ApplyUpdate.Test.AddInstanceField).Assembly;

var x1 = new System.Reflection.Metadata.ApplyUpdate.Test.AddInstanceField();

x1.TestMethod();

Assert.Equal ("abcd", x1.GetStringField);
Assert.Equal (3.14159, x1.GetDoubleField);

ApplyUpdateUtil.ApplyUpdate(assm);

x1.TestMethod();

Assert.Equal ("4567", x1.GetStringField);
Assert.Equal (0.707106, x1.GetDoubleField);

Assert.Equal (-1, x1.GetIntArrayLength ()); // new field on existing object is initially null

var x2 = new System.Reflection.Metadata.ApplyUpdate.Test.AddInstanceField();

Assert.Equal ("New Initial Value", x2.GetStringField);
Assert.Equal (6.5, x2.GetDoubleField);

Assert.Equal (6, x2.GetIntArrayLength());
Assert.Equal (7, x2.GetIntArrayElt (3));

// now check that reflection can get/set the new fields
var fi = x2.GetType().GetField("NewStructField");

Assert.NotNull(fi);

var s = fi.GetValue (x2);

Assert.NotNull(x2);

var fid = fi.FieldType.GetField("D");
Assert.NotNull(fid);
Assert.Equal(-1984.0, fid.GetValue(s));
var tr = TypedReference.MakeTypedReference (x2, new FieldInfo[] {fi});
fid.SetValueDirect(tr, (object)34567.0);
Assert.Equal (34567.0, fid.GetValueDirect (tr));

fi = x2.GetType().GetField("_doubleField2", BindingFlags.NonPublic | BindingFlags.Instance);

Assert.NotNull(fi);

fi.SetValue(x2, 65535.01);
Assert.Equal(65535.01, x2.GetDoubleField);

tr = __makeref(x2);
fi.SetValueDirect (tr, 32768.2);
Assert.Equal (32768.2, x2.GetDoubleField);
Assert.Equal ((object)32768.2, fi.GetValueDirect (tr));

Assert.Equal("abcd", x2.GetStringProp);

var propInfo = x2.GetType().GetProperty("AddedStringAutoProp", BindingFlags.Public | BindingFlags.Instance);

Assert.NotNull(propInfo);
Assert.Equal("abcd", propInfo.GetMethod.Invoke (x2, new object[] {}));

x2.TestMethod();

Assert.Equal("abcdTest", x2.GetStringProp);

var addedPropToken = propInfo.MetadataToken;

Assert.True (addedPropToken > 0);

// we don't know exactly what token Roslyn will assign to the added property, but
// since the AddInstanceField.dll assembly is relatively small, assume that the
// total number of properties in the updated generation is less than 64 and the
// token is in that range. If more code is added, revise this test.

Assert.True ((addedPropToken & 0x00ffffff) < 64);


var accumResult = x2.FireEvents();

Assert.Equal (246.0, accumResult);

var eventInfo = x2.GetType().GetEvent("AddedEvent", BindingFlags.Public | BindingFlags.Instance);

Assert.NotNull (eventInfo);

var addedEventToken = eventInfo.MetadataToken;

Assert.True (addedEventToken > 0);

// we don't know exactly what token Roslyn will assign to the added event, but
// since the AddInstanceField.dll assembly is relatively small, assume that the
// total number of events in the updated generation is less than 4 and the
// token is in that range. If more code is added, revise this test.

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


});
}

[ConditionalFact(typeof(ApplyUpdateUtil), nameof(ApplyUpdateUtil.IsSupported))]
public static void TestAddNestedClass()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
<ProjectReference Include="ApplyUpdate\System.Reflection.Metadata.ApplyUpdate.Test.FirstCallAfterUpdate\System.Reflection.Metadata.ApplyUpdate.Test.FirstCallAfterUpdate.csproj" />
<ProjectReference Include="ApplyUpdate\System.Reflection.Metadata.ApplyUpdate.Test.AddLambdaCapturingThis\System.Reflection.Metadata.ApplyUpdate.Test.AddLambdaCapturingThis.csproj" />
<ProjectReference Include="ApplyUpdate\System.Reflection.Metadata.ApplyUpdate.Test.AddStaticField\System.Reflection.Metadata.ApplyUpdate.Test.AddStaticField.csproj" />
<ProjectReference Include="ApplyUpdate\System.Reflection.Metadata.ApplyUpdate.Test.AddInstanceField\System.Reflection.Metadata.ApplyUpdate.Test.AddInstanceField.csproj" />
<ProjectReference Include="ApplyUpdate\System.Reflection.Metadata.ApplyUpdate.Test.AddNestedClass\System.Reflection.Metadata.ApplyUpdate.Test.AddNestedClass.csproj" />
<ProjectReference Include="ApplyUpdate\System.Reflection.Metadata.ApplyUpdate.Test.AddStaticLambda\System.Reflection.Metadata.ApplyUpdate.Test.AddStaticLambda.csproj" />
<ProjectReference Include="ApplyUpdate\System.Reflection.Metadata.ApplyUpdate.Test.StaticLambdaRegression\System.Reflection.Metadata.ApplyUpdate.Test.StaticLambdaRegression.csproj" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -644,5 +644,9 @@

<!-- FIXME: only if feature="System.Reflection.Metadata.MetadataUpdater.IsSupported" featurevalue="true" -->
<type fullname="Mono.HotReload.FieldStore" preserve="fields" />
<type fullname="Mono.HotReload.InstanceFieldTable">
<!-- hot_reload.c -->
<method name="GetInstanceFieldFieldStore" />
</type>
</assembly>
</linker>
20 changes: 10 additions & 10 deletions src/mono/System.Private.CoreLib/src/Mono/HotReload.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace Mono.HotReload;

// TODO: this is just a sketch, instance field additions aren't supported by Mono yet until https://github.com/dotnet/runtime/issues/63643 is fixed
#if false
internal class InstanceFieldTable
internal sealed class InstanceFieldTable
{
// Q: Does CoreCLR EnC allow adding fields to a valuetype?
// A: No, see EEClass::AddField - if the type has layout or is a valuetype, you can't add fields to it.
Expand All @@ -34,9 +33,10 @@ internal class InstanceFieldTable
// This should behave somewhat like EditAndContinueModule::ResolveOrAddField (and EnCAddedField::Allocate)
// we want to create some storage space that has the same lifetime as the instance object.

// // TODO: should the linker keep this if Hot Reload stuff is enabled? Hot Reload is predicated on the linker not rewriting user modules, but maybe trimming SPC is ok?
internal static FieldStore GetInstanceFieldFieldStore(object inst, RuntimeTypeHandle type, uint fielddef_token)
=> _singleton.GetOrCreateInstanceFields(inst).LookupOrAdd(type, fielddef_token);
internal static FieldStore GetInstanceFieldFieldStore(object inst, IntPtr type, uint fielddef_token)
{
return _singleton.GetOrCreateInstanceFields(inst).LookupOrAdd(new RuntimeTypeHandle (type), fielddef_token);
}

private static InstanceFieldTable _singleton = new();

Expand All @@ -50,7 +50,7 @@ private InstanceFieldTable()
private InstanceFields GetOrCreateInstanceFields(object key)
=> _table.GetOrCreateValue(key);

private class InstanceFields
private sealed class InstanceFields
{
private Dictionary<uint, FieldStore> _fields;
private object _lock;
Expand All @@ -61,6 +61,8 @@ public InstanceFields()
_lock = new();
}

[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "Hot reload required untrimmed apps")]
public FieldStore LookupOrAdd(RuntimeTypeHandle type, uint key)
{
if (_fields.TryGetValue(key, out FieldStore? v))
Expand All @@ -78,7 +80,6 @@ public FieldStore LookupOrAdd(RuntimeTypeHandle type, uint key)
}

}
#endif

// This is similar to System.Diagnostics.EditAndContinueHelper in CoreCLR, except instead of
// having the allocation logic in native (see EditAndContinueModule::ResolveOrAllocateField,
Expand All @@ -96,8 +97,7 @@ private FieldStore (object? loc)
_loc = loc;
}

public object? Location => _loc;

[RequiresUnreferencedCode("Hot reload required untrimmed apps")]
public static FieldStore Create (RuntimeTypeHandle type)
{
Type t = Type.GetTypeFromHandle(type) ?? throw new ArgumentException(nameof(type), "Type handle was null");
Expand Down
Loading