diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.AddInstanceField/AddInstanceField.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.AddInstanceField/AddInstanceField.cs new file mode 100644 index 00000000000000..0b10a8b11fc9c5 --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.AddInstanceField/AddInstanceField.cs @@ -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 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; + } + } +} diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.AddInstanceField/AddInstanceField_v1.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.AddInstanceField/AddInstanceField_v1.cs new file mode 100644 index 00000000000000..23626acf5af6f1 --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.AddInstanceField/AddInstanceField_v1.cs @@ -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 ExistingEvent; + public event EventHandler 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; + } + + } +} diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.AddInstanceField/System.Reflection.Metadata.ApplyUpdate.Test.AddInstanceField.csproj b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.AddInstanceField/System.Reflection.Metadata.ApplyUpdate.Test.AddInstanceField.csproj new file mode 100644 index 00000000000000..712e5b36fcce23 --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.AddInstanceField/System.Reflection.Metadata.ApplyUpdate.Test.AddInstanceField.csproj @@ -0,0 +1,11 @@ + + + System.Runtime.Loader.Tests + $(NetCoreAppCurrent) + true + deltascript.json + + + + + diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.AddInstanceField/deltascript.json b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.AddInstanceField/deltascript.json new file mode 100644 index 00000000000000..3c81223a4fc7d9 --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.AddInstanceField/deltascript.json @@ -0,0 +1,6 @@ +{ + "changes": [ + {"document": "AddInstanceField.cs", "update": "AddInstanceField_v1.cs"}, + ] +} + diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs index 92f19a6b0c023e..141bc239084441 100644 --- a/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs @@ -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() { 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 6c5a8469726179..041e21c9d9af8b 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 @@ -58,6 +58,7 @@ + diff --git a/src/mono/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.xml b/src/mono/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.xml index 3d13f30e92bbed..97aba6b7225d2f 100644 --- a/src/mono/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.xml +++ b/src/mono/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.xml @@ -639,5 +639,9 @@ + + + + diff --git a/src/mono/System.Private.CoreLib/src/Mono/HotReload.cs b/src/mono/System.Private.CoreLib/src/Mono/HotReload.cs index f9096a68869bf0..afdcc34cabbc9c 100644 --- a/src/mono/System.Private.CoreLib/src/Mono/HotReload.cs +++ b/src/mono/System.Private.CoreLib/src/Mono/HotReload.cs @@ -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. @@ -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(); @@ -50,7 +50,7 @@ private InstanceFieldTable() private InstanceFields GetOrCreateInstanceFields(object key) => _table.GetOrCreateValue(key); - private class InstanceFields + private sealed class InstanceFields { private Dictionary _fields; private object _lock; @@ -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)) @@ -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, @@ -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"); diff --git a/src/mono/mono/component/debugger-agent.c b/src/mono/mono/component/debugger-agent.c index 2be637db94ea9f..09acd344bf24b8 100644 --- a/src/mono/mono/component/debugger-agent.c +++ b/src/mono/mono/component/debugger-agent.c @@ -9820,8 +9820,6 @@ object_commands (int command, guint8 *p, guint8 *end, Buffer *buf) if (!found) goto invalid_fieldid; get_field_value: - /* TODO: metadata-update: implement support for added fields */ - g_assert (!m_field_is_from_update (f)); if (f->type->attrs & FIELD_ATTRIBUTE_STATIC) { guint8 *val; MonoVTable *vtable; @@ -9844,7 +9842,17 @@ object_commands (int command, guint8 *p, guint8 *end, Buffer *buf) buffer_add_value (buf, f->type, val, obj->vtable->domain); g_free (val); } else { - void *field_value = (guint8*)obj + m_field_get_offset (f); + void *field_value = NULL; + if (G_UNLIKELY (m_field_is_from_update (f))) { + uint32_t token = mono_metadata_make_token (MONO_TABLE_FIELD, mono_metadata_update_get_field_idx (f)); + field_value = mono_metadata_update_added_field_ldflda (obj, f->type, token, error); + if (!is_ok (error)) { + mono_error_cleanup (error); + goto invalid_object; + } + } else { + field_value = (guint8*)obj + m_field_get_offset (f); + } buffer_add_value (buf, f->type, field_value, obj->vtable->domain); } @@ -9869,9 +9877,6 @@ object_commands (int command, guint8 *p, guint8 *end, Buffer *buf) if (!found) goto invalid_fieldid; - /* TODO: metadata-update: implement support for added fields. */ - g_assert (!m_field_is_from_update (f)); - if (f->type->attrs & FIELD_ATTRIBUTE_STATIC) { guint8 *val; MonoVTable *vtable; @@ -9895,7 +9900,18 @@ object_commands (int command, guint8 *p, guint8 *end, Buffer *buf) mono_field_static_set_value_internal (vtable, f, val); g_free (val); } else { - err = decode_value (f->type, obj->vtable->domain, (guint8*)obj + m_field_get_offset (f), p, &p, end, TRUE); + void *dest = NULL; + if (G_UNLIKELY (m_field_is_from_update (f))) { + uint32_t token = mono_metadata_make_token (MONO_TABLE_FIELD, mono_metadata_update_get_field_idx (f)); + dest = mono_metadata_update_added_field_ldflda (obj, f->type, token, error); + if (!is_ok (error)) { + mono_error_cleanup (error); + goto invalid_fieldid; + } + } else { + dest = (guint8*)obj + m_field_get_offset (f); + } + err = decode_value (f->type, obj->vtable->domain, dest, p, &p, end, TRUE); if (err != ERR_NONE) goto exit; } diff --git a/src/mono/mono/component/hot_reload-internals.h b/src/mono/mono/component/hot_reload-internals.h index cc97538749d056..65cdcde8f56bfb 100644 --- a/src/mono/mono/component/hot_reload-internals.h +++ b/src/mono/mono/component/hot_reload-internals.h @@ -82,7 +82,7 @@ typedef struct _MonoClassMetadataUpdateProperty { typedef struct _MonoClassMetadataUpdateEvent { MonoEvent evt; - uint32_t generatino; /* when this event was added */ + uint32_t generation; /* when this event was added */ uint32_t token; /* the Event table token where this event was defined. */ } MonoClassMetadataUpdateEvent; diff --git a/src/mono/mono/component/hot_reload-stub.c b/src/mono/mono/component/hot_reload-stub.c index 94730bbe26b164..73fc1fba50f381 100644 --- a/src/mono/mono/component/hot_reload-stub.c +++ b/src/mono/mono/component/hot_reload-stub.c @@ -113,6 +113,21 @@ hot_reload_get_capabilities (void); static uint32_t hot_reload_stub_get_method_params (MonoImage *base_image, uint32_t methoddef_token, uint32_t *out_param_count_opt); +static gpointer +hot_reload_stub_added_field_ldflda (MonoObject *instance, MonoType *field_type, uint32_t fielddef_token, MonoError *error); + +static MonoProperty * +hot_reload_stub_added_properties_iter (MonoClass *klass, gpointer *iter); + +static uint32_t +hot_reload_stub_get_property_idx (MonoProperty *prop); + +static MonoEvent * +hot_reload_stub_added_events_iter (MonoClass *klass, gpointer *iter); + +static uint32_t +hot_reload_stub_get_event_idx (MonoEvent *evt); + static MonoComponentHotReload fn_table = { { MONO_COMPONENT_ITF_VERSION, &hot_reload_stub_available }, &hot_reload_stub_set_fastpath_data, @@ -147,6 +162,11 @@ static MonoComponentHotReload fn_table = { &hot_reload_get_num_methods_added, &hot_reload_get_capabilities, &hot_reload_stub_get_method_params, + &hot_reload_stub_added_field_ldflda, + &hot_reload_stub_added_properties_iter, + &hot_reload_stub_get_property_idx, + &hot_reload_stub_added_events_iter, + &hot_reload_stub_get_event_idx, }; static bool @@ -353,9 +373,41 @@ hot_reload_stub_get_method_params (MonoImage *base_image, uint32_t methoddef_tok return 0; } +static gpointer +hot_reload_stub_added_field_ldflda (MonoObject *instance, MonoType *field_type, uint32_t fielddef_token, MonoError *error) +{ + return NULL; +} + +static MonoProperty * +hot_reload_stub_added_properties_iter (MonoClass *klass, gpointer *iter) +{ + return NULL; +} + +static uint32_t +hot_reload_stub_get_property_idx (MonoProperty *prop) +{ + return 0; +} + +MonoEvent * +hot_reload_stub_added_events_iter (MonoClass *klass, gpointer *iter) +{ + return NULL; +} + +static uint32_t +hot_reload_stub_get_event_idx (MonoEvent *evt) +{ + return 0; +} + MONO_COMPONENT_EXPORT_ENTRYPOINT MonoComponentHotReload * mono_component_hot_reload_init (void) { return component_hot_reload_stub_init (); } + + diff --git a/src/mono/mono/component/hot_reload.c b/src/mono/mono/component/hot_reload.c index c412aa3efe6662..120d227d7ccace 100644 --- a/src/mono/mono/component/hot_reload.c +++ b/src/mono/mono/component/hot_reload.c @@ -34,8 +34,6 @@ #include -#undef ALLOW_INSTANCE_FIELD_ADD - typedef struct _BaselineInfo BaselineInfo; typedef struct _DeltaInfo DeltaInfo; @@ -147,9 +145,39 @@ hot_reload_get_capabilities (void); static uint32_t hot_reload_get_method_params (MonoImage *base_image, uint32_t methoddef_token, uint32_t *out_param_count_opt); +static gpointer +hot_reload_added_field_ldflda (MonoObject *instance, MonoType *field_type, uint32_t fielddef_token, MonoError *error); + +static MonoProperty * +hot_reload_added_properties_iter (MonoClass *klass, gpointer *iter); + +static uint32_t +hot_reload_get_property_idx (MonoProperty *prop); + +MonoEvent * +hot_reload_added_events_iter (MonoClass *klass, gpointer *iter); + +static uint32_t +hot_reload_get_event_idx (MonoEvent *evt); + static MonoClassMetadataUpdateField * metadata_update_field_setup_basic_info (MonoImage *image_base, BaselineInfo *base_info, uint32_t generation, DeltaInfo *delta_info, MonoClass *parent_klass, uint32_t fielddef_token, uint32_t field_flags); +static uint32_t +hot_reload_member_parent (MonoImage *base_image, uint32_t member_token); + +static MonoClassMetadataUpdateProperty * +add_property_to_existing_class (MonoImage *image_base, BaselineInfo *base_info, uint32_t generation, DeltaInfo *delta_info, MonoClass *parent_klass, uint32_t property_token, uint32_t property_flags); + +static void +add_semantic_method_to_existing_property (MonoImage *image_base, BaselineInfo *base_info, uint32_t semantics, uint32_t klass_token, uint32_t prop_token, uint32_t method_token); + +MonoClassMetadataUpdateEvent * +add_event_to_existing_class (MonoImage *image_base, BaselineInfo *base_info, uint32_t generation, DeltaInfo *delta_info, MonoClass *parent_klass, uint32_t event_token, uint32_t event_flags); + +static void +add_semantic_method_to_existing_event (MonoImage *image_base, BaselineInfo *base_info, uint32_t semantics, uint32_t klass_token, uint32_t event_token, uint32_t method_token); + static MonoComponentHotReload fn_table = { { MONO_COMPONENT_ITF_VERSION, &hot_reload_available }, &hot_reload_set_fastpath_data, @@ -184,6 +212,11 @@ static MonoComponentHotReload fn_table = { &hot_reload_get_num_methods_added, &hot_reload_get_capabilities, &hot_reload_get_method_params, + &hot_reload_added_field_ldflda, + &hot_reload_added_properties_iter, + &hot_reload_get_property_idx, + &hot_reload_added_events_iter, + &hot_reload_get_event_idx, }; MonoComponentHotReload * @@ -2201,15 +2234,6 @@ apply_enclog_pass2 (Pass2Context *ctx, MonoImage *image_base, BaselineInfo *base uint32_t mapped_token = hot_reload_relative_delta_index (image_dmeta, delta_info, log_token); uint32_t field_flags = mono_metadata_decode_row_col (&image_dmeta->tables [MONO_TABLE_FIELD], mapped_token - 1, MONO_FIELD_FLAGS); -#ifndef ALLOW_INSTANCE_FIELD_ADD - if ((field_flags & FIELD_ATTRIBUTE_STATIC) == 0) { - /* TODO: implement instance (and literal?) fields */ - mono_trace (G_LOG_LEVEL_WARNING, MONO_TRACE_METADATA_UPDATE, "Adding non-static fields isn't implemented yet (token 0x%08x, class %s.%s)", log_token, m_class_get_name_space (add_member_klass), m_class_get_name (add_member_klass)); - mono_error_set_not_implemented (error, "Adding non-static fields isn't implemented yet (token 0x%08x, class %s.%s)", log_token, m_class_get_name_space (add_member_klass), m_class_get_name (add_member_klass)); - return FALSE; - } -#endif - add_field_to_baseline (base_info, delta_info, add_member_klass, log_token); /* This actually does slightly more than @@ -2305,7 +2329,11 @@ apply_enclog_pass2 (Pass2Context *ctx, MonoImage *image_base, BaselineInfo *base } mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "Adding new property 0x%08x to class %s.%s", log_token, m_class_get_name_space (add_property_klass), m_class_get_name (add_property_klass)); - /* TODO: metadata-update: add a new MonoClassMetadataUpdatePropertyInfo to added_props */ + add_member_parent (base_info, parent_type_token, log_token); + + uint32_t mapped_token = hot_reload_relative_delta_index (image_dmeta, delta_info, log_token); + uint32_t property_flags = mono_metadata_decode_row_col (&image_dmeta->tables [MONO_TABLE_PROPERTY], mapped_token - 1, MONO_PROPERTY_FLAGS); + add_property_to_existing_class (image_base, base_info, generation, delta_info, add_property_klass, log_token, property_flags); break; } @@ -2358,7 +2386,12 @@ apply_enclog_pass2 (Pass2Context *ctx, MonoImage *image_base, BaselineInfo *base } mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "Adding new event 0x%08x to class %s.%s", log_token, m_class_get_name_space (add_event_klass), m_class_get_name (add_event_klass)); - /* TODO: metadata-update: add a new MonoEventInfo to the bag on the class? */ + add_member_parent (base_info, parent_type_token, log_token); + + uint32_t mapped_token = hot_reload_relative_delta_index (image_dmeta, delta_info, log_token); + uint32_t event_flags = mono_metadata_decode_row_col (&image_dmeta->tables [MONO_TABLE_EVENT], mapped_token - 1, MONO_EVENT_FLAGS); + add_event_to_existing_class (image_base, base_info, generation, delta_info, add_event_klass, log_token, event_flags); + break; } @@ -2411,6 +2444,56 @@ apply_enclog_pass2 (Pass2Context *ctx, MonoImage *image_base, BaselineInfo *base /* added rows ok (for added classes). will be processed when the MonoClass is created. */ break; } + case MONO_TABLE_METHODSEMANTICS: { + g_assert (is_addition); + /* added rows ok (for new or existing classes). modifications rejected by pass1 */ + uint32_t sema_cols[MONO_METHOD_SEMA_SIZE]; + + uint32_t mapped_token = hot_reload_relative_delta_index (image_dmeta, delta_info, log_token); + + mono_metadata_decode_row (&image_dmeta->tables [MONO_TABLE_METHODSEMANTICS], mapped_token - 1, sema_cols, MONO_METHOD_SEMA_SIZE); + + uint32_t assoc_idx = sema_cols [MONO_METHOD_SEMA_ASSOCIATION] >> MONO_HAS_SEMANTICS_BITS; + /* prop or event */ + gboolean is_prop = (sema_cols [MONO_METHOD_SEMA_ASSOCIATION] & MONO_HAS_SEMANTICS_MASK) == MONO_HAS_SEMANTICS_PROPERTY; + uint32_t method_idx = sema_cols [MONO_METHOD_SEMA_METHOD]; + uint32_t semantics = sema_cols [MONO_METHOD_SEMA_SEMANTICS]; + uint32_t assoc_token = mono_metadata_make_token (is_prop ? MONO_TABLE_PROPERTY : MONO_TABLE_EVENT, assoc_idx); + uint32_t method_token = mono_metadata_make_token (MONO_TABLE_METHOD, method_idx); + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "MethodSemantics [0x%08x] = { method_idx = 0x%08x, semantics = 0x%08x, association = 0x%08x (idx = 0x%08x, is_prop = %s)} ", log_token, method_idx, semantics, sema_cols [MONO_METHOD_SEMA_ASSOCIATION], assoc_idx, is_prop ? "true" : "false"); + /* class that owns the property or event */ + uint32_t klass_token = hot_reload_member_parent (image_base, assoc_token); + if (klass_token == 0) { + /* This can happen because Roslyn emits new MethodSemantics + * rows when a getter/setter method is updated. If you have + * something like: + * + * public string MyProp => string.Empty; + * + * and you change it to + * + * public string MyProp => "abcd"; + * + * Roslyn emits a MethodDef update (with the new method body RVA), a + * Property update (with the same content as the previous + * generation) and a new MethodSemantics row. + * + * In that case the assoc token points to the mutated Property row, + * for which we don't have a member_parent entry. So just ignore it. + */ + break; + } + if (pass2_context_is_skeleton (ctx, klass_token)) { + /* nothing to do, the semantics rows for the new class are contiguous and will be inited when the class is created */ + } else { + /* attach the new method to the correct place on the property/event */ + if (is_prop) + add_semantic_method_to_existing_property (image_base, base_info, semantics, klass_token, assoc_token, method_token); + else + add_semantic_method_to_existing_event (image_base, base_info, semantics, klass_token, assoc_token, method_token); + } + break; + } default: { g_assert (token_index > table_info_get_rows (&image_base->tables [token_table])); if (mono_trace_is_traced (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE)) @@ -2939,6 +3022,35 @@ hot_reload_get_field (MonoClass *klass, uint32_t fielddef_token) { return NULL; } +static MonoProperty * +hot_reload_get_property (MonoClass *klass, uint32_t property_token) +{ + MonoClassMetadataUpdateInfo *info = mono_class_get_or_add_metadata_update_info (klass); + g_assert (mono_metadata_token_table (property_token) == MONO_TABLE_PROPERTY); + GSList *added_props = info->added_props; + + for (GSList *p = added_props; p; p = p->next) { + MonoClassMetadataUpdateProperty *prop = (MonoClassMetadataUpdateProperty*)p->data; + if (prop->token == property_token) + return &prop->prop; + } + return NULL; +} + +static MonoEvent * +hot_reload_get_event (MonoClass *klass, uint32_t event_token) +{ + MonoClassMetadataUpdateInfo *info = mono_class_get_or_add_metadata_update_info (klass); + g_assert (mono_metadata_token_table (event_token) == MONO_TABLE_EVENT); + GSList *added_events = info->added_events; + + for (GSList *p = added_events; p; p = p->next) { + MonoClassMetadataUpdateEvent *evt = (MonoClassMetadataUpdateEvent*)p->data; + if (evt->token == event_token) + return &evt->evt; + } + return NULL; +} static MonoClassMetadataUpdateField * metadata_update_field_setup_basic_info (MonoImage *image_base, BaselineInfo *base_info, uint32_t generation, DeltaInfo *delta_info, MonoClass *parent_klass, uint32_t fielddef_token, uint32_t field_flags) @@ -2973,6 +3085,137 @@ metadata_update_field_setup_basic_info (MonoImage *image_base, BaselineInfo *bas return field; } +MonoClassMetadataUpdateProperty * +add_property_to_existing_class (MonoImage *image_base, BaselineInfo *base_info, uint32_t generation, DeltaInfo *delta_info, MonoClass *parent_klass, uint32_t property_token, uint32_t property_flags) +{ + if (!m_class_is_inited (parent_klass)) + mono_class_init_internal (parent_klass); + + MonoClassMetadataUpdateInfo *parent_info = mono_class_get_or_add_metadata_update_info (parent_klass); + + MonoClassMetadataUpdateProperty *prop = mono_class_alloc0 (parent_klass, sizeof (MonoClassMetadataUpdateProperty)); + + prop->prop.parent = parent_klass; + + uint32_t name_idx = mono_metadata_decode_table_row_col (image_base, MONO_TABLE_PROPERTY, mono_metadata_token_index (property_token) - 1, MONO_PROPERTY_NAME); + prop->prop.name = mono_metadata_string_heap (image_base, name_idx); + + prop->prop.attrs = property_flags | MONO_PROPERTY_META_FLAG_FROM_UPDATE; + /* get and set will be set when we process the added MethodSemantics rows */ + + + prop->token = property_token; + + parent_info->added_props = g_slist_prepend_mem_manager (m_class_get_mem_manager (parent_klass), parent_info->added_props, prop); + + return prop; + +} + +MonoClassMetadataUpdateEvent * +add_event_to_existing_class (MonoImage *image_base, BaselineInfo *base_info, uint32_t generation, DeltaInfo *delta_info, MonoClass *parent_klass, uint32_t event_token, uint32_t event_flags) +{ + if (!m_class_is_inited (parent_klass)) + mono_class_init_internal (parent_klass); + + MonoClassMetadataUpdateInfo *parent_info = mono_class_get_or_add_metadata_update_info (parent_klass); + + MonoClassMetadataUpdateEvent *evt = mono_class_alloc0 (parent_klass, sizeof (MonoClassMetadataUpdateEvent)); + + evt->evt.parent = parent_klass; + + uint32_t name_idx = mono_metadata_decode_table_row_col (image_base, MONO_TABLE_EVENT, mono_metadata_token_index (event_token) - 1, MONO_EVENT_NAME); + evt->evt.name = mono_metadata_string_heap (image_base, name_idx); + + evt->evt.attrs = event_flags | MONO_EVENT_META_FLAG_FROM_UPDATE; + /* add/remove/raise will be set when we process the added MethodSemantics rows */ + + evt->token = event_token; + + parent_info->added_events = g_slist_prepend_mem_manager (m_class_get_mem_manager (parent_klass), parent_info->added_events, evt); + + return evt; + +} + + +void +add_semantic_method_to_existing_property (MonoImage *image_base, BaselineInfo *base_info, uint32_t semantics, uint32_t klass_token, uint32_t prop_token, uint32_t method_token) +{ + ERROR_DECL (error); + + MonoClass *klass = mono_class_get_checked (image_base, klass_token, error); + mono_error_assert_ok (error); + g_assert (klass); + + MonoProperty *prop = hot_reload_get_property (klass, prop_token); + g_assert (prop != NULL); + + g_assert (m_property_is_from_update (prop)); + + MonoMethod **dest = NULL; + switch (semantics) { + case METHOD_SEMANTIC_GETTER: + dest = &prop->get; + break; + case METHOD_SEMANTIC_SETTER: + dest = &prop->set; + break; + default: + g_error ("EnC: Expected getter or setter semantic but got 0x%08x for method 0x%08x", semantics, method_token); + } + + + g_assert (dest != NULL); + g_assert (*dest == NULL); + + MonoMethod *method = mono_get_method_checked (image_base, method_token, klass, NULL, error); + mono_error_assert_ok (error); + g_assert (method != NULL); + + *dest = method; +} + +void +add_semantic_method_to_existing_event (MonoImage *image_base, BaselineInfo *base_info, uint32_t semantics, uint32_t klass_token, uint32_t event_token, uint32_t method_token) +{ + ERROR_DECL (error); + + MonoClass *klass = mono_class_get_checked (image_base, klass_token, error); + mono_error_assert_ok (error); + g_assert (klass); + + MonoEvent *evt = hot_reload_get_event (klass, event_token); + g_assert (evt != NULL); + + g_assert (m_event_is_from_update (evt)); + + MonoMethod **dest = NULL; + + switch (semantics) { + case METHOD_SEMANTIC_ADD_ON: + dest = &evt->add; + break; + case METHOD_SEMANTIC_REMOVE_ON: + dest = &evt->remove; + break; + case METHOD_SEMANTIC_FIRE: + dest = &evt->raise; + break; + default: + g_error ("EnC: Expected add/remove/raise semantic but got 0x%08x for method 0x%08x", semantics, method_token); + } + + g_assert (dest != NULL); + g_assert (*dest == NULL); + + MonoMethod *method = mono_get_method_checked (image_base, method_token, klass, NULL, error); + mono_error_assert_ok (error); + g_assert (method != NULL); + + *dest = method; +} + static void ensure_class_runtime_info_inited (MonoClass *klass, MonoClassRuntimeMetadataUpdateInfo *runtime_info) { @@ -3234,5 +3477,119 @@ hot_reload_get_method_params (MonoImage *base_image, uint32_t methoddef_token, u static const char * hot_reload_get_capabilities (void) { - return "Baseline AddMethodToExistingType AddStaticFieldToExistingType NewTypeDefinition ChangeCustomAttributes"; + return "Baseline AddMethodToExistingType AddStaticFieldToExistingType NewTypeDefinition ChangeCustomAttributes AddInstanceFieldToExistingType"; +} + +static GENERATE_GET_CLASS_WITH_CACHE_DECL (hot_reload_instance_field_table); + +static GENERATE_GET_CLASS_WITH_CACHE(hot_reload_instance_field_table, "Mono.HotReload", "InstanceFieldTable"); + + +static gpointer +hot_reload_added_field_ldflda (MonoObject *instance, MonoType *field_type, uint32_t fielddef_token, MonoError *error) +{ + static MonoMethod *get_instance_store = NULL; + if (G_UNLIKELY (get_instance_store == NULL)) { + MonoClass *table_class = mono_class_get_hot_reload_instance_field_table_class (); + get_instance_store = mono_class_get_method_from_name_checked (table_class, "GetInstanceFieldFieldStore", 3, 0, error); + mono_error_assert_ok (error); + } + g_assert (get_instance_store); + + gpointer args[3]; + + args[0] = instance; + args[1] = &field_type; + args[2] = &fielddef_token; + + MonoHotReloadFieldStoreObject *field_store; + field_store = (MonoHotReloadFieldStoreObject*) mono_runtime_invoke_checked (get_instance_store, NULL, args, error); + gpointer result = NULL; + /* If it's a value type, return a ptr to the beginning of the + * boxed data in FieldStore:_loc. If it's a reference type, + * return the address of FieldStore:_loc itself. */ + if (!mono_type_is_reference (field_type)) + result = mono_object_unbox_internal (field_store->_loc); + else + result = (gpointer)&field_store->_loc; + return result; +} + +static MonoProperty * +hot_reload_added_properties_iter (MonoClass *klass, gpointer *iter) +{ + MonoClassMetadataUpdateInfo *info = mono_class_get_metadata_update_info (klass); + if (!info) + return NULL; + + GSList *added_props = info->added_props; + + // invariant: idx is one past the field we previously returned. + uint32_t idx = GPOINTER_TO_UINT(*iter); + + MonoClassPropertyInfo *prop_info = mono_class_get_property_info (klass); + g_assert (idx >= prop_info->count); + + uint32_t prop_idx = idx - prop_info->count; + + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "Iterating added properties of 0x%08x idx = %u", m_class_get_type_token (klass), prop_idx); + + GSList *prop_node = g_slist_nth (added_props, prop_idx); + + /* we reached the end, we're done */ + if (!prop_node) + return NULL; + MonoClassMetadataUpdateProperty *prop = (MonoClassMetadataUpdateProperty *)prop_node->data; + + idx++; + *iter = GUINT_TO_POINTER (idx); + return &prop->prop; +} + +uint32_t +hot_reload_get_property_idx (MonoProperty *prop) +{ + g_assert (m_property_is_from_update (prop)); + MonoClassMetadataUpdateProperty *prop_info = (MonoClassMetadataUpdateProperty *)prop; + return mono_metadata_token_index (prop_info->token); +} + +uint32_t +hot_reload_get_event_idx (MonoEvent *evt) +{ + g_assert (m_event_is_from_update (evt)); + MonoClassMetadataUpdateEvent *event_info = (MonoClassMetadataUpdateEvent *)evt; + return mono_metadata_token_index (event_info->token); +} + + +MonoEvent * +hot_reload_added_events_iter (MonoClass *klass, gpointer *iter) +{ + MonoClassMetadataUpdateInfo *info = mono_class_get_metadata_update_info (klass); + if (!info) + return NULL; + + GSList *added_events = info->added_events; + + // invariant: idx is one past the field we previously returned. + uint32_t idx = GPOINTER_TO_UINT(*iter); + + MonoClassEventInfo *event_info = mono_class_get_event_info (klass); + g_assert (idx >= event_info->count); + + uint32_t event_idx = idx - event_info->count; + + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "Iterating added events of 0x%08x idx = %u", m_class_get_type_token (klass), event_idx); + + GSList *event_node = g_slist_nth (added_events, event_idx); + + /* we reached the end, we're done */ + if (!event_node) + return NULL; + MonoClassMetadataUpdateEvent *evt = (MonoClassMetadataUpdateEvent *)event_node->data; + + idx++; + *iter = GUINT_TO_POINTER (idx); + return &evt->evt; } diff --git a/src/mono/mono/component/hot_reload.h b/src/mono/mono/component/hot_reload.h index 40c6a0f263f417..76d7f1ab4bc5ec 100644 --- a/src/mono/mono/component/hot_reload.h +++ b/src/mono/mono/component/hot_reload.h @@ -49,6 +49,11 @@ typedef struct _MonoComponentHotReload { uint32_t (*get_num_methods_added) (MonoClass *klass); const char* (*get_capabilities) (void); uint32_t (*get_method_params) (MonoImage *base_image, uint32_t methoddef_token, uint32_t *out_param_count_opt); + gpointer (*added_field_ldflda) (MonoObject *instance, MonoType *field_type, uint32_t fielddef_token, MonoError *error); + MonoProperty* (*added_properties_iter) (MonoClass *klass, gpointer *iter); + uint32_t (*get_property_idx) (MonoProperty *prop); + MonoEvent* (*added_events_iter) (MonoClass *klass, gpointer *iter); + uint32_t (*get_event_idx) (MonoEvent *evt); } MonoComponentHotReload; MONO_COMPONENT_EXPORT_ENTRYPOINT diff --git a/src/mono/mono/metadata/class-init.c b/src/mono/mono/metadata/class-init.c index 32576a730d6ce6..c396c3a42624cb 100644 --- a/src/mono/mono/metadata/class-init.c +++ b/src/mono/mono/metadata/class-init.c @@ -2273,6 +2273,7 @@ mono_class_layout_fields (MonoClass *klass, int base_instance_size, int packing_ int idx = first_field_idx + i; guint32 offset; mono_metadata_field_info (klass->image, idx, &offset, NULL, NULL); + /* metadata-update: updates to explicit layout classes are not allowed. */ field_offsets [i] = offset + MONO_ABI_SIZEOF (MonoObject); } ftype = mono_type_get_underlying_type (field->type); @@ -3586,6 +3587,11 @@ mono_class_setup_properties (MonoClass *klass) first = ginfo->first; count = ginfo->count; } else { + /* + * metadata-update: note this is only adding properties from the base image. new + * properties added to an existing class won't be here since they're not in the + * contiguous rows [first,last]. + */ first = mono_metadata_properties_from_typedef (klass->image, mono_metadata_token_index (klass->type_token) - 1, &last); g_assert ((last - first) >= 0); count = last - first; diff --git a/src/mono/mono/metadata/class-internals.h b/src/mono/mono/metadata/class-internals.h index d0158da21a67c6..32736a6a980b6b 100644 --- a/src/mono/mono/metadata/class-internals.h +++ b/src/mono/mono/metadata/class-internals.h @@ -1350,13 +1350,13 @@ MONO_COMPONENT_API void mono_class_set_nested_classes_property (MonoClass *klass, GList *value); -MonoClassPropertyInfo* +MONO_COMPONENT_API MonoClassPropertyInfo* mono_class_get_property_info (MonoClass *klass); void mono_class_set_property_info (MonoClass *klass, MonoClassPropertyInfo *info); -MonoClassEventInfo* +MONO_COMPONENT_API MonoClassEventInfo* mono_class_get_event_info (MonoClass *klass); void diff --git a/src/mono/mono/metadata/class.c b/src/mono/mono/metadata/class.c index 9ac6c35553bbd7..3a04a9dee77c41 100644 --- a/src/mono/mono/metadata/class.c +++ b/src/mono/mono/metadata/class.c @@ -2510,6 +2510,13 @@ mono_class_get_field_token (MonoClassField *field) mono_class_setup_fields (klass); + if (G_UNLIKELY (m_class_get_image (klass)->has_updates)) { + if (G_UNLIKELY (m_field_is_from_update (field))) { + uint32_t idx = mono_metadata_update_get_field_idx (field); + return mono_metadata_make_token (MONO_TABLE_FIELD, idx); + } + } + while (klass) { MonoClassField *klass_fields = m_class_get_fields (klass); if (!klass_fields) @@ -2525,10 +2532,6 @@ mono_class_get_field_token (MonoClassField *field) return mono_metadata_make_token (MONO_TABLE_FIELD, idx); } } - if (G_UNLIKELY (m_class_get_image (klass)->has_updates)) { - /* TODO: metadata-update: check if the field was added. */ - g_assert_not_reached (); - } klass = m_class_get_parent (klass); } @@ -2539,6 +2542,7 @@ mono_class_get_field_token (MonoClassField *field) static int mono_field_get_index (MonoClassField *field) { + /* metadata-update: the callers of this method need changes to support updates */ g_assert (!m_field_is_from_update (field)); int index = GPTRDIFF_TO_INT (field - m_class_get_fields (m_field_get_parent (field))); g_assert (index >= 0 && GINT_TO_UINT32(index) < mono_class_get_field_count (m_field_get_parent (field))); @@ -2569,6 +2573,9 @@ mono_class_get_field_default_value (MonoClassField *field, MonoTypeEnum *def_typ mono_class_set_field_def_values (klass, def_values); } + /* TODO: metadata-update - added literal fields */ + g_assert (!m_field_is_from_update (field)); + field_index = mono_field_get_index (field); if (!def_values [field_index].data) { @@ -2592,6 +2599,7 @@ mono_class_get_field_default_value (MonoClassField *field, MonoTypeEnum *def_typ static int mono_property_get_index (MonoProperty *prop) { + g_assert (!m_property_is_from_update (prop)); MonoClassPropertyInfo *info = mono_class_get_property_info (prop->parent); int index = GPTRDIFF_TO_INT (prop - info->properties); @@ -2629,6 +2637,8 @@ mono_class_get_property_default_value (MonoProperty *property, MonoTypeEnum *def } return NULL; } + /* metadata-update: Roslyn doesn't emit HasDefault on added properties. */ + g_assert (!m_property_is_from_update (property)); cindex = mono_metadata_get_constant_index (klass_image, mono_class_get_property_token (property), 0); if (!cindex) return NULL; @@ -2646,11 +2656,17 @@ mono_class_get_event_token (MonoEvent *event) { MonoClass *klass = event->parent; + if (G_UNLIKELY (m_class_get_image (klass)->has_updates)) { + if (G_UNLIKELY (m_event_is_from_update (event))) { + uint32_t idx = mono_metadata_update_get_event_idx (event); + return mono_metadata_make_token (MONO_TABLE_EVENT, idx); + } + } + while (klass) { MonoClassEventInfo *info = mono_class_get_event_info (klass); if (info) { for (guint32 i = 0; i < info->count; ++i) { - /* TODO: metadata-update: get tokens for added props, too */ g_assert (!m_event_is_from_update (&info->events[i])); if (&info->events [i] == event) return mono_metadata_make_token (MONO_TABLE_EVENT, info->first + i + 1); @@ -2689,6 +2705,14 @@ guint32 mono_class_get_property_token (MonoProperty *prop) { MonoClass *klass = prop->parent; + + if (G_UNLIKELY (m_class_get_image (klass)->has_updates)) { + if (G_UNLIKELY (m_property_is_from_update (prop))) { + uint32_t idx = mono_metadata_update_get_property_idx (prop); + return mono_metadata_make_token (MONO_TABLE_PROPERTY, idx); + } + } + while (klass) { MonoProperty* p; int i = 0; @@ -5207,7 +5231,6 @@ mono_class_get_methods (MonoClass* klass, gpointer *iter) MonoProperty* mono_class_get_properties (MonoClass* klass, gpointer *iter) { - MonoProperty* property; if (!iter) return NULL; if (!*iter) { @@ -5215,19 +5238,28 @@ mono_class_get_properties (MonoClass* klass, gpointer *iter) MonoClassPropertyInfo *info = mono_class_get_property_info (klass); /* start from the first */ if (info->count) { - *iter = &info->properties [0]; - return (MonoProperty *)*iter; + uint32_t idx = 0; + *iter = GUINT_TO_POINTER (idx + 1); + return (MonoProperty *)&info->properties [0]; } else { /* no fields */ - return NULL; + if (G_LIKELY (!m_class_get_image (klass)->has_updates)) + return NULL; + else + *iter = 0; } } - property = (MonoProperty *)*iter; - property++; + // invariant: idx is one past the field we previously returned + uint32_t idx = GPOINTER_TO_UINT (*iter); MonoClassPropertyInfo *info = mono_class_get_property_info (klass); - if (property < &info->properties [info->count]) { - *iter = property; - return (MonoProperty *)*iter; + if (idx < info->count) { + MonoProperty *property = &info->properties [idx]; + ++idx; + *iter = GUINT_TO_POINTER (idx); + return property; + } + if (G_UNLIKELY (m_class_get_image (klass)->has_updates)) { + return mono_metadata_update_added_properties_iter (klass, iter); } return NULL; } @@ -5247,7 +5279,6 @@ mono_class_get_properties (MonoClass* klass, gpointer *iter) MonoEvent* mono_class_get_events (MonoClass* klass, gpointer *iter) { - MonoEvent* event; if (!iter) return NULL; if (!*iter) { @@ -5255,19 +5286,28 @@ mono_class_get_events (MonoClass* klass, gpointer *iter) MonoClassEventInfo *info = mono_class_get_event_info (klass); /* start from the first */ if (info->count) { - *iter = &info->events [0]; - return (MonoEvent *)*iter; + uint32_t idx = 0; + *iter = GUINT_TO_POINTER (idx + 1); + return (MonoEvent *)&info->events [0]; } else { /* no fields */ - return NULL; + if (G_LIKELY (!m_class_get_image (klass)->has_updates)) + return NULL; + else + *iter = 0; } } - event = (MonoEvent *)*iter; - event++; + // invariant: idx is one past the event we previously returned + uint32_t idx = GPOINTER_TO_UINT (*iter); MonoClassEventInfo *info = mono_class_get_event_info (klass); - if (event < &info->events [info->count]) { - *iter = event; - return (MonoEvent *)*iter; + if (idx < info->count) { + MonoEvent *event = &info->events[idx]; + ++idx; + *iter = GUINT_TO_POINTER (idx); + return event; + } + if (G_UNLIKELY (m_class_get_image (klass)->has_updates)) { + return mono_metadata_update_added_events_iter (klass, iter); } return NULL; } @@ -5547,7 +5587,7 @@ mono_field_get_rva (MonoClassField *field, int swizzle) g_assert (field->type->attrs & FIELD_ATTRIBUTE_HAS_FIELD_RVA); - /* TODO: metadata-update: make this work. */ + /* metadata-update: added static fields with initializers don't seem to get here */ g_assert (!m_field_is_from_update (field)); def_values = mono_class_get_field_def_values_with_swizzle (klass, swizzle); @@ -6605,13 +6645,13 @@ static guint32 mono_field_resolve_flags (MonoClassField *field) { if (G_UNLIKELY (m_field_is_from_update (field))) { - /* metadata-update: Just resolve the whole field, for simplicity. */ - ERROR_DECL (error); - mono_field_resolve_type (field, error); - mono_error_assert_ok (error); - g_assert (field->type); - return field->type->attrs; - } + /* metadata-update: Just resolve the whole field, for simplicity. */ + ERROR_DECL (error); + mono_field_resolve_type (field, error); + mono_error_assert_ok (error); + g_assert (field->type); + return field->type->attrs; + } MonoClass *klass = m_field_get_parent (field); MonoImage *image = m_class_get_image (klass); diff --git a/src/mono/mono/metadata/custom-attrs.c b/src/mono/mono/metadata/custom-attrs.c index d8fda30d4ce6eb..46d8f54b94dec6 100644 --- a/src/mono/mono/metadata/custom-attrs.c +++ b/src/mono/mono/metadata/custom-attrs.c @@ -169,6 +169,8 @@ find_field_index (MonoClass *klass, MonoClassField *field) { static guint32 find_property_index (MonoClass *klass, MonoProperty *property) { + if (G_UNLIKELY (m_property_is_from_update (property))) + return mono_metadata_update_get_property_idx (property); MonoClassPropertyInfo *info = mono_class_get_property_info (klass); for (guint32 i = 0; i < info->count; ++i) { @@ -184,6 +186,8 @@ find_property_index (MonoClass *klass, MonoProperty *property) static guint32 find_event_index (MonoClass *klass, MonoEvent *event) { + if (G_UNLIKELY (m_event_is_from_update (event))) + return mono_metadata_update_get_event_idx (event); MonoClassEventInfo *info = mono_class_get_event_info (klass); for (guint32 i = 0; i < info->count; ++i) { @@ -360,7 +364,7 @@ load_cattr_value (MonoImage *image, MonoType *t, MonoObject **out_obj, const cha type = mono_class_enum_basetype_internal (t->data.klass)->type; goto handle_enum; } else { - MonoClass *k = t->data.klass; + MonoClass *k = t->data.klass; if (mono_is_corlib_image (m_class_get_image (k)) && strcmp (m_class_get_name_space (k), "System") == 0 && strcmp (m_class_get_name (k), "DateTime") == 0){ guint64 *val = (guint64 *)g_malloc (sizeof (guint64)); @@ -700,7 +704,7 @@ load_cattr_value_noalloc (MonoImage *image, MonoType *t, const char *p, const ch type = mono_class_enum_basetype_internal (t->data.klass)->type; goto handle_enum; } else { - MonoClass *k = t->data.klass; + MonoClass *k = t->data.klass; if (mono_is_corlib_image (m_class_get_image (k)) && strcmp (m_class_get_name_space (k), "System") == 0 && strcmp (m_class_get_name (k), "DateTime") == 0){ guint64 *val = (guint64 *)g_malloc (sizeof (guint64)); @@ -2592,7 +2596,7 @@ mono_reflection_get_custom_attrs_data_checked (MonoObjectHandle obj, MonoError * if (!cinfo->cached) mono_custom_attrs_free (cinfo); goto_if_nok (error, leave); - } else { + } else { MonoClass *cattr_data = try_get_cattr_data_class (error); goto_if_nok (error, return_null); @@ -2918,6 +2922,8 @@ init_weak_fields_inner (MonoImage *image, GHashTable *indexes) } } else { /* FIXME: metadata-update */ + if (G_UNLIKELY (image->has_updates)) + g_warning ("[WeakAttribute] ignored on fields added by hot reload in '%s'", image->name); /* Memberref pointing to a typeref */ tdef = &image->tables [MONO_TABLE_MEMBERREF]; diff --git a/src/mono/mono/metadata/handle.h b/src/mono/mono/metadata/handle.h index e18ce15c210d36..2d9b709be1dba6 100644 --- a/src/mono/mono/metadata/handle.h +++ b/src/mono/mono/metadata/handle.h @@ -487,28 +487,6 @@ This is why we evaluate index and value before any call to MONO_HANDLE_RAW or ot #define mono_handle_domain(handle) MONO_HANDLE_DOMAIN ((handle)) -/* Given an object and a MonoClassField, return the value (must be non-object) - * of the field. It's the caller's responsibility to check that the object is - * of the correct class. */ -#define MONO_HANDLE_GET_FIELD_VAL(HANDLE,TYPE,FIELD) (*(TYPE *)(mono_handle_unsafe_field_addr (MONO_HANDLE_CAST (MonoObject, (HANDLE)), (FIELD)))) -#define MONO_HANDLE_GET_FIELD_BOOL(handle, type, field) (MONO_BOOL (MONO_HANDLE_GET_FIELD_VAL ((handle), type, (field)))) - -#define MONO_HANDLE_NEW_GET_FIELD(HANDLE,TYPE,FIELD) MONO_HANDLE_NEW (TYPE, MONO_HANDLE_SUPPRESS (*(TYPE**)(mono_handle_unsafe_field_addr (MONO_HANDLE_CAST (MonoObject, MONO_HANDLE_UNSUPPRESS (HANDLE)), (FIELD))))) - -#define MONO_HANDLE_SET_FIELD_VAL(HANDLE,TYPE,FIELD,VAL) do { \ - MonoObjectHandle __obj = (HANDLE); \ - MonoClassField *__field = (FIELD); \ - TYPE __value = (VAL); \ - *(TYPE*)(mono_handle_unsafe_field_addr (__obj, __field)) = __value; \ - } while (0) - -#define MONO_HANDLE_SET_FIELD_REF(HANDLE,FIELD,VALH) do { \ - MonoObjectHandle __obj = MONO_HANDLE_CAST (MonoObject, (HANDLE)); \ - MonoClassField *__field = (FIELD); \ - MonoObjectHandle __value = MONO_HANDLE_CAST (MonoObject, (VALH)); \ - MONO_HANDLE_SUPPRESS (mono_gc_wbarrier_generic_store_internal (mono_handle_unsafe_field_addr (__obj, __field), MONO_HANDLE_RAW (__value))); \ - } while (0) - #define MONO_HANDLE_GET_CLASS(handle) (MONO_HANDLE_GETVAL (MONO_HANDLE_CAST (MonoObject, (handle)), vtable)->klass) /* Baked typed handles we all want */ @@ -603,15 +581,6 @@ mono_handle_assign_raw (MonoObjectHandleOut dest, void *src) return dest; } -/* It is unsafe to call this function directly - it does not pin the handle! Use MONO_HANDLE_GET_FIELD_VAL(). */ -static inline gpointer -mono_handle_unsafe_field_addr (MonoObjectHandle h, MonoClassField *field) -{ - /* TODO: metadata-update: fix all callers */ - g_assert (!m_field_is_from_update (field)); - return MONO_HANDLE_SUPPRESS (((gchar *)MONO_HANDLE_RAW (h)) + field->offset); -} - /* Matches ObjectHandleOnStack in managed code */ typedef MonoObject **MonoObjectHandleOnStack; diff --git a/src/mono/mono/metadata/icall.c b/src/mono/mono/metadata/icall.c index 8c6c7329042b0c..a80047aaec4ec5 100644 --- a/src/mono/mono/metadata/icall.c +++ b/src/mono/mono/metadata/icall.c @@ -2074,7 +2074,7 @@ ves_icall_RuntimeFieldInfo_GetFieldOffset (MonoReflectionFieldHandle field, Mono MonoClassField *class_field = MONO_HANDLE_GETVAL (field, field); mono_class_setup_fields (m_field_get_parent (class_field)); - /* TODO: metadata-update: figure out what CoreCLR does in this situation. */ + /* metadata-update: mono only calls this for ExplicitLayout types */ g_assert (!m_field_is_from_update (class_field)); return m_field_get_offset (class_field) - MONO_ABI_SIZEOF (MonoObject); @@ -2193,9 +2193,19 @@ ves_icall_RuntimeFieldInfo_SetValueInternal (MonoReflectionFieldHandle field, Mo mono_field_static_set_value_internal (vtable, cf, v); } else { - if (isref) - MONO_HANDLE_SET_FIELD_REF (obj, cf, value); - else + if (isref) { + MonoObject *obj_ptr = MONO_HANDLE_RAW (obj); + MonoObject *value_ptr = MONO_HANDLE_RAW (value); + gpointer *dest; + if (G_LIKELY (!m_field_is_from_update (cf))) { + dest = (gpointer*)(((char *)obj_ptr) + m_field_get_offset (cf)); + } else { + uint32_t token = mono_metadata_make_token (MONO_TABLE_FIELD, mono_metadata_update_get_field_idx (cf)); + dest = mono_metadata_update_added_field_ldflda (obj_ptr, cf->type, token, error); + mono_error_assert_ok (error); + } + mono_gc_wbarrier_generic_store_internal (dest, value_ptr); + } else mono_field_set_value_internal (MONO_HANDLE_RAW (obj), cf, v); /* FIXME: make mono_field_set_value take a handle for obj */ } leave: @@ -2226,15 +2236,18 @@ ves_icall_System_RuntimeFieldHandle_GetValueDirect (MonoReflectionFieldHandle fi MonoClassField *field = MONO_HANDLE_GETVAL (field_h, field); MonoClass *klass = mono_class_from_mono_type_internal (field->type); - /* TODO: metadata-update: get the values of added fields */ - g_assert (!m_field_is_from_update (field)); if (!MONO_TYPE_ISSTRUCT (m_class_get_byval_arg (m_field_get_parent (field)))) { - mono_error_set_not_implemented (error, ""); - return MONO_HANDLE_NEW (MonoObject, NULL); + MonoObjectHandle objHandle = typed_reference_to_object (obj, error); + return_val_if_nok (error, MONO_HANDLE_NEW (MonoObject, NULL)); + return ves_icall_RuntimeFieldInfo_GetValueInternal (field_h, objHandle, error); } else if (MONO_TYPE_IS_REFERENCE (field->type)) { + /* metadata-update: can't add fields to structs */ + g_assert (!m_field_is_from_update (field)); return MONO_HANDLE_NEW (MonoObject, *(MonoObject**)((guint8*)obj->value + m_field_get_offset (field) - sizeof (MonoObject))); } else { + /* metadata-update can't add fields to structs */ + g_assert (!m_field_is_from_update (field)); return mono_value_box_handle (klass, (guint8*)obj->value + m_field_get_offset (field) - sizeof (MonoObject), error); } } @@ -2248,16 +2261,17 @@ ves_icall_System_RuntimeFieldHandle_SetValueDirect (MonoReflectionFieldHandle fi mono_class_setup_fields (m_field_get_parent (f)); - /* TODO: metadata-update: set the values of added fields */ - g_assert (!m_field_is_from_update (f)); - if (!MONO_TYPE_ISSTRUCT (m_class_get_byval_arg (m_field_get_parent (f)))) { MonoObjectHandle objHandle = typed_reference_to_object (obj, error); return_if_nok (error); ves_icall_RuntimeFieldInfo_SetValueInternal (field_h, objHandle, value_h, error); } else if (MONO_TYPE_IS_REFERENCE (f->type)) { + /* metadata-update: can't add fields to structs */ + g_assert (!m_field_is_from_update (f)); mono_copy_value (f->type, (guint8*)obj->value + m_field_get_offset (f) - sizeof (MonoObject), MONO_HANDLE_RAW (value_h), FALSE); } else { + /* metadata-update: can't add fields to structs */ + g_assert (!m_field_is_from_update (f)); MonoGCHandle gchandle = NULL; g_assert (MONO_HANDLE_RAW (value_h)); mono_copy_value (f->type, (guint8*)obj->value + m_field_get_offset (f) - sizeof (MonoObject), mono_object_handle_pin_unbox (value_h, &gchandle), FALSE); @@ -6349,27 +6363,42 @@ ves_icall_System_TypedReference_InternalMakeTypedReference (MonoTypedRef *res, M (void)mono_handle_class (target); - int offset = 0; + /* if relative, offset is from the start of target. Otherwise offset is actually an address */ + gboolean relative = TRUE; + intptr_t offset = 0; for (guint i = 0; i < mono_array_handle_length (fields); ++i) { MonoClassField *f; MONO_HANDLE_ARRAY_GETVAL (f, fields, MonoClassField*, i); g_assert (f); - /* TODO: metadata-update: the first field might be added, right? the rest are inside structs */ - g_assert (!m_field_is_from_update (f)); - - if (i == 0) - offset = m_field_get_offset (f); - else + if (i == 0) { + if (G_LIKELY (!m_field_is_from_update (f))) + offset = m_field_get_offset (f); + else { + /* The first field was added by a metadata-update to an exsiting type. + * Since it's store outside the object, offset is an absolute address + */ + relative = FALSE; + uint32_t token = mono_metadata_make_token (MONO_TABLE_FIELD, mono_metadata_update_get_field_idx (f)); + offset = (intptr_t) mono_metadata_update_added_field_ldflda (MONO_HANDLE_RAW (target), f->type, token, error); + mono_error_assert_ok (error); + } + } else { + /* metadata-update: the first field might be added, the rest are inside structs */ + g_assert (!m_field_is_from_update (f)); offset += m_field_get_offset (f) - sizeof (MonoObject); + } (void)mono_class_from_mono_type_internal (f->type); ftype = f->type; } res->type = ftype; res->klass = mono_class_from_mono_type_internal (ftype); - res->value = (guint8*)MONO_HANDLE_RAW (target) + offset; + if (G_LIKELY (relative)) + res->value = (guint8*)MONO_HANDLE_RAW (target) + offset; + else + res->value = (guint8*)offset; } void @@ -6538,6 +6567,9 @@ ves_icall_property_info_get_default_value (MonoReflectionPropertyHandle property return NULL_HANDLE; } + /* metadata-update: looks like Roslyn doesn't set the HasDefault attribute for updates */ + g_assert (!m_property_is_from_update (prop)); + def_value = mono_class_get_property_default_value (prop, &def_type); mono_type_from_blob_type (&blob_type, def_type, type); diff --git a/src/mono/mono/metadata/metadata-update.c b/src/mono/mono/metadata/metadata-update.c index 9541a25384c00a..eda27cc1c8a62a 100644 --- a/src/mono/mono/metadata/metadata-update.c +++ b/src/mono/mono/metadata/metadata-update.c @@ -236,3 +236,34 @@ mono_metadata_update_get_method_params (MonoImage *image, uint32_t methoddef_tok { return mono_component_hot_reload()->get_method_params (image, methoddef_token, out_param_count_opt); } + +gpointer +mono_metadata_update_added_field_ldflda (MonoObject *instance, MonoType *field_type, uint32_t fielddef_token, MonoError *error) +{ + return mono_component_hot_reload()->added_field_ldflda (instance, field_type, fielddef_token, error); +} + + +MonoProperty * +mono_metadata_update_added_properties_iter (MonoClass *klass, gpointer *iter) +{ + return mono_component_hot_reload()->added_properties_iter (klass, iter); +} + +uint32_t +mono_metadata_update_get_property_idx (MonoProperty *prop) +{ + return mono_component_hot_reload()->get_property_idx (prop); +} + +MonoEvent * +mono_metadata_update_added_events_iter (MonoClass *klass, gpointer *iter) +{ + return mono_component_hot_reload()->added_events_iter (klass, iter); +} + +uint32_t +mono_metadata_update_get_event_idx (MonoEvent *evt) +{ + return mono_component_hot_reload()->get_event_idx (evt); +} diff --git a/src/mono/mono/metadata/metadata-update.h b/src/mono/mono/metadata/metadata-update.h index e68ca1a8ad71ae..4e45c60439b5c3 100644 --- a/src/mono/mono/metadata/metadata-update.h +++ b/src/mono/mono/metadata/metadata-update.h @@ -64,7 +64,7 @@ mono_metadata_update_metadata_linear_search (MonoImage *base_image, MonoTableInf MonoMethod* mono_metadata_update_find_method_by_name (MonoClass *klass, const char *name, int param_count, int flags, MonoError *error); -uint32_t +MONO_COMPONENT_API uint32_t mono_metadata_update_get_field_idx (MonoClassField *field); MonoClassField * @@ -96,4 +96,21 @@ mono_metadata_update_get_num_methods_added (MonoClass *klass); uint32_t mono_metadata_update_get_method_params (MonoImage *image, uint32_t methoddef_token, uint32_t *out_param_count_opt); + +MONO_COMPONENT_API gpointer +mono_metadata_update_added_field_ldflda (MonoObject *instance, MonoType *field_type, uint32_t fielddef_token, MonoError *error); + +MonoProperty * +mono_metadata_update_added_properties_iter (MonoClass *klass, gpointer *iter); + +uint32_t +mono_metadata_update_get_property_idx (MonoProperty *prop); + +MonoEvent * +mono_metadata_update_added_events_iter (MonoClass *klass, gpointer *iter); + +uint32_t +mono_metadata_update_get_event_idx (MonoEvent *event); + + #endif /*__MONO_METADATA_UPDATE_H__*/ diff --git a/src/mono/mono/metadata/metadata.c b/src/mono/mono/metadata/metadata.c index 0c63c068929173..3683a42488b41e 100644 --- a/src/mono/mono/metadata/metadata.c +++ b/src/mono/mono/metadata/metadata.c @@ -6180,7 +6180,7 @@ mono_metadata_field_info_full (MonoImage *meta, guint32 index, guint32 *offset, MonoMarshalSpec **marshal_spec, gboolean alloc_from_image) { MonoTableInfo *tdef; - locator_t loc; + locator_t loc = {0,}; loc.idx = index + 1; if (meta->uncompressed_metadata) @@ -6192,7 +6192,7 @@ mono_metadata_field_info_full (MonoImage *meta, guint32 index, guint32 *offset, loc.col_idx = MONO_FIELD_LAYOUT_FIELD; loc.t = tdef; - /* FIXME: metadata-update */ + /* metadata-update: explicit layout not supported, just return -1 */ if (tdef->base && mono_binary_search (&loc, tdef->base, table_info_get_rows (tdef), tdef->row_size, table_locator)) { *offset = mono_metadata_decode_row_col (tdef, loc.result, MONO_FIELD_LAYOUT_OFFSET); @@ -6206,7 +6206,14 @@ mono_metadata_field_info_full (MonoImage *meta, guint32 index, guint32 *offset, loc.col_idx = MONO_FIELD_RVA_FIELD; loc.t = tdef; - if (tdef->base && mono_binary_search (&loc, tdef->base, table_info_get_rows (tdef), tdef->row_size, table_locator)) { + gboolean found = tdef->base && mono_binary_search (&loc, tdef->base, table_info_get_rows (tdef), tdef->row_size, table_locator); + + if (G_UNLIKELY (meta->has_updates)) { + if (!found) + found = (mono_metadata_update_metadata_linear_search (meta, tdef, &loc, table_locator) != NULL); + } + + if (found) { /* * LAMESPEC: There is no signature, no nothing, just the raw data. */ @@ -6320,6 +6327,12 @@ mono_metadata_events_from_typedef (MonoImage *meta, guint32 index, guint *end_id } start = mono_metadata_decode_row_col (tdef, loc.result, MONO_EVENT_MAP_EVENTLIST); + /* + * metadata-update: note this next line needs block needs to look at the number of rows in + * EventMap and Event of the base image. Updates will add rows for new properties, + * but they won't be contiguous. if we set end to the number of rows in the updated + * Property table, the range will include properties from some other class + */ if (loc.result + 1 < table_info_get_rows (tdef)) { end = mono_metadata_decode_row_col (tdef, loc.result + 1, MONO_EVENT_MAP_EVENTLIST) - 1; } else { @@ -6433,10 +6446,16 @@ mono_metadata_properties_from_typedef (MonoImage *meta, guint32 index, guint *en } start = mono_metadata_decode_row_col (tdef, loc.result, MONO_PROPERTY_MAP_PROPERTY_LIST); - if (loc.result + 1 < mono_metadata_table_num_rows (meta, MONO_TABLE_PROPERTYMAP)) { + /* + * metadata-update: note this next line needs block needs to look at the number of rows in + * PropertyMap and Property of the base image. Updates will add rows for new properties, + * but they won't be contiguous. if we set end to the number of rows in the updated + * Property table, the range will include properties from some other class + */ + if (loc.result + 1 < table_info_get_rows (&meta->tables [MONO_TABLE_PROPERTYMAP])) { end = mono_metadata_decode_row_col (tdef, loc.result + 1, MONO_PROPERTY_MAP_PROPERTY_LIST) - 1; } else { - end = mono_metadata_table_num_rows (meta, MONO_TABLE_PROPERTY); + end = table_info_get_rows (&meta->tables [MONO_TABLE_PROPERTY]); } *end_idx = GUINT32_TO_UINT(end); @@ -6939,21 +6958,21 @@ mono_type_to_unmanaged (MonoType *type, MonoMarshalSpec *mspec, gboolean as_fiel const char* mono_metadata_get_marshal_info (MonoImage *meta, guint32 idx, gboolean is_field) { - locator_t loc; + locator_t loc = {0,}; MonoTableInfo *tdef = &meta->tables [MONO_TABLE_FIELDMARSHAL]; - if (!tdef->base) - return NULL; - loc.t = tdef; loc.col_idx = MONO_FIELD_MARSHAL_PARENT; loc.idx = ((idx + 1) << MONO_HAS_FIELD_MARSHAL_BITS) | (is_field? MONO_HAS_FIELD_MARSHAL_FIELDSREF: MONO_HAS_FIELD_MARSHAL_PARAMDEF); - /* FIXME: metadata-update */ /* FIXME: Index translation */ - if (!mono_binary_search (&loc, tdef->base, table_info_get_rows (tdef), tdef->row_size, table_locator)) - return NULL; + gboolean found = tdef->base && mono_binary_search (&loc, tdef->base, table_info_get_rows (tdef), tdef->row_size, table_locator); + + if (G_UNLIKELY (meta->has_updates)) { + if (!found && !mono_metadata_update_metadata_linear_search (meta, tdef, &loc, table_locator)) + return NULL; + } return mono_metadata_blob_heap (meta, mono_metadata_decode_row_col (tdef, loc.result, MONO_FIELD_MARSHAL_NATIVE_TYPE)); } diff --git a/src/mono/mono/metadata/object.c b/src/mono/mono/metadata/object.c index 6e8e933bce6357..fa0b9bf99710d2 100644 --- a/src/mono/mono/metadata/object.c +++ b/src/mono/mono/metadata/object.c @@ -2816,10 +2816,14 @@ mono_field_set_value_internal (MonoObject *obj, MonoClassField *field, void *val if ((field->type->attrs & FIELD_ATTRIBUTE_STATIC)) return; - /* TODO: metadata-update: implement support for added fields */ - g_assert (!m_field_is_from_update (field)); + if (G_UNLIKELY (m_field_is_from_update (field))) { + ERROR_DECL (error); + uint32_t token = mono_metadata_make_token (MONO_TABLE_FIELD, mono_metadata_update_get_field_idx (field)); + dest = mono_metadata_update_added_field_ldflda (obj, field->type, token, error); + mono_error_assert_ok (error); + } else + dest = (char*)obj + m_field_get_offset (field); - dest = (char*)obj + m_field_get_offset (field); mono_copy_value (field->type, dest, value, value && field->type->type == MONO_TYPE_PTR); } @@ -2908,9 +2912,15 @@ mono_field_get_addr (MonoObject *obj, MonoVTable *vt, MonoClassField *field) if (field->type->attrs & FIELD_ATTRIBUTE_STATIC) return mono_static_field_get_addr (vt, field); else { - /* TODO: metadata-update: implement support for added fields */ - g_assert (!m_field_is_from_update (field)); - return (guint8*)obj + m_field_get_offset (field); + if (G_LIKELY (!m_field_is_from_update (field))) + return (guint8*)obj + m_field_get_offset (field); + else { + ERROR_DECL (error); + uint32_t token = mono_metadata_make_token (MONO_TABLE_FIELD, mono_metadata_update_get_field_idx (field)); + gpointer addr = mono_metadata_update_added_field_ldflda (obj, field->type, token, error); + mono_error_assert_ok (error); + return addr; + } } } @@ -2975,10 +2985,14 @@ mono_field_get_value_internal (MonoObject *obj, MonoClassField *field, void *val g_return_if_fail (!(field->type->attrs & FIELD_ATTRIBUTE_STATIC)); - /* TODO: metadata-update: implement me */ - g_assert (!m_field_is_from_update (field)); - - src = (char*)obj + m_field_get_offset (field); + if (G_UNLIKELY (m_field_is_from_update (field))) { + ERROR_DECL (error); + uint32_t token = mono_metadata_make_token (MONO_TABLE_FIELD, mono_metadata_update_get_field_idx (field)); + src = mono_metadata_update_added_field_ldflda (obj, field->type, token, error); + mono_error_assert_ok (error); + } else { + src = (char*)obj + m_field_get_offset (field); + } mono_copy_value (field->type, value, src, TRUE); } @@ -7854,6 +7868,7 @@ mono_class_value_size (MonoClass *klass, guint32 *align) gpointer mono_vtype_get_field_addr (gpointer vtype, MonoClassField *field) { + /* metadata-update: no added fields in valuetypes */ g_assert (!m_field_is_from_update (field)); return ((char*)vtype) + m_field_get_offset (field) - MONO_ABI_SIZEOF (MonoObject); } diff --git a/src/mono/mono/metadata/reflection.c b/src/mono/mono/metadata/reflection.c index 52107a74225441..57a7c58ccda822 100644 --- a/src/mono/mono/metadata/reflection.c +++ b/src/mono/mono/metadata/reflection.c @@ -2502,7 +2502,8 @@ mono_reflection_get_token_checked (MonoObjectHandle obj, MonoError *error) } else if (strcmp (klass_name, "RuntimePropertyInfo") == 0) { MonoReflectionPropertyHandle p = MONO_HANDLE_CAST (MonoReflectionProperty, obj); - token = mono_class_get_property_token (MONO_HANDLE_GETVAL (p, property)); + MonoProperty *prop = MONO_HANDLE_GETVAL (p, property); + token = mono_class_get_property_token (prop); } else if (strcmp (klass_name, "RuntimeEventInfo") == 0) { MonoReflectionMonoEventHandle p = MONO_HANDLE_CAST (MonoReflectionMonoEvent, obj); diff --git a/src/mono/mono/mini/aot-compiler.c b/src/mono/mono/mini/aot-compiler.c index f7fdc9e9771d75..75249bea3ad1c6 100644 --- a/src/mono/mono/mini/aot-compiler.c +++ b/src/mono/mono/mini/aot-compiler.c @@ -3011,6 +3011,7 @@ mono_get_field_token (MonoClassField *field) MonoClass *klass = m_field_get_parent (field); int i; + /* metadata-update: there are no updates during AOT */ g_assert (!m_field_is_from_update (field)); int fcount = mono_class_get_field_count (klass); MonoClassField *klass_fields = m_class_get_fields (klass); diff --git a/src/mono/mono/mini/interp/interp.c b/src/mono/mono/mini/interp/interp.c index d25eb77b72e826..e6284680a2cd40 100644 --- a/src/mono/mono/mini/interp/interp.c +++ b/src/mono/mono/mini/interp/interp.c @@ -7504,7 +7504,19 @@ MINT_IN_CASE(MINT_BRTRUE_I8_SP) ZEROP_SP(gint64, !=); MINT_IN_BREAK; ip += 3; MINT_IN_BREAK; } - + MINT_IN_CASE(MINT_METADATA_UPDATE_LDFLDA) { + MonoObject *inst = LOCAL_VAR (ip [2], MonoObject*); + MonoType *field_type = frame->imethod->data_items [ip [3]]; + uint32_t fielddef_token = GPOINTER_TO_UINT32 (frame->imethod->data_items [ip [4]]); + // FIXME: can we emit a call directly instead of a runtime-invoke? + gpointer field_addr = mono_metadata_update_added_field_ldflda (inst, field_type, fielddef_token, error); + /* FIXME: think about pinning the FieldStore and adding a second opcode to + * unpin it */ + LOCAL_VAR (ip [1], gpointer) = field_addr; + mono_interp_error_cleanup (error); + ip += 5; + MINT_IN_BREAK; + } #ifdef HOST_BROWSER MINT_IN_CASE(MINT_TIER_NOP_JITERPRETER) { ip += 3; diff --git a/src/mono/mono/mini/interp/mintops.def b/src/mono/mono/mini/interp/mintops.def index 8fa0c024a41d4e..b2a40b413b9f25 100644 --- a/src/mono/mono/mini/interp/mintops.def +++ b/src/mono/mono/mini/interp/mintops.def @@ -800,6 +800,8 @@ OPDEF(MINT_INTRINS_64ORDINAL_IGNORE_CASE_ASCII, "intrins_64ordinal_ignore_case_a OPDEF(MINT_INTRINS_U32_TO_DECSTR, "intrins_u32_to_decstr", 5, 1, 1, MintOpTwoShorts) OPDEF(MINT_INTRINS_WIDEN_ASCII_TO_UTF16, "intrins_widen_ascii_to_utf16", 5, 1, 3, MintOpNoArgs) +OPDEF(MINT_METADATA_UPDATE_LDFLDA, "metadata_update.ldflda", 5, 1, 1, MintOpTwoShorts) + // TODO: Make this wasm only OPDEF(MINT_TIER_PREPARE_JITERPRETER, "tier_prepare_jiterpreter", 3, 0, 0, MintOpInt) OPDEF(MINT_TIER_NOP_JITERPRETER, "tier_nop_jiterpreter", 3, 0, 0, MintOpInt) diff --git a/src/mono/mono/mini/interp/transform.c b/src/mono/mono/mini/interp/transform.c index 89a611b1713764..d7d71ac3b6ea27 100644 --- a/src/mono/mono/mini/interp/transform.c +++ b/src/mono/mono/mini/interp/transform.c @@ -1919,6 +1919,27 @@ interp_emit_ldelema (TransformData *td, MonoClass *array_class, MonoClass *check interp_ins_set_dreg (td->last_ins, td->sp [-1].local); } +static void +interp_emit_metadata_update_ldflda (TransformData *td, MonoClassField *field, MonoError *error) +{ + g_assert (m_field_is_from_update (field)); + MonoType *field_type = field->type; + g_assert (!m_type_is_byref (field_type)); + MonoClass *field_klass = mono_class_from_mono_type_internal (field_type); + /* get a heap-allocated version of the field type */ + field_type = m_class_get_byval_arg (field_klass); + guint32 field_token = mono_metadata_make_token (MONO_TABLE_FIELD, mono_metadata_update_get_field_idx (field)); + + interp_add_ins (td, MINT_METADATA_UPDATE_LDFLDA); + td->sp--; + interp_ins_set_sreg (td->last_ins, td->sp [0].local); + push_simple_type (td, STACK_TYPE_MP); + interp_ins_set_dreg (td->last_ins, td->sp [-1].local); + td->last_ins->data [0] = get_data_item_index (td, field_type); + td->last_ins->data [1] = get_data_item_index (td, GUINT_TO_POINTER (field_token)); +} + + /* Return TRUE if call transformation is finished */ static gboolean interp_handle_intrinsics (TransformData *td, MonoMethod *target_method, MonoClass *constrained_class, MonoMethodSignature *csignature, gboolean readonly, int *op) @@ -6051,9 +6072,15 @@ generate_code (TransformData *td, MonoMethod *method, MonoMethodHeader *header, interp_emit_ldsflda (td, field, error); goto_if_nok (error, exit); } else { + if (G_UNLIKELY (m_field_is_from_update (field))) { + /* metadata-update: can't add byref fields */ + g_assert (!m_type_is_byref (ftype)); + interp_emit_metadata_update_ldflda (td, field, error); + goto_if_nok (error, exit); + td->ip += 5; + break; + } td->sp--; - /* TODO: metadata-update: implement me. If it's an added field, emit a call to the helper method instead of MINT_LDFLDA_UNSAFE */ - g_assert (!m_field_is_from_update (field)); int foffset = m_class_is_valuetype (klass) ? m_field_get_offset (field) - MONO_ABI_SIZEOF (MonoObject) : m_field_get_offset (field); if (td->sp->type == STACK_TYPE_O || td->sp->type == STACK_TYPE_I) { interp_add_ins (td, MINT_LDFLDA); @@ -6098,7 +6125,7 @@ generate_code (TransformData *td, MonoMethod *method, MonoMethodHeader *header, interp_emit_sfld_access (td, field, field_klass, mt, TRUE, error); goto_if_nok (error, exit); } else if (td->sp [-1].type == STACK_TYPE_VT) { - /* TODO: metadata-update: implement me */ + /* metadata-update: can't add fields to structs */ g_assert (!m_field_is_from_update (field)); int size = 0; /* First we pop the vt object from the stack. Then we push the field */ @@ -6124,8 +6151,19 @@ generate_code (TransformData *td, MonoMethod *method, MonoMethodHeader *header, push_type (td, stack_type [mt], field_klass); interp_ins_set_dreg (td->last_ins, td->sp [-1].local); } else { - /* TODO: metadata-update: implement me */ - g_assert (!m_field_is_from_update (field)); + if (G_UNLIKELY (m_field_is_from_update (field))) { + g_assert (!m_type_is_byref (ftype)); + MonoClass *field_class = mono_class_from_mono_type_internal (ftype); + interp_emit_metadata_update_ldflda (td, field, error); + goto_if_nok (error, exit); + interp_add_ins (td, interp_get_ldind_for_mt (mt)); + interp_ins_set_sreg (td->last_ins, td->sp [-1].local); + td->sp--; + push_type (td, stack_type [mt], field_class); + interp_ins_set_dreg (td->last_ins, td->sp[-1].local); + td->ip += 5; + break; + } int opcode = MINT_LDFLD_I1 + mt - MINT_TYPE_I1; #ifdef NO_UNALIGNED_ACCESS if ((mt == MINT_TYPE_I8 || mt == MINT_TYPE_R8) && field->offset % SIZEOF_VOID_P != 0) @@ -6177,8 +6215,20 @@ generate_code (TransformData *td, MonoMethod *method, MonoMethodHeader *header, mono_class_vtable_checked (field_klass, error); goto_if_nok (error, exit); } else { - /* TODO: metadata-update: implement me */ - g_assert (!m_field_is_from_update (field)); + if (G_UNLIKELY (m_field_is_from_update (field))) { + // metadata-update: Can't add byref fields + g_assert (!m_type_is_byref (ftype)); + MonoClass *field_class = mono_class_from_mono_type_internal (ftype); + MonoType *local_type = m_class_get_byval_arg (field_class); + int local = create_interp_local (td, local_type); + store_local (td, local); + interp_emit_metadata_update_ldflda (td, field, error); + goto_if_nok (error, exit); + load_local (td, local); + interp_emit_stobj (td, field_class); + td->ip += 5; + break; + } int opcode = MINT_STFLD_I1 + mt - MINT_TYPE_I1; #ifdef NO_UNALIGNED_ACCESS if ((mt == MINT_TYPE_I8 || mt == MINT_TYPE_R8) && field->offset % SIZEOF_VOID_P != 0) diff --git a/src/mono/mono/mini/mini-amd64.c b/src/mono/mono/mini/mini-amd64.c index 20bacf6236cd01..335cf81e2a0337 100644 --- a/src/mono/mono/mini/mini-amd64.c +++ b/src/mono/mono/mini/mini-amd64.c @@ -391,6 +391,7 @@ collect_field_info_nested (MonoClass *klass, GArray *fields_array, int offset, g while ((field = mono_class_get_fields_internal (klass, &iter))) { if (field->type->attrs & FIELD_ATTRIBUTE_STATIC) continue; + /* metadata-update: we're in the JIT here, right? updates aren't supported */ g_assert (!m_field_is_from_update (field)); if (MONO_TYPE_ISSTRUCT (field->type)) { collect_field_info_nested (mono_class_from_mono_type_internal (field->type), fields_array, m_field_get_offset (field) - MONO_ABI_SIZEOF (MonoObject), pinvoke, unicode); diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs index 32727996e98400..e07fdd718ae9bf 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs @@ -904,14 +904,14 @@ public void SetDebugId(int id) debugId = id; } - public bool EnC(byte[] meta, byte[] pdb) + public bool EnC(MonoSDBHelper sdbAgent, byte[] meta, byte[] pdb) { var asmStream = new MemoryStream(meta); MetadataReader asmMetadataReader = MetadataReaderProvider.FromMetadataStream(asmStream).GetMetadataReader(); var pdbStream = new MemoryStream(pdb); MetadataReader pdbMetadataReader = MetadataReaderProvider.FromPortablePdbStream(pdbStream).GetMetadataReader(); enCMetadataReader.Add(new (asmMetadataReader, pdbMetadataReader)); - PopulateEnC(asmMetadataReader, pdbMetadataReader); + PopulateEnC(sdbAgent, asmMetadataReader, pdbMetadataReader); return true; } private static int GetTypeDefIdx(MetadataReader asmMetadataReaderParm, int number) @@ -958,10 +958,12 @@ public string EnCGetString(StringHandle strHandle) return asmMetadataReaderLocal.GetString(MetadataTokens.StringHandle(strIdx)); } - private void PopulateEnC(MetadataReader asmMetadataReaderParm, MetadataReader pdbMetadataReaderParm) + private void PopulateEnC(MonoSDBHelper sdbAgent, MetadataReader asmMetadataReaderParm, MetadataReader pdbMetadataReaderParm) { TypeInfo typeInfo = null; int methodIdxAsm = 1; + sdbAgent.ResetTypes(); // FIXME: only remove the cache for the affected type if fields or methods are added + foreach (var entry in asmMetadataReaderParm.GetEditAndContinueLogEntries()) { if (entry.Operation == EditAndContinueOperation.AddMethod || @@ -1351,9 +1353,9 @@ private sealed class DebugItem public string Url { get; set; } public Task Data { get; set; } } - public static IEnumerable EnC(AssemblyInfo asm, byte[] meta_data, byte[] pdb_data) + public static IEnumerable EnC(MonoSDBHelper sdbAgent, AssemblyInfo asm, byte[] meta_data, byte[] pdb_data) { - asm.EnC(meta_data, pdb_data); + asm.EnC(sdbAgent, meta_data, pdb_data); return GetEnCMethods(asm); } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs index 5b6bf24290f731..45c772a3428129 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs @@ -844,7 +844,7 @@ private async Task ProcessEnC(SessionId sessionId, ExecutionContext contex var assemblyName = await context.SdbAgent.GetAssemblyNameFromModule(moduleId, token); DebugStore store = await LoadStore(sessionId, true, token); AssemblyInfo asm = store.GetAssemblyByName(assemblyName); - var methods = DebugStore.EnC(asm, meta_buf, pdb_buf); + var methods = DebugStore.EnC(context.SdbAgent, asm, meta_buf, pdb_buf); foreach (var method in methods) { await ResetBreakpoint(sessionId, store, method, token); diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs index de9585dd1691d0..f5d9dc06dbcc0b 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs @@ -833,6 +833,10 @@ public void ResetStore(DebugStore store) ClearCache(); } + public void ResetTypes() { + this.types = new (); + } + public async Task GetAssemblyInfo(int assemblyId, CancellationToken token) { if (assemblies.TryGetValue(assemblyId, out AssemblyInfo asm)) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/HotReloadTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/HotReloadTests.cs index eec98fcbd1902b..c463cc3baebb43 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/HotReloadTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/HotReloadTests.cs @@ -561,5 +561,43 @@ public async Task DebugHotReloadMethod_AddingNewMethodWithoutAnyOtherChange_With CheckLocation("dotnet://ApplyUpdateReferencedAssembly2.dll/MethodBody2.cs", 12, 12, scripts, pause_location["callFrames"]?[0]["location"]); await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", $"dotnet://ApplyUpdateReferencedAssembly2.dll/MethodBody2.cs", 18, 12, "ApplyUpdateReferencedAssembly.AddMethod.StaticMethod2"); } + + [ConditionalFact(nameof(RunningOnChrome))] + public async Task DebugHotReload_NewInstanceFields() + { + string asm_name = "ApplyUpdateReferencedAssembly3"; + string asm_file = Path.Combine (DebuggerTestAppPath, asm_name + ".dll"); + string pdb_file = Path.Combine (DebuggerTestAppPath, asm_name + ".pdb"); + string asm_file_hot_reload = Path.Combine (DebuggerTestAppPath, "..", "wasm", asm_name+ ".dll"); + var pause_location = await LoadAssemblyAndTestHotReload(asm_file, pdb_file, asm_file_hot_reload, "AddInstanceFields", "StaticMethod1", + expectBpResolvedEvent: false, sourcesToWait: new string [] { "MethodBody2.cs" }); + var frame = pause_location["callFrames"][0]; + var locals = await GetProperties(frame["callFrameId"].Value()); + await CheckObject(locals, "c", "ApplyUpdateReferencedAssembly.AddInstanceFields.C"); + await SendCommandAndCheck (JObject.FromObject(new { }), "Debugger.resume", script_loc: null, line: -1, column: -1, function_name: null, + locals_fn: async (locals) => { + await CheckObject(locals, "c", "ApplyUpdateReferencedAssembly.AddInstanceFields.C"); + var c = await GetObjectOnLocals(locals, "c"); + await CheckProps (c, new { + Field1 = TNumber(123), + }, "c", num_fields: 1); + var cObj = GetAndAssertObjectWithName (locals, "c"); + await SetValueOnObject (cObj, "Field1", "456.5"); + + c = await GetObjectOnLocals(locals, "c"); + await CheckProps (c, new { + Field1 = TNumber("456.5", isDecimal: true), + }, "c", num_fields: 1); + }); + await SendCommandAndCheck (JObject.FromObject(new { }), "Debugger.resume", script_loc: null, line: -1, column: -1, function_name: null, + locals_fn: async (locals) => { + await CheckObject(locals, "c", "ApplyUpdateReferencedAssembly.AddInstanceFields.C"); + var c = await GetObjectOnLocals(locals, "c"); + await CheckProps (c, new { + Field1 = TNumber(123), + Field2 = TString("spqr"), + }, "c", num_fields: 2); + }); + } } } diff --git a/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly3/ApplyUpdateReferencedAssembly3.csproj b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly3/ApplyUpdateReferencedAssembly3.csproj new file mode 100644 index 00000000000000..f024e0e58b3f6b --- /dev/null +++ b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly3/ApplyUpdateReferencedAssembly3.csproj @@ -0,0 +1,35 @@ + + + true + deltascript.json + library + false + true + + false + true + + false + false + false + true + + + true + + + + + + + + + + + diff --git a/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly3/MethodBody2.cs b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly3/MethodBody2.cs new file mode 100644 index 00000000000000..4c5f28e85e30b2 --- /dev/null +++ b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly3/MethodBody2.cs @@ -0,0 +1,23 @@ +// 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; +//keep the same line number for class in the original file and the updates ones +namespace ApplyUpdateReferencedAssembly +{ + public class AddInstanceFields { + public static string StaticMethod1 () { + C c = new(); + Debugger.Break(); + return "OLD STRING"; + } + + public class C { + public C() + { + } + } + + } +} diff --git a/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly3/MethodBody2_v1.cs b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly3/MethodBody2_v1.cs new file mode 100644 index 00000000000000..92a4dde4a1acf0 --- /dev/null +++ b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly3/MethodBody2_v1.cs @@ -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.Diagnostics; +using System; +//keep the same line number for class in the original file and the updates ones +namespace ApplyUpdateReferencedAssembly +{ + public class AddInstanceFields { + public static string StaticMethod1 () { + C c = new(); + Debugger.Break(); + return "OLD STRING"; + } + + public class C { + public C() + { + Field1 = 123.0; + } + public double Field1; + } + } + +} diff --git a/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly3/MethodBody2_v2.cs b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly3/MethodBody2_v2.cs new file mode 100644 index 00000000000000..379adbe266908e --- /dev/null +++ b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly3/MethodBody2_v2.cs @@ -0,0 +1,28 @@ +// 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; +//keep the same line number for class in the original file and the updates ones +namespace ApplyUpdateReferencedAssembly +{ + public class AddInstanceFields { + public static string StaticMethod1 () { + C c = new(); + c.Field2 = "spqr"; + Debugger.Break(); + return "OLD STRING"; + } + + public class C { + public C() + { + Field1 = 123.0; + Field2 = "abcd"; + } + public double Field1; + public string Field2; + } + } + +} diff --git a/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly3/deltascript.json b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly3/deltascript.json new file mode 100644 index 00000000000000..6603dcff23d1f8 --- /dev/null +++ b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssembly3/deltascript.json @@ -0,0 +1,7 @@ +{ + "changes": [ + {"document": "MethodBody2.cs", "update": "MethodBody2_v1.cs"}, + {"document": "MethodBody2.cs", "update": "MethodBody2_v2.cs"} + ] +} + diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj b/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj index a9d8b0e66ecdc0..ea37306876ee73 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj @@ -21,6 +21,7 @@ +