Skip to content

Commit 9ef4b1e

Browse files
[release/7.0] [hot_reload] implement param reflection (#77734)
* Add new test ReflectionAddNewMethod * FIXME: get_param_names, get_marshal_info and custom_attrs need work * WIP - add a method param reverse lookup * look up params from added methods * Remove FIXMEs and unused field * remove writelines from test * fix test on coreclr * why does coreclr have 2 attributes here?? * There should be 2 attributes on the 4th param * one more place that looks at params * A couple more places where we look at the Params table * Check default values on params on added method * fix lookup if table is empty * add a gratuitious typeof assert otherwise the CancellationToken type is trimmed on wasm * Add a single mono_metadata_get_method_params function remove duplicated code Co-authored-by: Aleksey Kliger <[email protected]> Co-authored-by: Aleksey Kliger <[email protected]>
1 parent 555d283 commit 9ef4b1e

17 files changed

+325
-70
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
using System;
4+
5+
6+
namespace System.Reflection.Metadata.ApplyUpdate.Test
7+
{
8+
public class ReflectionAddNewMethod
9+
{
10+
public string ExistingMethod(string u, double f)
11+
{
12+
return u + f.ToString();;
13+
}
14+
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
using System;
4+
using System.Runtime.CompilerServices;
5+
using CancellationToken = System.Threading.CancellationToken;
6+
7+
namespace System.Reflection.Metadata.ApplyUpdate.Test
8+
{
9+
public class ReflectionAddNewMethod
10+
{
11+
public string ExistingMethod(string u, double f)
12+
{
13+
return u + f.ToString();;
14+
}
15+
16+
public double AddedNewMethod(char c, float h, string w, CancellationToken ct = default, [CallerMemberName] string callerName = "")
17+
{
18+
return ((double)Convert.ToInt32(c)) + h;
19+
}
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<RootNamespace>System.Runtime.Loader.Tests</RootNamespace>
4+
<TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
5+
<TestRuntime>true</TestRuntime>
6+
<DeltaScript>deltascript.json</DeltaScript>
7+
</PropertyGroup>
8+
<ItemGroup>
9+
<Compile Include="ReflectionAddNewMethod.cs" />
10+
</ItemGroup>
11+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"changes": [
3+
{"document": "ReflectionAddNewMethod.cs", "update": "ReflectionAddNewMethod_v1.cs"},
4+
]
5+
}
6+

src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Reflection;
55
using System.Runtime.CompilerServices;
6+
using System.Runtime.InteropServices;
67
using Xunit;
78

89
namespace System.Reflection.Metadata
@@ -629,5 +630,87 @@ public static void TestReflectionAddNewType()
629630
System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewType.ZExistingClass.ExistingMethod ();
630631
});
631632
}
633+
634+
[ConditionalFact(typeof(ApplyUpdateUtil), nameof(ApplyUpdateUtil.IsSupported))]
635+
public static void TestReflectionAddNewMethod()
636+
{
637+
ApplyUpdateUtil.TestCase(static () =>
638+
{
639+
var ty = typeof(System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewMethod);
640+
var assm = ty.Assembly;
641+
642+
var bindingFlags = BindingFlags.Instance | BindingFlags.Public;
643+
var allMethods = ty.GetMethods(bindingFlags);
644+
645+
int objectMethods = typeof(object).GetMethods(bindingFlags).Length;
646+
Assert.Equal (objectMethods + 1, allMethods.Length);
647+
648+
ApplyUpdateUtil.ApplyUpdate(assm);
649+
ApplyUpdateUtil.ClearAllReflectionCaches();
650+
651+
ty = typeof(System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewMethod);
652+
653+
allMethods = ty.GetMethods(bindingFlags);
654+
Assert.Equal (objectMethods + 2, allMethods.Length);
655+
656+
var mi = ty.GetMethod ("AddedNewMethod");
657+
658+
Assert.NotNull (mi);
659+
660+
var retParm = mi.ReturnParameter;
661+
Assert.NotNull (retParm);
662+
Assert.NotNull (retParm.ParameterType);
663+
Assert.Equal (-1, retParm.Position);
664+
665+
var retCas = retParm.GetCustomAttributes(false);
666+
Assert.NotNull(retCas);
667+
Assert.Equal(0, retCas.Length);
668+
669+
var parms = mi.GetParameters();
670+
Assert.Equal (5, parms.Length);
671+
672+
int parmPos = 0;
673+
foreach (var parm in parms)
674+
{
675+
Assert.NotNull(parm);
676+
Assert.NotNull(parm.ParameterType);
677+
Assert.Equal(parmPos, parm.Position);
678+
Assert.NotNull(parm.Name);
679+
680+
var cas = parm.GetCustomAttributes(false);
681+
foreach (var ca in cas) {
682+
Assert.NotNull (ca);
683+
}
684+
685+
parmPos++;
686+
}
687+
688+
var parmAttrs = parms[4].GetCustomAttributes(false);
689+
Assert.Equal (2, parmAttrs.Length);
690+
bool foundCallerMemberName = false;
691+
bool foundOptional = false;
692+
foreach (var pa in parmAttrs) {
693+
if (typeof (CallerMemberNameAttribute).Equals(pa.GetType()))
694+
{
695+
foundCallerMemberName = true;
696+
}
697+
if (typeof (OptionalAttribute).Equals(pa.GetType()))
698+
{
699+
foundOptional = true;
700+
}
701+
}
702+
Assert.True(foundCallerMemberName);
703+
Assert.True(foundOptional);
704+
705+
// n.b. this typeof() also makes the rest of the test work on Wasm with aggressive trimming.
706+
Assert.Equal (typeof(System.Threading.CancellationToken), parms[3].ParameterType);
707+
708+
Assert.True(parms[3].HasDefaultValue);
709+
Assert.True(parms[4].HasDefaultValue);
710+
711+
Assert.Null(parms[3].DefaultValue);
712+
Assert.Equal(string.Empty, parms[4].DefaultValue);
713+
});
714+
}
632715
}
633716
}

src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
<ProjectReference Include="ApplyUpdate\System.Reflection.Metadata.ApplyUpdate.Test.AddStaticLambda\System.Reflection.Metadata.ApplyUpdate.Test.AddStaticLambda.csproj" />
6363
<ProjectReference Include="ApplyUpdate\System.Reflection.Metadata.ApplyUpdate.Test.StaticLambdaRegression\System.Reflection.Metadata.ApplyUpdate.Test.StaticLambdaRegression.csproj" />
6464
<ProjectReference Include="ApplyUpdate\System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewType\System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewType.csproj" />
65+
<ProjectReference Include="ApplyUpdate\System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewMethod\System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewMethod.csproj" />
6566
</ItemGroup>
6667
<ItemGroup Condition="'$(TargetOS)' == 'Browser'">
6768
<WasmFilesToIncludeFromPublishDir Include="$(AssemblyName).dll" />

src/mono/mono/component/hot_reload-internals.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,9 @@ typedef struct _MonoClassMetadataUpdateEvent {
8686
uint32_t token; /* the Event table token where this event was defined. */
8787
} MonoClassMetadataUpdateEvent;
8888

89+
typedef struct _MonoMethodMetadataUpdateParamInfo {
90+
uint32_t first_param_token; /* a Param token */
91+
uint32_t param_count;
92+
} MonoMethodMetadataUpdateParamInfo;
93+
8994
#endif/*_MONO_COMPONENT_HOT_RELOAD_INTERNALS_H*/

src/mono/mono/component/hot_reload-stub.c

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ hot_reload_get_num_methods_added (MonoClass *klass);
110110
static const char *
111111
hot_reload_get_capabilities (void);
112112

113+
static uint32_t
114+
hot_reload_stub_get_method_params (MonoImage *base_image, uint32_t methoddef_token, uint32_t *out_param_count_opt);
115+
113116
static MonoComponentHotReload fn_table = {
114117
{ MONO_COMPONENT_ITF_VERSION, &hot_reload_stub_available },
115118
&hot_reload_stub_set_fastpath_data,
@@ -142,7 +145,8 @@ static MonoComponentHotReload fn_table = {
142145
&hot_reload_stub_added_fields_iter,
143146
&hot_reload_get_num_fields_added,
144147
&hot_reload_get_num_methods_added,
145-
&hot_reload_get_capabilities
148+
&hot_reload_get_capabilities,
149+
&hot_reload_stub_get_method_params,
146150
};
147151

148152
static bool
@@ -343,6 +347,12 @@ hot_reload_get_capabilities (void)
343347
return "";
344348
}
345349

350+
static uint32_t
351+
hot_reload_stub_get_method_params (MonoImage *base_image, uint32_t methoddef_token, uint32_t *out_param_count_opt)
352+
{
353+
return 0;
354+
}
355+
346356
MONO_COMPONENT_EXPORT_ENTRYPOINT
347357
MonoComponentHotReload *
348358
mono_component_hot_reload_init (void)

src/mono/mono/component/hot_reload.c

Lines changed: 87 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@ hot_reload_get_num_methods_added (MonoClass *klass);
144144
static const char *
145145
hot_reload_get_capabilities (void);
146146

147+
static uint32_t
148+
hot_reload_get_method_params (MonoImage *base_image, uint32_t methoddef_token, uint32_t *out_param_count_opt);
149+
147150
static MonoClassMetadataUpdateField *
148151
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);
149152

@@ -179,7 +182,8 @@ static MonoComponentHotReload fn_table = {
179182
&hot_reload_added_fields_iter,
180183
&hot_reload_get_num_fields_added,
181184
&hot_reload_get_num_methods_added,
182-
&hot_reload_get_capabilities
185+
&hot_reload_get_capabilities,
186+
&hot_reload_get_method_params,
183187
};
184188

185189
MonoComponentHotReload *
@@ -272,6 +276,9 @@ struct _BaselineInfo {
272276
/* Parents for added methods, fields, etc */
273277
GHashTable *member_parent; /* maps added methoddef or fielddef tokens to typedef tokens */
274278

279+
/* Params for added methods */
280+
GHashTable *method_params; /* maps methoddef tokens to a MonoClassMetadataUpdateMethodParamInfo* */
281+
275282
/* Skeletons for all newly-added types from every generation. Accessing the array requires the image lock. */
276283
GArray *skeletons;
277284
};
@@ -412,6 +419,12 @@ baseline_info_destroy (BaselineInfo *info)
412419
if (info->skeletons)
413420
g_array_free (info->skeletons, TRUE);
414421

422+
if (info->member_parent)
423+
g_hash_table_destroy (info->member_parent);
424+
425+
if (info->method_params)
426+
g_hash_table_destroy (info->method_params);
427+
415428
g_free (info);
416429
}
417430

@@ -627,6 +640,11 @@ add_method_to_baseline (BaselineInfo *base_info, DeltaInfo *delta_info, MonoClas
627640
static void
628641
add_field_to_baseline (BaselineInfo *base_info, DeltaInfo *delta_info, MonoClass *klass, uint32_t field_token);
629642

643+
/* Add a method->params lookup for new methods in existing classes */
644+
static void
645+
add_param_info_for_method (BaselineInfo *base_info, uint32_t param_token, uint32_t method_token);
646+
647+
630648
void
631649
hot_reload_init (void)
632650
{
@@ -2019,6 +2037,7 @@ apply_enclog_pass2 (Pass2Context *ctx, MonoImage *image_base, BaselineInfo *base
20192037
uint32_t add_member_typedef = 0;
20202038
uint32_t add_property_propertymap = 0;
20212039
uint32_t add_event_eventmap = 0;
2040+
uint32_t add_field_method = 0;
20222041

20232042
gboolean assemblyref_updated = FALSE;
20242043
for (guint32 i = 0; i < rows ; ++i) {
@@ -2049,6 +2068,7 @@ apply_enclog_pass2 (Pass2Context *ctx, MonoImage *image_base, BaselineInfo *base
20492068

20502069
case ENC_FUNC_ADD_PARAM: {
20512070
g_assert (token_table == MONO_TABLE_METHOD);
2071+
add_field_method = log_token;
20522072
break;
20532073
}
20542074
case ENC_FUNC_ADD_FIELD: {
@@ -2115,8 +2135,11 @@ apply_enclog_pass2 (Pass2Context *ctx, MonoImage *image_base, BaselineInfo *base
21152135
}
21162136
case MONO_TABLE_METHOD: {
21172137
/* if adding a param, handle it with the next record */
2118-
if (func_code == ENC_FUNC_ADD_PARAM)
2138+
if (func_code == ENC_FUNC_ADD_PARAM) {
2139+
g_assert (is_addition);
21192140
break;
2141+
}
2142+
g_assert (func_code == ENC_FUNC_DEFAULT);
21202143

21212144
if (!base_info->method_table_update)
21222145
base_info->method_table_update = g_hash_table_new (g_direct_hash, g_direct_equal);
@@ -2366,9 +2389,21 @@ apply_enclog_pass2 (Pass2Context *ctx, MonoImage *image_base, BaselineInfo *base
23662389
*
23672390
* So by the time we see the param additions, the methods are already in.
23682391
*
2369-
* FIXME: we need a lookaside table (like member_parent) for every place
2370-
* that looks at MONO_METHOD_PARAMLIST
23712392
*/
2393+
if (is_addition) {
2394+
g_assert (add_field_method != 0);
2395+
uint32_t parent_type_token = hot_reload_method_parent (image_base, add_field_method);
2396+
g_assert (parent_type_token != 0); // we added a parameter to a method that was added
2397+
if (pass2_context_is_skeleton (ctx, parent_type_token)) {
2398+
// it's a parameter on a new method in a brand new class
2399+
// FIXME: need to do something here?
2400+
} else {
2401+
// it's a parameter on a new method in an existing class
2402+
add_param_info_for_method (base_info, log_token, add_field_method);
2403+
}
2404+
add_field_method = 0;
2405+
break;
2406+
}
23722407
break;
23732408
}
23742409
case MONO_TABLE_INTERFACEIMPL: {
@@ -2819,6 +2854,28 @@ hot_reload_field_parent (MonoImage *base_image, uint32_t field_token)
28192854
}
28202855

28212856

2857+
static void
2858+
add_param_info_for_method (BaselineInfo *base_info, uint32_t param_token, uint32_t method_token)
2859+
{
2860+
if (!base_info->method_params) {
2861+
base_info->method_params = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
2862+
}
2863+
MonoMethodMetadataUpdateParamInfo* info = NULL;
2864+
info = g_hash_table_lookup (base_info->method_params, GUINT_TO_POINTER (method_token));
2865+
if (!info) {
2866+
// FIXME locking
2867+
info = g_new0 (MonoMethodMetadataUpdateParamInfo, 1);
2868+
g_hash_table_insert (base_info->method_params, GUINT_TO_POINTER (method_token), info);
2869+
info->first_param_token = param_token;
2870+
info->param_count = 1;
2871+
} else {
2872+
uint32_t param_index = mono_metadata_token_index (param_token);
2873+
// expect params for a single method to be a contiguous sequence of rows
2874+
g_assert (mono_metadata_token_index (info->first_param_token) + info->param_count == param_index);
2875+
info->param_count++;
2876+
}
2877+
}
2878+
28222879
/* HACK - keep in sync with locator_t in metadata/metadata.c */
28232880
typedef struct {
28242881
guint32 idx; /* The index that we are trying to locate */
@@ -3148,6 +3205,32 @@ hot_reload_get_num_methods_added (MonoClass *klass)
31483205
return count;
31493206
}
31503207

3208+
static uint32_t
3209+
hot_reload_get_method_params (MonoImage *base_image, uint32_t methoddef_token, uint32_t *out_param_count_opt)
3210+
{
3211+
BaselineInfo *base_info = baseline_info_lookup (base_image);
3212+
g_assert (base_info);
3213+
3214+
/* FIXME: locking in case the hash table grows */
3215+
3216+
if (!base_info->method_params)
3217+
return 0;
3218+
3219+
MonoMethodMetadataUpdateParamInfo* info = NULL;
3220+
info = g_hash_table_lookup (base_info->method_params, GUINT_TO_POINTER (methoddef_token));
3221+
if (!info) {
3222+
if (out_param_count_opt)
3223+
*out_param_count_opt = 0;
3224+
return 0;
3225+
}
3226+
3227+
if (out_param_count_opt)
3228+
*out_param_count_opt = info->param_count;
3229+
3230+
return mono_metadata_token_index (info->first_param_token);
3231+
}
3232+
3233+
31513234
static const char *
31523235
hot_reload_get_capabilities (void)
31533236
{

src/mono/mono/component/hot_reload.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ typedef struct _MonoComponentHotReload {
4848
uint32_t (*get_num_fields_added) (MonoClass *klass);
4949
uint32_t (*get_num_methods_added) (MonoClass *klass);
5050
const char* (*get_capabilities) (void);
51+
uint32_t (*get_method_params) (MonoImage *base_image, uint32_t methoddef_token, uint32_t *out_param_count_opt);
5152
} MonoComponentHotReload;
5253

5354
MONO_COMPONENT_EXPORT_ENTRYPOINT

0 commit comments

Comments
 (0)