Skip to content

Commit 7d0262d

Browse files
Fix #3439: Regressed decompilation of variables in lambda scope
1 parent 349a89c commit 7d0262d

File tree

5 files changed

+133
-66
lines changed

5 files changed

+133
-66
lines changed

ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@
133133
<Compile Include="TestCases\ILPretty\Issue3421.cs" />
134134
<Compile Include="TestCases\ILPretty\MonoFixed.cs" />
135135
<Compile Include="TestCases\Pretty\Comparisons.cs" />
136+
<Compile Include="TestCases\Pretty\Issue3439.cs" />
136137
<None Include="TestCases\VBPretty\VBAutomaticEvents.vb" />
137138
<Compile Include="TestCases\VBPretty\VBAutomaticEvents.cs" />
138139
<Compile Include="TestCases\VBPretty\VBNonGenericForEach.cs" />

ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,12 @@ public async Task Issue1080([ValueSource(nameof(roslynOnlyOptions))] CompilerOpt
604604
await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => settings.SetLanguageVersion(CSharp.LanguageVersion.CSharp6));
605605
}
606606

607+
[Test]
608+
public async Task Issue3439([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
609+
{
610+
await RunForLibrary(cscOptions: cscOptions);
611+
}
612+
607613
[Test]
608614
public async Task AssemblyCustomAttributes([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
609615
{
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
internal class VariableScopeTest
5+
{
6+
private class Item
7+
{
8+
public long Key;
9+
10+
public string Value;
11+
}
12+
13+
private void Test(List<string> list1)
14+
{
15+
AddAction(delegate (List<Item> list2) {
16+
long num2 = 1L;
17+
foreach (string item in list1)
18+
{
19+
list2.Add(new Item {
20+
Key = num2,
21+
Value = item
22+
});
23+
num2++;
24+
}
25+
});
26+
int num = 1;
27+
foreach (string item2 in list1)
28+
{
29+
int preservedName = num;
30+
num++;
31+
AddAction(item2, delegate (object x) {
32+
SetValue(x, preservedName);
33+
});
34+
}
35+
}
36+
37+
private static void AddAction(Action<List<Item>> action)
38+
{
39+
}
40+
41+
private static void AddAction(string name, Action<object> action)
42+
{
43+
}
44+
45+
private static void SetValue(object obj, int value)
46+
{
47+
}
48+
}

ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,15 @@ int NonStaticMethod<T3>(int unused)
5353
{
5454
t2 = default(T2);
5555
int l = 0;
56-
return NonStaticMethod2<T1>() + NonStaticMethod2<T2>() + z.GetHashCode();
57-
int NonStaticMethod2<T4>()
56+
return NonStaticMethod3<T1>() + NonStaticMethod3<T2>() + z.GetHashCode();
57+
int NonStaticMethod3<T4>()
5858
{
5959
return i2 + l + NonStaticMethod<T4>(0) + StaticMethod<decimal>();
6060
}
6161
}
6262
}
63-
return MixedLocalFunction<T1>() + MixedLocalFunction<T2>() + StaticMethod<decimal>() + StaticMethod<int>() + NonStaticMethod3() + StaticMethod4<object>(null) + StaticMethod5<T1>();
64-
int NonStaticMethod3()
63+
return MixedLocalFunction<T1>() + MixedLocalFunction<T2>() + StaticMethod<decimal>() + StaticMethod<int>() + NonStaticMethod2() + StaticMethod4<object>(null) + StaticMethod5<T1>();
64+
int NonStaticMethod2()
6565
{
6666
return GetHashCode();
6767
}
@@ -127,16 +127,16 @@ int NonStaticMethod<T3>()
127127
{
128128
t2 = default(T2);
129129
int l = 0;
130-
return StaticInvokeAsFunc(NonStaticMethod2<T1>) + StaticInvokeAsFunc(NonStaticMethod2<T2>) + z.GetHashCode();
131-
int NonStaticMethod2<T4>()
130+
return StaticInvokeAsFunc(NonStaticMethod3<T1>) + StaticInvokeAsFunc(NonStaticMethod3<T2>) + z.GetHashCode();
131+
int NonStaticMethod3<T4>()
132132
{
133133
return i2 + l + StaticInvokeAsFunc(NonStaticMethod<T4>) + StaticInvokeAsFunc(StaticMethod<decimal>);
134134
}
135135
}
136136
}
137137
Console.WriteLine(t2);
138-
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>);
139-
int NonStaticMethod3()
138+
return StaticInvokeAsFunc(MixedLocalFunction2Delegate<T1>) + StaticInvokeAsFunc(MixedLocalFunction2Delegate<T2>) + StaticInvokeAsFunc(StaticMethod<decimal>) + StaticInvokeAsFunc(StaticMethod<int>) + StaticInvokeAsFunc(NonStaticMethod2) + StaticInvokeAsFunc(StaticMethod4<T1>) + new Func<object, int>(StaticMethod5<object>)(null) + StaticInvokeAsFunc2<object>(StaticMethod5<object>) + new Func<Func<object, int>, int>(StaticInvokeAsFunc2<object>)(StaticMethod5<object>);
139+
int NonStaticMethod2()
140140
{
141141
return GetHashCode();
142142
}
@@ -732,15 +732,15 @@ int ZZZ()
732732
{
733733
t0 = 0;
734734
int t2 = t0;
735-
return new Func<int>(ZZZ2)();
736-
int ZZZ2()
735+
return new Func<int>(ZZZ3)();
736+
int ZZZ3()
737737
{
738738
t0 = 0;
739739
t2 = 0;
740-
return ZZZ3();
740+
return ZZZ2();
741741
}
742742
}
743-
int ZZZ3()
743+
int ZZZ2()
744744
{
745745
t0 = 0;
746746
int t3 = t0;

ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs

Lines changed: 66 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ public class AssignVariableNames : ILVisitor<AssignVariableNames.VariableScope,
5555
};
5656

5757
ILTransformContext context;
58+
Queue<(ILFunction function, VariableScope parentScope)> workList;
5859
const char maxLoopVariableName = 'n';
5960

6061
public class VariableScope
@@ -441,7 +442,70 @@ string GenerateNameForVariable(ILVariable variable)
441442
public void Run(ILFunction function, ILTransformContext context)
442443
{
443444
this.context = context;
444-
function.AcceptVisitor(this, null);
445+
this.workList ??= new Queue<(ILFunction function, VariableScope parentScope)>();
446+
this.workList.Clear();
447+
this.workList.Enqueue((function, null));
448+
449+
while (this.workList.Count > 0)
450+
{
451+
var (currentFunction, parentContext) = this.workList.Dequeue();
452+
453+
if (currentFunction.Kind == ILFunctionKind.LocalFunction)
454+
{
455+
// assign names to local functions
456+
if (!LocalFunctionDecompiler.ParseLocalFunctionName(currentFunction.Name, out _, out var newName) || !IsValidName(newName))
457+
newName = null;
458+
string nameWithoutNumber;
459+
int number;
460+
if (!string.IsNullOrEmpty(newName))
461+
{
462+
nameWithoutNumber = SplitName(newName, out number);
463+
}
464+
else
465+
{
466+
nameWithoutNumber = "f";
467+
number = 1;
468+
}
469+
int count;
470+
if (!parentContext.IsReservedVariableName(nameWithoutNumber, out int currentIndex))
471+
{
472+
count = 1;
473+
}
474+
else
475+
{
476+
if (currentIndex < number)
477+
count = number;
478+
else
479+
count = Math.Max(number, currentIndex) + 1;
480+
}
481+
parentContext.ReserveVariableName(nameWithoutNumber, count);
482+
if (count > 1)
483+
{
484+
newName = nameWithoutNumber + count.ToString();
485+
}
486+
else
487+
{
488+
newName = nameWithoutNumber;
489+
}
490+
currentFunction.Name = newName;
491+
currentFunction.ReducedMethod.Name = newName;
492+
parentContext.Add((MethodDefinitionHandle)currentFunction.ReducedMethod.MetadataToken, newName);
493+
}
494+
495+
var nestedContext = new VariableScope(currentFunction, this.context, parentContext);
496+
currentFunction.Body.AcceptVisitor(this, nestedContext);
497+
498+
foreach (var localFunction in currentFunction.LocalFunctions)
499+
workList.Enqueue((localFunction, nestedContext));
500+
501+
if (currentFunction.Kind != ILFunctionKind.TopLevelFunction)
502+
{
503+
foreach (var p in currentFunction.Variables.Where(v => v.Kind == VariableKind.Parameter && v.Index >= 0))
504+
{
505+
p.Name = nestedContext.AssignNameIfUnassigned(p);
506+
}
507+
}
508+
}
445509
}
446510

447511
Unit VisitChildren(ILInstruction inst, VariableScope context)
@@ -507,59 +571,7 @@ protected override Unit Default(ILInstruction inst, VariableScope context)
507571

508572
protected internal override Unit VisitILFunction(ILFunction function, VariableScope context)
509573
{
510-
if (function.Kind == ILFunctionKind.LocalFunction)
511-
{
512-
// assign names to local functions
513-
if (!LocalFunctionDecompiler.ParseLocalFunctionName(function.Name, out _, out var newName) || !IsValidName(newName))
514-
newName = null;
515-
string nameWithoutNumber;
516-
int number;
517-
if (!string.IsNullOrEmpty(newName))
518-
{
519-
nameWithoutNumber = SplitName(newName, out number);
520-
}
521-
else
522-
{
523-
nameWithoutNumber = "f";
524-
number = 1;
525-
}
526-
int count;
527-
if (!context.IsReservedVariableName(nameWithoutNumber, out int currentIndex))
528-
{
529-
count = 1;
530-
}
531-
else
532-
{
533-
if (currentIndex < number)
534-
count = number;
535-
else
536-
count = Math.Max(number, currentIndex) + 1;
537-
}
538-
context.ReserveVariableName(nameWithoutNumber, count);
539-
if (count > 1)
540-
{
541-
newName = nameWithoutNumber + count.ToString();
542-
}
543-
else
544-
{
545-
newName = nameWithoutNumber;
546-
}
547-
function.Name = newName;
548-
function.ReducedMethod.Name = newName;
549-
context.Add((MethodDefinitionHandle)function.ReducedMethod.MetadataToken, newName);
550-
}
551-
552-
var nestedContext = new VariableScope(function, this.context, context);
553-
base.VisitILFunction(function, nestedContext);
554-
555-
if (function.Kind != ILFunctionKind.TopLevelFunction)
556-
{
557-
foreach (var p in function.Variables.Where(v => v.Kind == VariableKind.Parameter && v.Index >= 0))
558-
{
559-
p.Name = nestedContext.AssignNameIfUnassigned(p);
560-
}
561-
}
562-
574+
workList.Enqueue((function, context));
563575
return default;
564576
}
565577

0 commit comments

Comments
 (0)