-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Description
Found this while working on #76462, but it also reproduces with net7 (and probably net6.0 and older, see below)
Repro:
dotnet new console- Use the following for Program.cs:
// #define UPDATE
using System.Reflection;
public class Test {
public static void Main ()
{
bool stop = false;
Console.CancelKeyPress += new ((sender, ev) => {
ev.Cancel = true;
stop = true;
});
var c0 = new C();
int i = 0;
while (!stop) {
Body (ref i);
}
Console.WriteLine ("stopping");
GC.KeepAlive (c0);
}
static void Body (ref int i)
{
Console.WriteLine($"alive {i}");
i += 1;
Thread.Sleep (1000);
#if UPDATE
var c = new C();
var fi = c.GetType().GetField ("MyS");
var s = fi.GetValue (c); // bad!
Console.WriteLine ("still alive");
#endif
}
class C {
#if UPDATE
public S MyS;
#endif
public C() {
#if UPDATE
MyS = new S {
D = -1985.0,
O = new int[2] {15, 17},
};
#endif
}
#if UPDATE
public struct S {
public double D;
public object O;
};
#endif
}
}- Run
dotnet watch(but I believe this would also reproduce with EnC in Visual Studio) - Change
// #define UPDATEto#define UPDATEand save (or hit "Apply Changes" in VS)
Expected result:
The hot reload change is applied and the application keeps running.
Actual result:
dotnet watch 🚀 Started
alive 0
alive 1
alive 2
alive 3
alive 4
alive 5
alive 6
dotnet watch ⌚ File changed: ./Program.cs.
alive 7
dotnet watch 🔥 Hot reload of changes succeeded.
alive 8
dotnet watch ❌ Exited with error code 139
dotnet watch ⏳ Waiting for a file to change before restarting dotnet...
dotnet watch 🛑 Shutdown requested. Press Ctrl+C again to force exit.
That is, the child process - dotnet running the user code crashed with exit code 139.
In #76462 we actually get a good combination of error messages from a CoreCLR builds on OSX and Windows, respectively:
Mac:
Assert failure(PID 21236 [0x000052f4], Thread: 191148 [0x2eaac]): IS_ALIGNED(src, sizeof(SIZE_T))
File: /Users/runner/work/1/s/src/coreclr/classlibnative/bcltype/arraynative.cpp Line: 736
Image: /private/tmp/helix/working/B02E09C7/p/dotnet
Windows:
Fatal error. Internal CLR error. (0x80131506)
at System.RuntimeFieldHandle.GetValue(System.Reflection.RtFieldInfo, System.Object, System.RuntimeType, System.RuntimeType, Boolean ByRef)
at System.Reflection.RtFieldInfo.GetValue(System.Object)
at System.Reflection.Metadata.ApplyUpdateTest+<>c.<TestAddInstanceField>b__10_0()
at System.RuntimeMethodHandle.InvokeMethod(System.Object, Void**, System.Signature, Boolean)
at System.Reflection.MethodInvoker.InterpretedInvoke(System.Object, IntPtr*)
at System.Reflection.MethodInvoker.Invoke(System.Object, IntPtr*, System.Reflection.BindingFlags)
at System.Reflection.MethodInvoker.InlinedInvoke(System.Object, IntPtr*, System.Reflection.BindingFlags)
at System.Reflection.RuntimeMethodInfo.Invoke(System.Object, System.Reflection.BindingFlags, System.Reflection.Binder, System.Object[], System.Globalization.CultureInfo)
at System.Reflection.MethodBase.Invoke(System.Object, System.Object[])
at Microsoft.DotNet.RemoteExecutor.Program.Main(System.String[])
Which points to InvokeUtil::GetFieldValue
runtime/src/coreclr/vm/invokeutil.cpp
Lines 1051 to 1061 in 8571fcb
| if (pField->IsStatic()) | |
| p = pField->GetCurrentStaticAddress(); | |
| else { | |
| p = (*((BYTE**)target)) + pField->GetOffset() + sizeof(Object); | |
| } | |
| GCPROTECT_END(); | |
| // copy the field to the unboxed object. | |
| // note: this will be done only for the non-remoting case | |
| if (p) { | |
| CopyValueClass(obj->GetData(), p, fieldType.AsMethodTable()); |
This looks wrong - p = (*((BYTE**)target)) + pField->GetOffset() + sizeof(Object); for fields added by EnC - they aren't at some offset, they're allocated entirely separately from the target object.
By the way, the same wrong logic is also in InvokeUtil::SetValidField so probably RtFieldInfo.SetField/RuntimeFieldHandle.SetField has issues too.