pardeike / Harmony

A library for patching, replacing and decorating .NET and Mono methods during runtime
https://www.patreon.com/pardeike
MIT License
5.28k stars 492 forks source link

Crash when accessing patched method through interface #545

Closed 2767mr closed 10 months ago

2767mr commented 1 year ago

When accessing a patched method through an interface, it will cause an internal CLR error. This only happens with .NET 7 or above.

To reproduce:

Code
using HarmonyLib;

Main();

void Main()
{
    var harmony = new Harmony("test");
    var info = typeof(Foo).GetMethod(nameof(Foo.Test)) ?? throw new Exception("Could not find method info");
    var prefixInfo = typeof(Example).GetMethod(nameof(Example.Prefix)) ?? throw new Exception("Could not find Prefix method info");
    harmony.Patch(info, new HarmonyMethod(prefixInfo));

    ((Bar)new Foo()).Test();
}

class Example
{
    public static void Prefix()
    {
        Console.WriteLine("hi");
    }
}

public interface Bar
{
    string Test();
}

public class Foo : Bar
{
    public string Test()
    {
        return "hi";
    }
}

Crash ``` Fatal error. Internal CLR error. (0x80131506) at Bar.Test() at Program.<
$>g__Main|0_0() at Program.
$(System.String[]) ```
Harmony debug log ``` ### Harmony id=test, version=2.2.2.0, location=C:\path\to\project\bin\Debug\net7.0\0Harmony.dll, env/clr=7.0.5, platform=Win32NT, ptrsize:runtime/env=8/Bits64, Windows ### Started from static System.Void Program::<
$>g__Main|0_0(), location T:\Projects\Tracer\Tracer.Example\bin\Debug\net7.0\Tracer.Example.dll ### At 2023-08-31 05.29.25 ### Patch: virtual System.String Foo::Test() ### Replacement: static System.String Foo::Foo.Test_Patch1(Foo this) IL_0000: Local var 0: System.String IL_0000: Local var 1: System.String IL_0000: ldnull IL_0001: stloc 1 (System.String) IL_0005: call static System.Void Example::Prefix() IL_000A: // start original IL_000A: nop IL_000B: ldstr "hi" IL_0010: stloc.0 IL_0011: br => Label0 IL_0016: Label0 IL_0016: ldloc.0 IL_0017: // end original IL_0017: stloc 1 (System.String) IL_001B: ldloc 1 (System.String) IL_001F: ret DONE ```

Runtime environment:

pardeike commented 1 year ago

Any ideas @nike4613 ?

nike4613 commented 1 year ago

The interface method being in the stack trace is a bit sus, but I'd need to look at it in WinDbg to understand the problem. (Also, isn't 2.2.2 pre-MM.Core?)

pardeike commented 1 year ago

Yes my bad. This should be tested with master to verify MonoMod.Core functionality. I thought you might chime in on the general usage of an interface here.

nike4613 commented 1 year ago

I can't even guess why its happening, but it might have to do with using a version that doesn't support the runtime.

pardeike commented 1 year ago

I tested with LinqPad 7 and NET7 and I cannot reproduce:

void Main()
{
    Harmony.VersionInfo(out var version);
    Console.WriteLine(version);
    Console.WriteLine(Environment.Version);

    var harmony = new Harmony("test");
    var info = typeof(Foo).GetMethod(nameof(Foo.Test)) ?? throw new Exception("Could not find method info");
    var prefixInfo = typeof(Example).GetMethod(nameof(Example.Prefix)) ?? throw new Exception("Could not find Prefix method info");
    harmony.Patch(info, new HarmonyMethod(prefixInfo));

    var s = ((Bar)new Foo()).Test();
    Console.WriteLine(s);
}

class Example
{
    public static void Prefix()
    {
        Console.WriteLine("Prefix");
    }
}

public interface Bar
{
    string Test();
}

public class Foo : Bar
{
    public string Test()
    {
        return "Test Result";
    }
}

prints

2.2.2.0
7.0.10
Prefix
Test Result

I also tested with prerelease and it prints:

2.3.0.0
7.0.10
Prefix
Test Result
2767mr commented 1 year ago

I discovered that the crash only happens when I use the Visual Studio (2022, community, latest) debugger. However, on my machine dotnet run still prints a wrong result:

2.2.2.0
7.0.10
Test Result
2767mr commented 1 year ago

I made a Dockerfile to (hopefully) reproduce the issue: Dockerfile.txt docker build -t bug . docker run --rm -it bug

Here is also a .NET Fiddle

2767mr commented 1 year ago

Prerelease does work properly. Will use that as a workaround for now.