From c0f50a1cc73b094b833ac0d50d1e55aca900f343 Mon Sep 17 00:00:00 2001
From: ds5678 <49847914+ds5678@users.noreply.github.com>
Date: Mon, 8 Sep 2025 13:19:46 -0700
Subject: [PATCH] Copy-propagate the stack slot for collection initializers
---
.../ICSharpCode.Decompiler.Tests.csproj | 2 +
.../ILPrettyTestRunner.cs | 6 +
.../TestCases/ILPretty/Issue3552.cs | 66 ++++++
.../TestCases/ILPretty/Issue3552.il | 198 ++++++++++++++++++
...ransformCollectionAndObjectInitializers.cs | 5 +
5 files changed, 277 insertions(+)
create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3552.cs
create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3552.il
diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
index c43c7db0d..971979b70 100644
--- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
+++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
@@ -105,6 +105,7 @@
+
@@ -150,6 +151,7 @@
+
diff --git a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs
index 693592692..50fdeb3d9 100644
--- a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs
+++ b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs
@@ -249,6 +249,12 @@ namespace ICSharpCode.Decompiler.Tests
await Run();
}
+ [Test]
+ public async Task Issue3552()
+ {
+ await Run();
+ }
+
[Test]
public async Task Issue2260SwitchString()
{
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3552.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3552.cs
new file mode 100644
index 000000000..10995337f
--- /dev/null
+++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3552.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace ICSharpCode.Decompiler.Tests.TestCases.ILPretty
+{
+ public static class Issue3552
+ {
+ public static Issue3552_IntegerPair MakePair1(int x, int y)
+ {
+ Issue3552_IntegerPairBuilder issue3552_IntegerPairBuilder = new Issue3552_IntegerPairBuilder { x, y };
+ return issue3552_IntegerPairBuilder.ToPair();
+ }
+ public static Issue3552_IntegerPair MakePair2(int x, int y)
+ {
+ Issue3552_IntegerPairBuilder issue3552_IntegerPairBuilder = new Issue3552_IntegerPairBuilder { x, y };
+ return issue3552_IntegerPairBuilder.ToPair();
+ }
+ public static Issue3552_IntegerPair MakePair3(int x, int y)
+ {
+ Issue3552_IntegerPairBuilder issue3552_IntegerPairBuilder = new Issue3552_IntegerPairBuilder { x, y };
+ return issue3552_IntegerPairBuilder.ToPair();
+ }
+ }
+ public struct Issue3552_IntegerPair
+ {
+ public int X;
+ public int Y;
+ }
+ public struct Issue3552_IntegerPairBuilder : IEnumerable, IEnumerable
+ {
+ private int index;
+ private Issue3552_IntegerPair pair;
+
+ public readonly Issue3552_IntegerPair ToPair()
+ {
+ return pair;
+ }
+
+ public void Add(int value)
+ {
+ switch (index)
+ {
+ case 0:
+ pair.X = value;
+ break;
+ case 1:
+ pair.Y = value;
+ break;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ index++;
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return null;
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ }
+}
\ No newline at end of file
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3552.il b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3552.il
new file mode 100644
index 000000000..1bdce8e20
--- /dev/null
+++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3552.il
@@ -0,0 +1,198 @@
+.class public auto ansi abstract sealed beforefieldinit ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552
+ extends [System.Runtime]System.Object
+{
+ // Methods
+ .method public hidebysig static
+ valuetype ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPair MakePair1 (
+ int32 x,
+ int32 y
+ ) cil managed
+ {
+ // Method begins at RVA 0x2050
+ // Header size: 12
+ // Code size: 34 (0x22)
+ .maxstack 2
+ .locals init (
+ [0] valuetype ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPairBuilder
+ )
+
+ IL_0000: ldloca.s 0
+ IL_0002: initobj ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPairBuilder
+ IL_0008: ldloca.s 0
+ IL_000a: ldarg.0
+ IL_000b: call instance void ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPairBuilder::Add(int32)
+ IL_0010: ldloca.s 0
+ IL_0012: ldarg.1
+ IL_0013: call instance void ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPairBuilder::Add(int32)
+ IL_0015: ldloca.s 0
+ IL_001c: call instance valuetype ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPair ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPairBuilder::ToPair()
+ IL_0021: ret
+ } // end of method Issue3552::MakePair1
+
+ .method public hidebysig static
+ valuetype ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPair MakePair2 (
+ int32 x,
+ int32 y
+ ) cil managed
+ {
+ // Method begins at RVA 0x2050
+ // Header size: 12
+ // Code size: 34 (0x22)
+ .maxstack 2
+ .locals init (
+ [0] valuetype ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPairBuilder
+ )
+
+ IL_0000: ldloca.s 0
+ IL_0001: dup
+ IL_0002: initobj ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPairBuilder
+ IL_0008: dup
+ IL_000a: ldarg.0
+ IL_000b: call instance void ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPairBuilder::Add(int32)
+ IL_0012: ldarg.1
+ IL_0013: call instance void ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPairBuilder::Add(int32)
+ IL_0015: ldloca.s 0
+ IL_001c: call instance valuetype ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPair ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPairBuilder::ToPair()
+ IL_0021: ret
+ } // end of method Issue3552::MakePair2
+
+ .method public hidebysig static
+ valuetype ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPair MakePair3 (
+ int32 x,
+ int32 y
+ ) cil managed
+ {
+ // Method begins at RVA 0x2050
+ // Header size: 12
+ // Code size: 34 (0x22)
+ .maxstack 2
+ .locals init (
+ [0] valuetype ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPairBuilder
+ )
+
+ IL_0000: ldloca.s 0
+ IL_0001: dup
+ IL_0002: initobj ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPairBuilder
+ IL_0008: dup
+ IL_000a: ldarg.0
+ IL_000b: call instance void ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPairBuilder::Add(int32)
+ IL_0010: dup
+ IL_0012: ldarg.1
+ IL_0013: call instance void ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPairBuilder::Add(int32)
+ IL_001c: call instance valuetype ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPair ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPairBuilder::ToPair()
+ IL_0021: ret
+ } // end of method Issue3552::MakePair3
+
+} // end of class ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552
+
+.class public sequential ansi sealed beforefieldinit ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPair
+ extends [System.Runtime]System.ValueType
+{
+ // Fields
+ .field public int32 X
+ .field public int32 Y
+
+} // end of class ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPair
+
+.class public sequential ansi sealed beforefieldinit ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPairBuilder
+ extends [System.Runtime]System.ValueType
+ implements class [System.Runtime]System.Collections.Generic.IEnumerable`1,
+ [System.Runtime]System.Collections.IEnumerable
+{
+ // Fields
+ .field private int32 index
+ .field private valuetype ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPair pair
+
+ // Methods
+ .method public hidebysig
+ instance valuetype ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPair ToPair () cil managed
+ {
+ .custom instance void [System.Runtime]System.Runtime.CompilerServices.IsReadOnlyAttribute::.ctor() = (
+ 01 00 00 00
+ )
+ // Method begins at RVA 0x207e
+ // Header size: 1
+ // Code size: 7 (0x7)
+ .maxstack 8
+
+ IL_0000: ldarg.0
+ IL_0001: ldfld valuetype ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPair ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPairBuilder::pair
+ IL_0006: ret
+ } // end of method Issue3552_IntegerPairBuilder::ToPair
+
+ .method public hidebysig
+ instance void Add (
+ int32 'value'
+ ) cil managed
+ {
+ // Method begins at RVA 0x2088
+ // Header size: 12
+ // Code size: 65 (0x41)
+ .maxstack 3
+ .locals init (
+ [0] int32
+ )
+
+ IL_0000: ldarg.0
+ IL_0001: ldfld int32 ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPairBuilder::index
+ IL_0006: stloc.0
+ IL_0007: ldloc.0
+ IL_0008: brfalse.s IL_0010
+
+ IL_000a: ldloc.0
+ IL_000b: ldc.i4.1
+ IL_000c: beq.s IL_001e
+
+ IL_000e: br.s IL_002c
+
+ IL_0010: ldarg.0
+ IL_0011: ldflda valuetype ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPair ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPairBuilder::pair
+ IL_0016: ldarg.1
+ IL_0017: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPair::X
+ IL_001c: br.s IL_0032
+
+ IL_001e: ldarg.0
+ IL_001f: ldflda valuetype ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPair ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPairBuilder::pair
+ IL_0024: ldarg.1
+ IL_0025: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPair::Y
+ IL_002a: br.s IL_0032
+
+ IL_002c: newobj instance void [System.Runtime]System.IndexOutOfRangeException::.ctor()
+ IL_0031: throw
+
+ IL_0032: ldarg.0
+ IL_0033: ldarg.0
+ IL_0034: ldfld int32 ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPairBuilder::index
+ IL_0039: ldc.i4.1
+ IL_003a: add
+ IL_003b: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPairBuilder::index
+ IL_0040: ret
+ } // end of method Issue3552_IntegerPairBuilder::Add
+
+ .method public final hidebysig newslot virtual
+ instance class [System.Runtime]System.Collections.Generic.IEnumerator`1 GetEnumerator () cil managed
+ {
+ // Method begins at RVA 0x20d5
+ // Header size: 1
+ // Code size: 2 (0x2)
+ .maxstack 8
+
+ IL_0000: ldnull
+ IL_0001: ret
+ } // end of method Issue3552_IntegerPairBuilder::GetEnumerator
+
+ .method private final hidebysig newslot virtual
+ instance class [System.Runtime]System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () cil managed
+ {
+ .override method instance class [System.Runtime]System.Collections.IEnumerator [System.Runtime]System.Collections.IEnumerable::GetEnumerator()
+ // Method begins at RVA 0x20d8
+ // Header size: 1
+ // Code size: 7 (0x7)
+ .maxstack 8
+
+ IL_0000: ldarg.0
+ IL_0001: call instance class [System.Runtime]System.Collections.Generic.IEnumerator`1 ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPairBuilder::GetEnumerator()
+ IL_0006: ret
+ } // end of method Issue3552_IntegerPairBuilder::System.Collections.IEnumerable.GetEnumerator
+
+} // end of class ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3552_IntegerPairBuilder
diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs
index 7aa27687a..2f02608da 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs
@@ -97,6 +97,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
return;
}
+ // Copy-propagate stack slot holding an 'ldloca' of the variable
+ if (pos < block.Instructions.Count && block.Instructions[pos + 1] is StLoc { Variable: { Kind: VariableKind.StackSlot, IsSingleDefinition: true }, Value: LdLoca ldLoca } && ldLoca.Variable == v)
+ {
+ CopyPropagation.Propagate((StLoc)block.Instructions[pos + 1], context);
+ }
int initializerItemsCount = 0;
bool initializerContainsInitOnlyItems = false;
possibleIndexVariables.Clear();