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 UPDATEusingSystem.Reflection;publicclassTest{publicstaticvoidMain(){boolstop=false;Console.CancelKeyPress+=new((sender,ev)=>{ev.Cancel=true;stop=true;});varc0=newC();inti=0;while(!stop){Body(refi);}Console.WriteLine("stopping");GC.KeepAlive(c0);}staticvoidBody(refinti){Console.WriteLine($"alive {i}");i+=1;Thread.Sleep(1000);
#if UPDATEvarc=newC();varfi=c.GetType().GetField("MyS");vars=fi.GetValue(c);// bad!Console.WriteLine("still alive");
#endif
}classC{
#if UPDATEpublicSMyS;
#endif
publicC(){
#if UPDATEMyS=newS{D=-1985.0,O=newint[2]{15,17},};
#endif
}
#if UPDATEpublicstructS{publicdoubleD;publicobjectO;};
#endif
}}- Run
dotnet watch (but I believe this would also reproduce with EnC in Visual Studio) - Change
// #define UPDATE to #define UPDATE and 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
| 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.
Found this while working on #76462, but it also reproduces with net7 (and probably net6.0 and older, see below)
Repro:
dotnet new consoledotnet watch(but I believe this would also reproduce with EnC in Visual Studio)// #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:
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:
Windows:
Which points to
InvokeUtil::GetFieldValueruntime/src/coreclr/vm/invokeutil.cpp
Lines 1051 to 1061 in 8571fcb
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::SetValidFieldso probablyRtFieldInfo.SetField/RuntimeFieldHandle.SetFieldhas issues too.