Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[mono] Hot Reload: support for reloadable types #66749

Merged
merged 37 commits into from
Mar 18, 2022
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
702faa1
Checkpoint. Infrastructure for added types
lambdageek Mar 9, 2022
6dc3e90
extra tracing
lambdageek Mar 10, 2022
6c08cfc
register member->parent lookups for newly-added classes and members
lambdageek Mar 10, 2022
e2c5f43
fix off by one error creating class from skeleton
lambdageek Mar 10, 2022
5c870a0
Revert "extra tracing"
lambdageek Mar 10, 2022
8d80630
AddNestedClass test works with mono now; also make it generic
lambdageek Mar 10, 2022
d9fd856
checkpoint: adding properties
lambdageek Mar 10, 2022
d133363
Add a property to AddNestedClass test
lambdageek Mar 11, 2022
3a7f6f5
remove allow class/field/method/property ifdefs
lambdageek Mar 11, 2022
9131764
add event to AddNestedClass
lambdageek Mar 11, 2022
37481f2
add DISALLOW_BROKEN_PARENT ifdef
lambdageek Mar 11, 2022
b5fe053
checkpoint: adding events
lambdageek Mar 11, 2022
f0528b8
Add new test ReflectionAddNewType
lambdageek Mar 14, 2022
5829e72
Add new types to the image name cache
lambdageek Mar 14, 2022
b1e3c04
Assembly.GetTypes and additional tests
lambdageek Mar 14, 2022
615d2a5
Make nested types work
lambdageek Mar 14, 2022
36fbca0
GetInterfaces on new types; also Activator.CreateInstance
lambdageek Mar 14, 2022
68e2eba
Mark mono_class_get_nested_classes_property as a component API
lambdageek Mar 15, 2022
63fb67a
Add GetMethods, GetFields, GetMethod, GetField test
lambdageek Mar 15, 2022
7861d05
[class] Implement added method iteration
lambdageek Mar 15, 2022
7c18fb9
Implement added field iteration
lambdageek Mar 15, 2022
13c32ca
Add a GetField(BindingFlags.Static) test case
lambdageek Mar 15, 2022
d2bcbfc
Add Enum.GetNames and GetProperties tests - not working yet
lambdageek Mar 15, 2022
64c03eb
Mark mono_class_get_method_count as a component API
lambdageek Mar 16, 2022
5153195
Enum values work
lambdageek Mar 16, 2022
e759238
Use GSList for added_fields (and props, events); add from_update bit
lambdageek Mar 16, 2022
ccd590f
Reflection on props in new classes works
lambdageek Mar 16, 2022
4c8e0f1
nit: unused variable
lambdageek Mar 17, 2022
a9d1293
events on new classes work
lambdageek Mar 17, 2022
fa0ec65
Add CustomAttribute reflection test
lambdageek Mar 17, 2022
0b61b58
fix whitespace
lambdageek Mar 17, 2022
6b59577
remove test for props on existing type - it's not part of this PR
lambdageek Mar 17, 2022
a393f3b
instance field additions to existing types aren't supported yet
lambdageek Mar 17, 2022
8d129c1
rm TODO and FIXME
lambdageek Mar 17, 2022
9917ef3
store prop and event from_update bit in attrs
lambdageek Mar 17, 2022
7a4c892
remove instance fields from reflection test
lambdageek Mar 17, 2022
2c56d90
make the Browser EAT lanes happy
lambdageek Mar 17, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,36 @@ public AddNestedClass()

public string TestMethod()
{
var n = new Nested();
n.f = "123";
return n.M();
var n = new Nested<string, int>();
n.Eff = "123";
n.g = 456;
n.Evt += new Action<string> (n.DefaultHandler);
n.RaiseEvt();
return n.M() + n.buf;
}

private class Nested {
private class Nested<T, U> {
public Nested() { }
internal string f;
internal T f;
internal U g;
public T Eff {
get => f;
set { f = value; }
}
public string M () {
return f + "456";
return Eff.ToString() + g.ToString();
}

public event Action<string> Evt;

public void RaiseEvt () {
Evt ("789");
}

public string buf;

public void DefaultHandler (string s) {
this.buf = s;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;


namespace System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewType;

public interface IExistingInterface {
public string ItfMethod(int i);
}

public class ZExistingClass
{
public class PreviousNestedClass { }
}

[AttributeUsage(AttributeTargets.All, AllowMultiple=true, Inherited=false)]
public class CustomNoteAttribute : Attribute {
public CustomNoteAttribute(string note) {Note = note;}

public string Note;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// 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.ReflectionAddNewType;

public interface IExistingInterface {
public string ItfMethod(int i);
}

public class ZExistingClass
{
public class PreviousNestedClass { }
public class NewNestedClass {};


public string NewMethod (string s, int i) => s + i.ToString();

public int NewField;

public static DateTime NewStaticField;

public double NewProp { get; set; }
}

[AttributeUsage(AttributeTargets.All, AllowMultiple=true, Inherited=false)]
public class CustomNoteAttribute : Attribute {
public CustomNoteAttribute(string note) {Note = note;}

public string Note;
}

[CustomNote("123")]
public class NewToplevelClass : IExistingInterface, ICloneable {
public string ItfMethod(int i) {
return i.ToString();
}

[CustomNote("abcd")]
public void SomeMethod(int x) {}

public virtual object Clone() {
return new NewToplevelClass();
}

public class AlsoNested { }

[CustomNote("hijkl")]
public float NewProp {get; set;}

public byte[] OtherNewProp {get; set;}

public event EventHandler<string> NewEvent;
public event EventHandler<byte> OtherNewEvent;
}

public class NewGenericClass<T> : NewToplevelClass {
public override object Clone() {
return new NewGenericClass<T>();
}
}

public struct NewToplevelStruct {
}

public interface INewInterface : IExistingInterface {
public int AddedItfMethod (string s);
}

public enum NewEnum {
Red, Yellow, Green
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>System.Runtime.Loader.Tests</RootNamespace>
<TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
<TestRuntime>true</TestRuntime>
<DeltaScript>deltascript.json</DeltaScript>
</PropertyGroup>
<ItemGroup>
<Compile Include="ReflectionAddNewType.cs" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"changes": [
{"document": "ReflectionAddNewType.cs", "update": "ReflectionAddNewType_v1.cs"},
]
}

197 changes: 195 additions & 2 deletions src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Reflection;
using System.Runtime.CompilerServices;
using Xunit;

Expand Down Expand Up @@ -306,7 +307,6 @@ public static void TestAddStaticField()
});
}

[ActiveIssue("https://github.com/dotnet/runtime/issues/63643", TestRuntimes.Mono)]
[ConditionalFact(typeof(ApplyUpdateUtil), nameof(ApplyUpdateUtil.IsSupported))]
public static void TestAddNestedClass()
{
Expand All @@ -325,7 +325,7 @@ public static void TestAddNestedClass()

r = x.TestMethod();

Assert.Equal("123456", r);
Assert.Equal("123456789", r);
});
}

Expand Down Expand Up @@ -427,5 +427,198 @@ public static void TestStaticLambdaRegression()

});
}

private static bool ContainsTypeWithName(Type[] types, string fullName)
{
foreach (var ty in types) {
if (ty.FullName == fullName)
return true;
}
return false;
}

internal static Type CheckReflectedType(Assembly assm, Type[] allTypes, string nameSpace, string typeName, Action<Type> moreChecks = null, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0)
{
var fullName = $"{nameSpace}.{typeName}";
var ty = assm.GetType(fullName);
Assert.True(ty != null, $"{callerFilePath}:{callerLineNumber}: expected Assembly.GetType for '{typeName}' to return non-null in {callerMemberName}");
int nestedIdx = typeName.LastIndexOf('+');
string comparisonName = typeName;
if (nestedIdx != -1)
comparisonName = typeName.Substring(nestedIdx+1);
Assert.True(comparisonName == ty.Name, $"{callerFilePath}:{callerLineNumber}: returned type has unexpected name '{ty.Name}' (expected: '{comparisonName}') in {callerMemberName}");
Assert.True(ContainsTypeWithName (allTypes, fullName), $"{callerFilePath}:{callerLineNumber}: expected Assembly.GetTypes to contain '{fullName}', but it didn't in {callerMemberName}");
if (moreChecks != null)
moreChecks(ty);
return ty;
}


internal static void CheckCustomNoteAttribute(MemberInfo subject, string expectedAttributeValue, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0)
{
var attrData = subject.GetCustomAttributesData();
CustomAttributeData noteData = null;
foreach (var cad in attrData)
{
if (cad.AttributeType.FullName.Contains("CustomNoteAttribute"))
noteData = cad;
}
Assert.True(noteData != null, $"{callerFilePath}:{callerLineNumber}: expected a CustomNoteAttribute attributes on '{subject.Name}', but got null, in {callerMemberName}");
Assert.True(1 == noteData.ConstructorArguments.Count, $"{callerFilePath}:{callerLineNumber}: expected exactly 1 constructor argument on CustomNoteAttribute, got {noteData.ConstructorArguments.Count}, in {callerMemberName}");
object argVal = noteData.ConstructorArguments[0].Value;
Assert.True(expectedAttributeValue.Equals(argVal), $"{callerFilePath}:{callerLineNumber}: expected '{expectedAttributeValue}' as CustomNoteAttribute argument, got '{argVal}', in {callerMemberName}");

var attrs = subject.GetCustomAttributes(false);
object note = null;
foreach (var attr in attrs)
{
if (attr.GetType().FullName.Contains("CustomNoteAttribute"))
note = attr;
}
Assert.True(note != null, $"{callerFilePath}:{callerLineNumber}: expected a CustomNoteAttribute object on '{subject.Name}', but got null, in {callerMemberName}");
object v = note.GetType().GetField("Note").GetValue(note);
Assert.True(expectedAttributeValue.Equals(v), $"{callerFilePath}:{callerLineNumber}: expected '{expectedAttributeValue}' in CustomNoteAttribute Note field, but got '{v}', in {callerMemberName}");
}

[ConditionalFact(typeof(ApplyUpdateUtil), nameof(ApplyUpdateUtil.IsSupported))]
public static void TestReflectionAddNewType()
{
ApplyUpdateUtil.TestCase(static () =>
{
const string ns = "System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewType";
var assm = typeof(System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewType.ZExistingClass).Assembly;

var allTypes = assm.GetTypes();

CheckReflectedType(assm, allTypes, ns, "ZExistingClass");
CheckReflectedType(assm, allTypes, ns, "ZExistingClass+PreviousNestedClass");

ApplyUpdateUtil.ApplyUpdate(assm);

allTypes = assm.GetTypes();

CheckReflectedType(assm, allTypes, ns, "ZExistingClass", static (ty) =>
{
var allMethods = ty.GetMethods();

MethodInfo newMethod = null;
foreach (var meth in allMethods)
{
if (meth.Name == "NewMethod")
newMethod = meth;
}
Assert.NotNull (newMethod);

Assert.Equal (newMethod, ty.GetMethod ("NewMethod"));

var allFields = ty.GetFields(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public);

FieldInfo newField = null;
FieldInfo newStaticField = null;
foreach (var fld in allFields)
{
if (fld.Name == "NewField")
newField = fld;
if (fld.Name == "NewStaticField")
newStaticField = fld;
}
Assert.NotNull(newField);
Assert.NotNull(newStaticField);

Assert.Equal(newField, ty.GetField("NewField"));
Assert.Equal(newStaticField, ty.GetField("NewStaticField", BindingFlags.Static | BindingFlags.Public));

});
CheckReflectedType(assm, allTypes, ns, "ZExistingClass+PreviousNestedClass");
CheckReflectedType(assm, allTypes, ns, "IExistingInterface");

CheckReflectedType(assm, allTypes, ns, "ZExistingClass+NewNestedClass");

var newTy = CheckReflectedType(assm, allTypes, ns, "NewToplevelClass", static (ty) =>
{
CheckCustomNoteAttribute(ty, "123");

var nested = ty.GetNestedType("AlsoNested");
var allNested = ty.GetNestedTypes();

Assert.Equal("AlsoNested", nested.Name);
Assert.Same(ty, nested.DeclaringType);

Assert.Equal(1, allNested.Length);
Assert.Same(nested, allNested[0]);

var allInterfaces = ty.GetInterfaces();

Assert.Equal (2, allInterfaces.Length);
bool hasICloneable = false, hasINewInterface = false;
for (int i = 0; i < allInterfaces.Length; ++i) {
var itf = allInterfaces[i];
if (itf.Name == "ICloneable")
hasICloneable = true;
if (itf.Name == "IExistingInterface")
hasINewInterface = true;
}
Assert.True(hasICloneable);
Assert.True(hasINewInterface);

var allProperties = ty.GetProperties();

PropertyInfo newProp = null;
foreach (var prop in allProperties)
{
if (prop.Name == "NewProp")
newProp = prop;
}
Assert.NotNull(newProp);

Assert.Equal(newProp, ty.GetProperty("NewProp"));
MethodInfo newPropGet = newProp.GetGetMethod();
Assert.NotNull(newPropGet);
MethodInfo newPropSet = newProp.GetSetMethod();
Assert.NotNull(newPropSet);

Assert.Equal("get_NewProp", newPropGet.Name);

CheckCustomNoteAttribute (newProp, "hijkl");

var allEvents = ty.GetEvents();

EventInfo newEvt = null;
foreach (var evt in allEvents)
{
if (evt.Name == "NewEvent")
newEvt = evt;
}
Assert.NotNull(newEvt);

Assert.Equal(newEvt, ty.GetEvent("NewEvent"));
MethodInfo newEvtAdd = newEvt.GetAddMethod();
Assert.NotNull(newEvtAdd);
MethodInfo newEvtRemove = newEvt.GetRemoveMethod();
Assert.NotNull(newEvtRemove);

Assert.Equal("add_NewEvent", newEvtAdd.Name);
});
CheckReflectedType(assm, allTypes, ns, "NewGenericClass`1");
CheckReflectedType(assm, allTypes, ns, "NewToplevelStruct");
CheckReflectedType(assm, allTypes, ns, "INewInterface");
CheckReflectedType(assm, allTypes, ns, "NewEnum", static (ty) => {
var names = Enum.GetNames (ty);
Assert.Equal(3, names.Length);
var vals = Enum.GetValues (ty);
Assert.Equal(3, vals.Length);

Assert.NotNull(Enum.Parse (ty, "Red"));
Assert.NotNull(Enum.Parse (ty, "Yellow"));
});

// make some instances using reflection and use them through known interfaces
var o = Activator.CreateInstance(newTy);

var i = (System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewType.IExistingInterface)o;

Assert.Equal("123", i.ItfMethod(123));
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
<ProjectReference Include="ApplyUpdate\System.Reflection.Metadata.ApplyUpdate.Test.AddNestedClass\System.Reflection.Metadata.ApplyUpdate.Test.AddNestedClass.csproj" />
<ProjectReference Include="ApplyUpdate\System.Reflection.Metadata.ApplyUpdate.Test.AddStaticLambda\System.Reflection.Metadata.ApplyUpdate.Test.AddStaticLambda.csproj" />
<ProjectReference Include="ApplyUpdate\System.Reflection.Metadata.ApplyUpdate.Test.StaticLambdaRegression\System.Reflection.Metadata.ApplyUpdate.Test.StaticLambdaRegression.csproj" />
<ProjectReference Include="ApplyUpdate\System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewType\System.Reflection.Metadata.ApplyUpdate.Test.ReflectionAddNewType.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(TargetOS)' == 'Browser'">
<WasmFilesToIncludeFromPublishDir Include="$(AssemblyName).dll" />
Expand Down
Loading