From 349a89c1fac5f075acf3d2bd803307b7248a26d1 Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Sat, 29 Mar 2025 17:57:43 +0100
Subject: [PATCH] Fix #3436: Duplicate naming of local functions

---
 .../TestCases/Pretty/DelegateConstruction.cs  |  32 ++--
 .../TestCases/Pretty/LocalFunctions.cs        | 154 +++++++++---------
 .../IL/Transforms/AssignVariableNames.cs      |  44 +++--
 ILSpy/Properties/Resources.resx               |   6 +-
 4 files changed, 125 insertions(+), 111 deletions(-)

diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs
index d7566dced..8b96d4a6f 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs
@@ -183,7 +183,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction
 
 		public interface IM3
 		{
-			void M3();
+			void M();
 		}
 
 		public class BaseClass : IM3
@@ -194,7 +194,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction
 			protected virtual void M2()
 			{
 			}
-			public virtual void M3()
+			public virtual void M()
 			{
 			}
 
@@ -209,7 +209,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction
 			protected override void M2()
 			{
 			}
-			public new void M3()
+			public new void M()
 			{
 			}
 
@@ -219,30 +219,30 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction
 				Noop("M1", M1);
 				Noop("M2.base", base.M2);
 				Noop("M2", M2);
-				Noop("M3.base", base.M3);
-				Noop("M3.base_virt", ((BaseClass)this).M3);
-				Noop("M3.base_interface", ((IM3)this).M3);
+				Noop("M.base", base.M);
+				Noop("M.base_virt", ((BaseClass)this).M);
+				Noop("M.base_interface", ((IM3)this).M);
 #if CS70
-				Noop("M3", this.M3);
-				Noop("M3", M3);
+				Noop("M", this.M);
+				Noop("M", M);
 
 #if CS80
-				static void M3()
+				static void M()
 #else
-				void M3()
+				void M()
 #endif
 				{
 
 				}
 #else
-				Noop("M3", M3);
+				Noop("M", M);
 #endif
 			}
 
 			public void Test2()
 			{
-				Noop("M3.new", new BaseClass().M3);
-				Noop("M3.new", new SubClass().M3);
+				Noop("M.new", new BaseClass().M);
+				Noop("M.new", new SubClass().M);
 			}
 
 			private void Noop(string name, Action _)
@@ -525,7 +525,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction
 		public static void LocalFunctionDelegateReference()
 		{
 			Use(LocalFunction);
-			Use2<int>(LocalFunction1<int>);
+			Use2<int>(LocalFunction2<int>);
 #if CS80
 			static void LocalFunction()
 #else
@@ -534,9 +534,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction
 			{
 			}
 #if CS80
-			static void LocalFunction1<T>()
+			static void LocalFunction2<T>()
 #else
-			void LocalFunction1<T>()
+			void LocalFunction2<T>()
 #endif
 			{
 			}
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs
index ff29a4351..36c2d340a 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs
@@ -42,50 +42,50 @@ namespace LocalFunctions
 				for (int i = 0; i < 10; i++)
 				{
 					int i2 = 0;
-					i2 += NonStaticMethod6<object>(0);
+					i2 += NonStaticMethod<object>(0);
 #if CS90
 					[My]
 					[return: My]
-					int NonStaticMethod6<[My] T3>([My] int unused)
+					int NonStaticMethod<[My] T3>([My] int unused)
 #else
-					int NonStaticMethod6<T3>(int unused)
+					int NonStaticMethod<T3>(int unused)
 #endif
 					{
 						t2 = default(T2);
 						int l = 0;
-						return NonStaticMethod6_1<T1>() + NonStaticMethod6_1<T2>() + z.GetHashCode();
-						int NonStaticMethod6_1<T4>()
+						return NonStaticMethod2<T1>() + NonStaticMethod2<T2>() + z.GetHashCode();
+						int NonStaticMethod2<T4>()
 						{
-							return i2 + l + NonStaticMethod6<T4>(0) + StaticMethod1<decimal>();
+							return i2 + l + NonStaticMethod<T4>(0) + StaticMethod<decimal>();
 						}
 					}
 				}
-				return MixedLocalFunction<T1>() + MixedLocalFunction<T2>() + StaticMethod1<decimal>() + StaticMethod1<int>() + NonStaticMethod3() + StaticMethod4<object>(null) + StaticMethod5<T1>();
+				return MixedLocalFunction<T1>() + MixedLocalFunction<T2>() + StaticMethod<decimal>() + StaticMethod<int>() + NonStaticMethod3() + StaticMethod4<object>(null) + StaticMethod5<T1>();
 				int NonStaticMethod3()
 				{
 					return GetHashCode();
 				}
 #if CS80
-				static int StaticMethod1<T3>() where T3 : struct
+				static int StaticMethod<T3>() where T3 : struct
 #else
-				int StaticMethod1<T3>() where T3 : struct
+				int StaticMethod<T3>() where T3 : struct
 #endif
 				{
-					return typeof(T1).Name.Length + typeof(T2).Name.Length + typeof(T3).Name.Length + StaticMethod1<float>() + StaticMethod1_1<T3, DayOfWeek>() + StaticMethod2_RepeatT2<T2, T3, DayOfWeek>();
+					return typeof(T1).Name.Length + typeof(T2).Name.Length + typeof(T3).Name.Length + StaticMethod<float>() + StaticMethod2<T3, DayOfWeek>() + StaticMethod3<T2, T3, DayOfWeek>();
 				}
 #if CS80
-				static int StaticMethod1_1<T3, T4>() where T3 : struct where T4 : Enum
+				static int StaticMethod2<T3, T4>() where T3 : struct where T4 : Enum
 #else
-				int StaticMethod1_1<T3, T4>() where T3 : struct where T4 : Enum
+				int StaticMethod2<T3, T4>() where T3 : struct where T4 : Enum
 #endif
 				{
-					return typeof(T1).Name.Length + typeof(T2).Name.Length + typeof(T3).Name.Length + typeof(T4).Name.Length + StaticMethod1<float>() + StaticMethod1_1<T3, DayOfWeek>();
+					return typeof(T1).Name.Length + typeof(T2).Name.Length + typeof(T3).Name.Length + typeof(T4).Name.Length + StaticMethod<float>() + StaticMethod2<T3, DayOfWeek>();
 				}
 #pragma warning disable CS8387
 #if CS80
-				static int StaticMethod2_RepeatT2<T2, T3, T4>() where T2 : IConvertible where T3 : struct where T4 : Enum
+				static int StaticMethod3<T2, T3, T4>() where T2 : IConvertible where T3 : struct where T4 : Enum
 #else
-				int StaticMethod2_RepeatT2<T2, T3, T4>() where T2 : IConvertible where T3 : struct where T4 : Enum
+				int StaticMethod3<T2, T3, T4>() where T2 : IConvertible where T3 : struct where T4 : Enum
 #endif
 #pragma warning restore CS8387
 				{
@@ -106,8 +106,8 @@ namespace LocalFunctions
 #endif
 				{
 					int k = 0;
-					return k + NonStaticMethod5_1<T1>();
-					int NonStaticMethod5_1<T4>()
+					return k + NonStaticMethod4<T1>();
+					int NonStaticMethod4<T4>()
 					{
 						return k;
 					}
@@ -122,20 +122,20 @@ namespace LocalFunctions
 				for (int i = 0; i < 10; i++)
 				{
 					int i2 = 0;
-					i2 += StaticInvokeAsFunc(NonStaticMethod6<object>);
-					int NonStaticMethod6<T3>()
+					i2 += StaticInvokeAsFunc(NonStaticMethod<object>);
+					int NonStaticMethod<T3>()
 					{
 						t2 = default(T2);
 						int l = 0;
-						return StaticInvokeAsFunc(NonStaticMethod6_1<T1>) + StaticInvokeAsFunc(NonStaticMethod6_1<T2>) + z.GetHashCode();
-						int NonStaticMethod6_1<T4>()
+						return StaticInvokeAsFunc(NonStaticMethod2<T1>) + StaticInvokeAsFunc(NonStaticMethod2<T2>) + z.GetHashCode();
+						int NonStaticMethod2<T4>()
 						{
-							return i2 + l + StaticInvokeAsFunc(NonStaticMethod6<T4>) + StaticInvokeAsFunc(StaticMethod1<decimal>);
+							return i2 + l + StaticInvokeAsFunc(NonStaticMethod<T4>) + StaticInvokeAsFunc(StaticMethod<decimal>);
 						}
 					}
 				}
 				Console.WriteLine(t2);
-				return StaticInvokeAsFunc(MixedLocalFunction2Delegate<T1>) + StaticInvokeAsFunc(MixedLocalFunction2Delegate<T2>) + StaticInvokeAsFunc(StaticMethod1<decimal>) + StaticInvokeAsFunc(StaticMethod1<int>) + StaticInvokeAsFunc(NonStaticMethod3) + StaticInvokeAsFunc(StaticMethod5<T1>) + new Func<object, int>(StaticMethod4<object>)(null) + StaticInvokeAsFunc2<object>(StaticMethod4<object>) + new Func<Func<object, int>, int>(StaticInvokeAsFunc2<object>)(StaticMethod4<object>);
+				return StaticInvokeAsFunc(MixedLocalFunction2Delegate<T1>) + StaticInvokeAsFunc(MixedLocalFunction2Delegate<T2>) + StaticInvokeAsFunc(StaticMethod<decimal>) + StaticInvokeAsFunc(StaticMethod<int>) + StaticInvokeAsFunc(NonStaticMethod3) + StaticInvokeAsFunc(StaticMethod4<T1>) + new Func<object, int>(StaticMethod5<object>)(null) + StaticInvokeAsFunc2<object>(StaticMethod5<object>) + new Func<Func<object, int>, int>(StaticInvokeAsFunc2<object>)(StaticMethod5<object>);
 				int NonStaticMethod3()
 				{
 					return GetHashCode();
@@ -157,51 +157,51 @@ namespace LocalFunctions
 					return func(default(T));
 				}
 #if CS80
-				static int StaticMethod1<T3>() where T3 : struct
+				static int StaticMethod<T3>() where T3 : struct
 #else
-				int StaticMethod1<T3>() where T3 : struct
+				int StaticMethod<T3>() where T3 : struct
 #endif
 				{
-					return typeof(T1).Name.Length + typeof(T2).Name.Length + typeof(T3).Name.Length + StaticInvokeAsFunc(StaticMethod1<float>) + StaticInvokeAsFunc(StaticMethod1_1<T3, DayOfWeek>) + StaticInvokeAsFunc(StaticMethod2_RepeatT2<T2, T3, DayOfWeek>);
+					return typeof(T1).Name.Length + typeof(T2).Name.Length + typeof(T3).Name.Length + StaticInvokeAsFunc(StaticMethod<float>) + StaticInvokeAsFunc(StaticMethod2<T3, DayOfWeek>) + StaticInvokeAsFunc(StaticMethod3<T2, T3, DayOfWeek>);
 				}
 #if CS80
-				static int StaticMethod1_1<T3, T4>() where T3 : struct where T4 : Enum
+				static int StaticMethod2<T3, T4>() where T3 : struct where T4 : Enum
 #else
-				int StaticMethod1_1<T3, T4>() where T3 : struct where T4 : Enum
+				int StaticMethod2<T3, T4>() where T3 : struct where T4 : Enum
 #endif
 				{
-					return typeof(T1).Name.Length + typeof(T2).Name.Length + typeof(T3).Name.Length + typeof(T4).Name.Length + StaticInvokeAsFunc(StaticMethod1<float>) + StaticInvokeAsFunc(StaticMethod1_1<T3, DayOfWeek>);
+					return typeof(T1).Name.Length + typeof(T2).Name.Length + typeof(T3).Name.Length + typeof(T4).Name.Length + StaticInvokeAsFunc(StaticMethod<float>) + StaticInvokeAsFunc(StaticMethod2<T3, DayOfWeek>);
 				}
 #pragma warning disable CS8387
 #if CS80
-				static int StaticMethod2_RepeatT2<T2, T3, T4>() where T2 : IConvertible where T3 : struct where T4 : Enum
+				static int StaticMethod3<T2, T3, T4>() where T2 : IConvertible where T3 : struct where T4 : Enum
 #else
-				int StaticMethod2_RepeatT2<T2, T3, T4>() where T2 : IConvertible where T3 : struct where T4 : Enum
+				int StaticMethod3<T2, T3, T4>() where T2 : IConvertible where T3 : struct where T4 : Enum
 #endif
 #pragma warning restore CS8387
 				{
 					return typeof(T2).Name.Length;
 				}
 #if CS80
-				static int StaticMethod4<T>(T dd)
+				static int StaticMethod4<T3>()
 #else
-				int StaticMethod4<T>(T dd)
+				int StaticMethod4<T3>()
 #endif
 				{
-					return 0;
+					int k = 0;
+					return k + StaticInvokeAsFunc(NonStaticMethod4<T1>);
+					int NonStaticMethod4<T4>()
+					{
+						return k;
+					}
 				}
 #if CS80
-				static int StaticMethod5<T3>()
+				static int StaticMethod5<T>(T dd)
 #else
-				int StaticMethod5<T3>()
+				int StaticMethod5<T>(T dd)
 #endif
 				{
-					int k = 0;
-					return k + StaticInvokeAsFunc(NonStaticMethod5_1<T1>);
-					int NonStaticMethod5_1<T4>()
-					{
-						return k;
-					}
+					return 0;
 				}
 			}
 
@@ -209,14 +209,14 @@ namespace LocalFunctions
 			{
 #pragma warning disable CS0219
 				T2 t2 = default(T2);
-				Method1<int>();
-				void Method1<T3>()
+				Method<int>();
+				void Method<T3>()
 				{
 					t2 = default(T2);
 					T2 t2x = t2;
 					T3 t3 = default(T3);
-					Method1_1();
-					void Method1_1()
+					Method2();
+					void Method2()
 					{
 						t2 = default(T2);
 						t2x = t2;
@@ -229,7 +229,7 @@ namespace LocalFunctions
 			public void TestGenericArgs<T2>() where T2 : List<T2>
 			{
 				ZZ<T2>(null);
-				ZZ2<object>(null);
+				ZZ3<object>(null);
 #if CS80
 				static void Nop<T>(T data)
 #else
@@ -244,25 +244,25 @@ namespace LocalFunctions
 #endif
 				{
 					Nop<List<T2>>(t3);
-					ZZ1<T3>(t3);
-					ZZ3();
-					void ZZ3()
+					ZZ2<T3>(t3);
+					ZZ4();
+					void ZZ4()
 					{
 						Nop<List<T2>>(t3);
 					}
 				}
 #if CS80
-				static void ZZ1<T3>(T3 t3)
+				static void ZZ2<T3>(T3 t3)
 #else
-				void ZZ1<T3>(T3 t3)
+				void ZZ2<T3>(T3 t3)
 #endif
 				{
 					Nop<List<T2>>((List<T2>)(object)t3);
 				}
 #if CS80
-				static void ZZ2<T3>(T3 t3)
+				static void ZZ3<T3>(T3 t3)
 #else
-				void ZZ2<T3>(T3 t3)
+				void ZZ3<T3>(T3 t3)
 #endif
 				{
 					Nop<List<T2>>((List<T2>)(object)t3);
@@ -695,22 +695,22 @@ namespace LocalFunctions
 
 		public void NestedCapture1()
 		{
-			Method1(null);
+			Method(null);
 
 #if CS80
-			static Action<object> Method1(Action<object> action)
+			static Action<object> Method(Action<object> action)
 #else
-			Action<object> Method1(Action<object> action)
+			Action<object> Method(Action<object> action)
 #endif
 			{
-				return Method1_1;
+				return Method2;
 
-				void Method1_1(object containerBuilder)
+				void Method2(object containerBuilder)
 				{
-					Method1_2(containerBuilder);
+					Method3(containerBuilder);
 				}
 
-				void Method1_2(object containerBuilder)
+				void Method3(object containerBuilder)
 				{
 					action(containerBuilder);
 				}
@@ -727,25 +727,25 @@ namespace LocalFunctions
 #endif
 			{
 				int t0 = 0;
-				return ZZZ_0();
-				int ZZZ_0()
+				return ZZZ();
+				int ZZZ()
 				{
 					t0 = 0;
 					int t2 = t0;
-					return new Func<int>(ZZZ_0_0)();
-					int ZZZ_0_0()
+					return new Func<int>(ZZZ2)();
+					int ZZZ2()
 					{
 						t0 = 0;
 						t2 = 0;
-						return ZZZ_1();
+						return ZZZ3();
 					}
 				}
-				int ZZZ_1()
+				int ZZZ3()
 				{
 					t0 = 0;
 					int t3 = t0;
-					return new Func<int>(ZZZ_1_0)();
-					int ZZZ_1_0()
+					return new Func<int>(ZZZ4)();
+					int ZZZ4()
 					{
 						t0 = 0;
 						t3 = 0;
@@ -765,18 +765,18 @@ namespace LocalFunctions
 #endif
 			{
 				int t0 = 0;
-				return ZZZ_0();
-				int ZZZ_0()
+				return ZZZ();
+				int ZZZ()
 				{
 					t0 = 0;
 					int t2 = t0;
 					return ((Func<int>)delegate {
 						t0 = 0;
 						t2 = 0;
-						return ZZZ_1();
+						return ZZZ2();
 					})();
 				}
-				int ZZZ_1()
+				int ZZZ2()
 				{
 					t0 = 0;
 					int t3 = t0;
@@ -808,18 +808,18 @@ namespace LocalFunctions
 #endif
 			{
 				int t0 = 0;
-				return ZZZ_0() + ZZZ_1();
-				int ZZZ_0()
+				return ZZZ() + ZZZ2();
+				int ZZZ()
 				{
 					t0 = 0;
 					int t2 = t0;
 					return ((Func<int>)delegate {
 						t0 = 0;
 						t2 = 0;
-						return ZZZ_1();
+						return ZZZ2();
 					})();
 				}
-				int ZZZ_1()
+				int ZZZ2()
 				{
 					t0 = 0;
 					int t3 = t0;
diff --git a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs
index dbe8c5dd4..81dbef08e 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs
@@ -512,23 +512,37 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 				// assign names to local functions
 				if (!LocalFunctionDecompiler.ParseLocalFunctionName(function.Name, out _, out var newName) || !IsValidName(newName))
 					newName = null;
-				if (newName == null)
+				string nameWithoutNumber;
+				int number;
+				if (!string.IsNullOrEmpty(newName))
 				{
-					string nameWithoutNumber = "f";
-					if (!context.IsReservedVariableName(nameWithoutNumber, out int currentIndex))
-					{
-						currentIndex = 1;
-					}
-					int count = Math.Max(1, currentIndex) + 1;
-					context.ReserveVariableName(nameWithoutNumber, count);
-					if (count > 1)
-					{
-						newName = nameWithoutNumber + count.ToString();
-					}
+					nameWithoutNumber = SplitName(newName, out number);
+				}
+				else
+				{
+					nameWithoutNumber = "f";
+					number = 1;
+				}
+				int count;
+				if (!context.IsReservedVariableName(nameWithoutNumber, out int currentIndex))
+				{
+					count = 1;
+				}
+				else
+				{
+					if (currentIndex < number)
+						count = number;
 					else
-					{
-						newName = nameWithoutNumber;
-					}
+						count = Math.Max(number, currentIndex) + 1;
+				}
+				context.ReserveVariableName(nameWithoutNumber, count);
+				if (count > 1)
+				{
+					newName = nameWithoutNumber + count.ToString();
+				}
+				else
+				{
+					newName = nameWithoutNumber;
 				}
 				function.Name = newName;
 				function.ReducedMethod.Name = newName;
diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx
index 8b6e4ce23..3c23e3ed5 100644
--- a/ILSpy/Properties/Resources.resx
+++ b/ILSpy/Properties/Resources.resx
@@ -381,6 +381,9 @@ Are you sure you want to continue?</value>
   <data name="DecompilerSettings.IntroduceLocalFunctions" xml:space="preserve">
     <value>Introduce local functions</value>
   </data>
+  <data name="DecompilerSettings.IntroducePrivateProtectedAccessibility" xml:space="preserve">
+    <value>Introduce 'private protected' accessibility</value>
+  </data>
   <data name="DecompilerSettings.IntroduceStaticLocalFunctions" xml:space="preserve">
     <value>Introduce static local functions</value>
   </data>
@@ -1108,7 +1111,4 @@ Do you want to continue?</value>
   <data name="_Window" xml:space="preserve">
     <value>_Window</value>
   </data>
-  <data name="DecompilerSettings.IntroducePrivateProtectedAccessibility" xml:space="preserve">
-    <value>Introduce 'private protected' accessibility</value>
-  </data>
 </root>
\ No newline at end of file