From 46d7acb102c5a694efc7142fb18dedf8dadda51d Mon Sep 17 00:00:00 2001 From: "monomod[bot]" <213363817+monomod[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 02:04:45 +0000 Subject: [PATCH 1/6] Add test for IL hook InvalidProgramException on Mono runtime --- src/MonoMod.UnitTest/Github/Issue230.cs | 118 ++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 src/MonoMod.UnitTest/Github/Issue230.cs diff --git a/src/MonoMod.UnitTest/Github/Issue230.cs b/src/MonoMod.UnitTest/Github/Issue230.cs new file mode 100644 index 00000000..beb36763 --- /dev/null +++ b/src/MonoMod.UnitTest/Github/Issue230.cs @@ -0,0 +1,118 @@ +extern alias New; +using New::MonoMod.RuntimeDetour; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using Mono.Cecil.Cil; +using MonoMod.Cil; +using Xunit; +using Xunit.Abstractions; + +namespace MonoMod.UnitTest.Github +{ + public class Issue230 : TestBase + { + public Issue230(ITestOutputHelper helper) : base(helper) + { + } + + private class SomeType + { + public int index { get; set; } + } + + private static readonly List[] _list = new List[3]; + + [Fact] + public void ILHookOnMonoShouldSucceed() + { + for (var i = 0; i < 3; i++) + { + _list[i] = new List(3); + } + + var original = typeof(Issue230).GetMethod(nameof(Original), BindingFlags.Static | BindingFlags.NonPublic); + + // Verify that applying multiple ILHooks does not throw an exception + using (new ILHook(original, Hook1)) + using (new ILHook(original, Hook2)) + { + // Running the method should succeed + Original(); + } + } + + private static void Original() + { + var random = new Random(); + var enumerable = Enumerable.Range(0, 50).Select(i => new SomeType { index = random.Next(0, 3) }); + + foreach (var item in enumerable) + { + _ = _list[item.index]; + } + } + + private static void Hook1(ILContext il) + { + var cursor = new ILCursor(il); + + cursor.GotoNext( + MoveType.After, + x => x.Match(OpCodes.Ldloc_2), + x => x.Match(OpCodes.Callvirt), + x => x.Match(OpCodes.Ldelem_Ref)); + + cursor.Index--; + + cursor.Emit(OpCodes.Ldc_I4_0); + cursor.Emit(OpCodes.Ldc_I4_3); + cursor.EmitCall(typeof(Issue230).GetMethod(nameof(Clamp), new[] { typeof(int), typeof(int), typeof(int) })); + } + + private static void Hook2(ILContext il) + { + var cursor = new ILCursor(il); + + if (!cursor.TryGotoNext( + MoveType.After, + x => x.Match(OpCodes.Ldloc_2), + x => x.Match(OpCodes.Callvirt), + x => x.Match(OpCodes.Ldelem_Ref))) + { + cursor.Index = cursor.Instrs.Count; + } + else + { + cursor.Index--; + } + + cursor.Emit(OpCodes.Ldc_I4_0); + cursor.Emit(OpCodes.Ldc_I4_3); + cursor.EmitCall(typeof(Issue230).GetMethod(nameof(Clamp), new[] { typeof(int), typeof(int), typeof(int) })!); + + if (!cursor.TryGotoNext( + MoveType.Before, + x => x.Match(OpCodes.Ldloc_1), + x => x.Match(OpCodes.Callvirt), + x => x.Match(OpCodes.Stloc_S))) + { + cursor.Index = cursor.Instrs.Count; + } + + cursor.Emit(OpCodes.Ret); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Clamp(int value, int min, int max) + { + if (min > max) + throw new ArgumentException($"'{min}' cannot be greater than {max}."); + if (value < min) + return min; + return value > max ? max : value; + } + } +} \ No newline at end of file From 43c115bb94646537f50b8e22801d9ef8623b07cb Mon Sep 17 00:00:00 2001 From: nike4613 Date: Sat, 31 May 2025 02:27:30 +0000 Subject: [PATCH 2/6] Match any ldloc var in test --- src/MonoMod.UnitTest/Github/Issue230.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/MonoMod.UnitTest/Github/Issue230.cs b/src/MonoMod.UnitTest/Github/Issue230.cs index beb36763..9d22088b 100644 --- a/src/MonoMod.UnitTest/Github/Issue230.cs +++ b/src/MonoMod.UnitTest/Github/Issue230.cs @@ -47,7 +47,7 @@ public void ILHookOnMonoShouldSucceed() private static void Original() { var random = new Random(); - var enumerable = Enumerable.Range(0, 50).Select(i => new SomeType { index = random.Next(0, 3) }); + var enumerable = Enumerable.Range(0, 50).Select(i => new SomeType { index = random.Next(0, 5) }); foreach (var item in enumerable) { @@ -61,7 +61,7 @@ private static void Hook1(ILContext il) cursor.GotoNext( MoveType.After, - x => x.Match(OpCodes.Ldloc_2), + x => x.MatchLdloc(out _), x => x.Match(OpCodes.Callvirt), x => x.Match(OpCodes.Ldelem_Ref)); @@ -78,7 +78,7 @@ private static void Hook2(ILContext il) if (!cursor.TryGotoNext( MoveType.After, - x => x.Match(OpCodes.Ldloc_2), + x => x.MatchLdloc(out _), x => x.Match(OpCodes.Callvirt), x => x.Match(OpCodes.Ldelem_Ref))) { @@ -95,7 +95,7 @@ private static void Hook2(ILContext il) if (!cursor.TryGotoNext( MoveType.Before, - x => x.Match(OpCodes.Ldloc_1), + x => x.MatchLdloc(out _), x => x.Match(OpCodes.Callvirt), x => x.Match(OpCodes.Stloc_S))) { From 311cc4ad221909b97d27bad1e74ee0da500a9e6f Mon Sep 17 00:00:00 2001 From: nike4613 Date: Sat, 31 May 2025 02:44:38 +0000 Subject: [PATCH 3/6] Mark original NoInlining so the test can ever pass --- src/MonoMod.UnitTest/Github/Issue230.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/MonoMod.UnitTest/Github/Issue230.cs b/src/MonoMod.UnitTest/Github/Issue230.cs index 9d22088b..72bcc6a1 100644 --- a/src/MonoMod.UnitTest/Github/Issue230.cs +++ b/src/MonoMod.UnitTest/Github/Issue230.cs @@ -44,6 +44,7 @@ public void ILHookOnMonoShouldSucceed() } } + [MethodImpl(MethodImplOptions.NoInlining)] private static void Original() { var random = new Random(); From 8bd972b678e1655978538e8ca6bcf60ff4cad89a Mon Sep 17 00:00:00 2001 From: Meivyn <793322+Meivyn@users.noreply.github.com> Date: Thu, 28 Aug 2025 15:28:05 -0400 Subject: [PATCH 4/6] Use `MatchStloc` so test can pass --- src/MonoMod.UnitTest/Github/Issue230.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MonoMod.UnitTest/Github/Issue230.cs b/src/MonoMod.UnitTest/Github/Issue230.cs index 72bcc6a1..4357f7e6 100644 --- a/src/MonoMod.UnitTest/Github/Issue230.cs +++ b/src/MonoMod.UnitTest/Github/Issue230.cs @@ -98,7 +98,7 @@ private static void Hook2(ILContext il) MoveType.Before, x => x.MatchLdloc(out _), x => x.Match(OpCodes.Callvirt), - x => x.Match(OpCodes.Stloc_S))) + x => x.MatchStloc(out _))) { cursor.Index = cursor.Instrs.Count; } @@ -116,4 +116,4 @@ public static int Clamp(int value, int min, int max) return value > max ? max : value; } } -} \ No newline at end of file +} From 444605a5cc0d891084fd06c331cee5d872bfbcd8 Mon Sep 17 00:00:00 2001 From: Meivyn <793322+Meivyn@users.noreply.github.com> Date: Thu, 28 Aug 2025 15:30:56 -0400 Subject: [PATCH 5/6] Fix random logic --- src/MonoMod.UnitTest/Github/Issue230.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MonoMod.UnitTest/Github/Issue230.cs b/src/MonoMod.UnitTest/Github/Issue230.cs index 72bcc6a1..234afba2 100644 --- a/src/MonoMod.UnitTest/Github/Issue230.cs +++ b/src/MonoMod.UnitTest/Github/Issue230.cs @@ -48,7 +48,7 @@ public void ILHookOnMonoShouldSucceed() private static void Original() { var random = new Random(); - var enumerable = Enumerable.Range(0, 50).Select(i => new SomeType { index = random.Next(0, 5) }); + var enumerable = Enumerable.Range(0, 50).Select(i => new SomeType { index = random.Next(50) }); foreach (var item in enumerable) { @@ -116,4 +116,4 @@ public static int Clamp(int value, int min, int max) return value > max ? max : value; } } -} \ No newline at end of file +} From ab402bf40c78add90723ce3d54d9627885b7bc85 Mon Sep 17 00:00:00 2001 From: Meivyn <793322+Meivyn@users.noreply.github.com> Date: Thu, 28 Aug 2025 16:21:03 -0400 Subject: [PATCH 6/6] Fix out of range exception --- src/MonoMod.UnitTest/Github/Issue230.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MonoMod.UnitTest/Github/Issue230.cs b/src/MonoMod.UnitTest/Github/Issue230.cs index 4357f7e6..26e45ffb 100644 --- a/src/MonoMod.UnitTest/Github/Issue230.cs +++ b/src/MonoMod.UnitTest/Github/Issue230.cs @@ -23,7 +23,7 @@ private class SomeType public int index { get; set; } } - private static readonly List[] _list = new List[3]; + private static readonly List[] _list = new List[4]; [Fact] public void ILHookOnMonoShouldSucceed()