Browse Source

Merge pull request #3416 from icsharpcode/variable-naming

Add scopes to AssignVariableName
pull/3443/head
Siegfried Pammer 3 months ago committed by GitHub
parent
commit
a40a033335
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 50
      ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
  2. 16
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.cs
  3. 4
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs
  4. 39
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs
  5. 12
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExceptionHandling.cs
  6. 16
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs
  7. 12
      ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.cs
  8. 2
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  9. 9
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  10. 9
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
  11. 10
      ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
  12. 16
      ICSharpCode.Decompiler/CSharp/Transforms/CombineQueryExpressions.cs
  13. 21
      ICSharpCode.Decompiler/CSharp/Transforms/IntroduceQueryExpressions.cs
  14. 1
      ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs
  15. 1
      ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs
  16. 4
      ICSharpCode.Decompiler/IL/ILReader.cs
  17. 5
      ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs
  18. 547
      ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs
  19. 2
      ICSharpCode.Decompiler/Util/CollectionExtensions.cs

50
ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

@ -233,22 +233,21 @@ namespace ICSharpCode.Decompiler.Tests
[Test] [Test]
public async Task ExceptionHandling([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) public async Task ExceptionHandling([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
{ {
await RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings { await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => {
NullPropagation = false, settings.NullPropagation = false;
// legacy csc generates a dead store in debug builds // legacy csc generates a dead store in debug builds
RemoveDeadStores = (cscOptions == CompilerOptions.None), settings.RemoveDeadStores = (cscOptions == CompilerOptions.None);
FileScopedNamespaces = false, settings.FileScopedNamespaces = false;
}); });
} }
[Test] [Test]
public async Task Switch([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) public async Task Switch([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
{ {
await RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings { await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => {
// legacy csc generates a dead store in debug builds // legacy csc generates a dead store in debug builds
RemoveDeadStores = (cscOptions == CompilerOptions.None), settings.RemoveDeadStores = (cscOptions == CompilerOptions.None);
SwitchExpressions = false, settings.SwitchExpressions = false;
FileScopedNamespaces = false,
}); });
} }
@ -267,7 +266,10 @@ namespace ICSharpCode.Decompiler.Tests
[Test] [Test]
public async Task DelegateConstruction([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions) public async Task DelegateConstruction([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions)
{ {
await RunForLibrary(cscOptions: cscOptions); await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => {
settings.QueryExpressions = false;
settings.NullableReferenceTypes = false;
});
} }
[Test] [Test]
@ -293,9 +295,9 @@ namespace ICSharpCode.Decompiler.Tests
{ {
await RunForLibrary( await RunForLibrary(
cscOptions: cscOptions, cscOptions: cscOptions,
decompilerSettings: new DecompilerSettings { configureDecompiler: settings => {
UseEnhancedUsing = false, settings.UseEnhancedUsing = false;
FileScopedNamespaces = false, settings.FileScopedNamespaces = false;
} }
); );
} }
@ -327,11 +329,11 @@ namespace ICSharpCode.Decompiler.Tests
[Test] [Test]
public async Task Loops([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions) public async Task Loops([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions)
{ {
DecompilerSettings settings = Tester.GetSettings(cscOptions); await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => {
// legacy csc generates a dead store in debug builds // legacy csc generates a dead store in debug builds
settings.RemoveDeadStores = (cscOptions == CompilerOptions.None); settings.RemoveDeadStores = (cscOptions == CompilerOptions.None);
settings.UseExpressionBodyForCalculatedGetterOnlyProperties = false; settings.UseExpressionBodyForCalculatedGetterOnlyProperties = false;
await RunForLibrary(cscOptions: cscOptions, decompilerSettings: settings); });
} }
[Test] [Test]
@ -440,9 +442,7 @@ namespace ICSharpCode.Decompiler.Tests
[Test] [Test]
public async Task VariableNamingWithoutSymbols([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) public async Task VariableNamingWithoutSymbols([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
{ {
var settings = Tester.GetSettings(cscOptions); await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => settings.UseDebugSymbols = false);
settings.UseDebugSymbols = false;
await RunForLibrary(cscOptions: cscOptions, decompilerSettings: settings);
} }
[Test] [Test]
@ -474,7 +474,7 @@ namespace ICSharpCode.Decompiler.Tests
{ {
await RunForLibrary( await RunForLibrary(
cscOptions: cscOptions, cscOptions: cscOptions,
decompilerSettings: new DecompilerSettings { UseEnhancedUsing = false, FileScopedNamespaces = false } configureDecompiler: settings => { settings.UseEnhancedUsing = false; }
); );
} }
@ -499,7 +499,7 @@ namespace ICSharpCode.Decompiler.Tests
[Test] [Test]
public async Task FileScopedNamespaces([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions) public async Task FileScopedNamespaces([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions)
{ {
await RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings()); await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => settings.FileScopedNamespaces = true);
} }
[Test] [Test]
@ -601,7 +601,7 @@ namespace ICSharpCode.Decompiler.Tests
[Test] [Test]
public async Task Issue1080([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions) public async Task Issue1080([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions)
{ {
await RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings(CSharp.LanguageVersion.CSharp6)); await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => settings.SetLanguageVersion(CSharp.LanguageVersion.CSharp6));
} }
[Test] [Test]
@ -712,12 +712,12 @@ namespace ICSharpCode.Decompiler.Tests
await RunForLibrary(cscOptions: cscOptions); await RunForLibrary(cscOptions: cscOptions);
} }
async Task RunForLibrary([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, DecompilerSettings decompilerSettings = null) async Task RunForLibrary([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, Action<DecompilerSettings> configureDecompiler = null)
{ {
await Run(testName, asmOptions | AssemblerOptions.Library, cscOptions | CompilerOptions.Library, decompilerSettings); await Run(testName, asmOptions | AssemblerOptions.Library, cscOptions | CompilerOptions.Library, configureDecompiler);
} }
async Task Run([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, DecompilerSettings decompilerSettings = null) async Task Run([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, Action<DecompilerSettings> configureDecompiler = null)
{ {
var csFile = Path.Combine(TestCasePath, testName + ".cs"); var csFile = Path.Combine(TestCasePath, testName + ".cs");
var exeFile = TestsAssemblyOutput.GetFilePath(TestCasePath, testName, Tester.GetSuffix(cscOptions) + ".exe"); var exeFile = TestsAssemblyOutput.GetFilePath(TestCasePath, testName, Tester.GetSuffix(cscOptions) + ".exe");
@ -739,7 +739,9 @@ namespace ICSharpCode.Decompiler.Tests
} }
// 2. Decompile // 2. Decompile
var decompiled = await Tester.DecompileCSharp(exeFile, decompilerSettings ?? Tester.GetSettings(cscOptions)).ConfigureAwait(false); var settings = Tester.GetSettings(cscOptions);
configureDecompiler?.Invoke(settings);
var decompiled = await Tester.DecompileCSharp(exeFile, settings).ConfigureAwait(false);
// 3. Compile // 3. Compile
CodeAssert.FilesAreEqual(csFile, decompiled, Tester.GetPreprocessorSymbols(cscOptions).Append("EXPECTED_OUTPUT").ToArray()); CodeAssert.FilesAreEqual(csFile, decompiled, Tester.GetPreprocessorSymbols(cscOptions).Append("EXPECTED_OUTPUT").ToArray());

16
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.cs

@ -15,8 +15,8 @@ namespace ClassLibrary1
//IL_0007: Expected O, but got Unknown //IL_0007: Expected O, but got Unknown
UnknownClass val = new UnknownClass(); UnknownClass val = new UnknownClass();
int? unknownProperty = val.UnknownProperty; int? unknownProperty = val.UnknownProperty;
int? num2 = (val.UnknownProperty = unknownProperty.GetValueOrDefault()); int? num = (val.UnknownProperty = unknownProperty.GetValueOrDefault());
int? num3 = num2; int? num3 = num;
List<object> list = new List<object> { List<object> list = new List<object> {
val[unknownProperty.Value] ?? "", val[unknownProperty.Value] ?? "",
val.NotProperty, val.NotProperty,
@ -50,9 +50,9 @@ namespace ClassLibrary1
//IL_00e1: Expected O, but got Unknown //IL_00e1: Expected O, but got Unknown
//IL_00e1: Expected O, but got Unknown //IL_00e1: Expected O, but got Unknown
UnknownGenericClass<UnknownEventArgs> val = new UnknownGenericClass<UnknownEventArgs>(); UnknownGenericClass<UnknownEventArgs> val = new UnknownGenericClass<UnknownEventArgs>();
UnknownEventArgs val2 = (val.UnknownProperty = val.UnknownProperty); UnknownEventArgs e = (val.UnknownProperty = val.UnknownProperty);
List<object> list = new List<object> { List<object> list = new List<object> {
val[((object)val2).GetHashCode()] ?? "", val[((object)e).GetHashCode()] ?? "",
val.NotProperty, val.NotProperty,
val.get_NotPropertyWithGeneric<string>(42), val.get_NotPropertyWithGeneric<string>(42),
val[42], val[42],
@ -61,10 +61,10 @@ namespace ClassLibrary1
}; };
val.OnEvent += Instance_OnEvent; val.OnEvent += Instance_OnEvent;
val.OnEvent -= Instance_OnEvent; val.OnEvent -= Instance_OnEvent;
UnknownEventArgs val3 = val[(UnknownEventArgs)null]; UnknownEventArgs e2 = val[(UnknownEventArgs)null];
val[new UnknownEventArgs()] = val3; val[new UnknownEventArgs()] = e2;
UnknownEventArgs val4 = val[new UnknownEventArgs(), new UnknownEventArgs()]; UnknownEventArgs e3 = val[new UnknownEventArgs(), new UnknownEventArgs()];
val[new UnknownEventArgs(), new UnknownEventArgs()] = val4; val[new UnknownEventArgs(), new UnknownEventArgs()] = e3;
} }
public void MethodUnknownStatic() public void MethodUnknownStatic()

4
ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs

@ -4943,8 +4943,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public void Issue1779(int value) public void Issue1779(int value)
{ {
CustomStruct2 @struct = GetStruct(); CustomStruct2 customStruct = GetStruct();
@struct.IntProp += value; customStruct.IntProp += value;
} }
} }
} }

39
ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs

@ -19,15 +19,33 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
#if CS100 #if CS100
using System.Threading.Tasks; using System.Threading.Tasks;
#endif #endif
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction
{ {
public static class DelegateConstruction public static class DelegateConstruction
{ {
internal class Dummy
{
public int baz;
public List<Dummy> more;
}
[CompilerGenerated]
internal class Helper
{
internal bool HelpMe(Dummy dum)
{
return true;
}
}
private class InstanceTests private class InstanceTests
{ {
public struct SomeData public struct SomeData
@ -377,10 +395,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction
public static void NameConflict2(int j) public static void NameConflict2(int j)
{ {
List<Action<int>> list = new List<Action<int>>(); List<Action<int>> list = new List<Action<int>>();
for (int k = 0; k < 10; k++) for (int i = 0; i < 10; i++)
{ {
list.Add(delegate (int i) { list.Add(delegate (int k) {
Console.WriteLine(i); Console.WriteLine(k);
}); });
} }
} }
@ -643,6 +661,21 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction
{ {
del(x); del(x);
} }
public void Issue1572(DelegateConstruction.Dummy dum)
{
#if EXPECTED_OUTPUT
DelegateConstruction.Helper CS_0024_003C_003E8__locals0 = new DelegateConstruction.Helper();
DelegateConstruction.Dummy dummy = dum.more.Where((DelegateConstruction.Dummy dummy2) => true).Where((DelegateConstruction.Dummy dummy2) => true).FirstOrDefault();
Console.WriteLine();
dummy.baz++;
#else
DelegateConstruction.Helper h = new DelegateConstruction.Helper();
DelegateConstruction.Dummy localDummy = dum.more.Where(h.HelpMe).Where(h.HelpMe).FirstOrDefault();
Console.WriteLine();
localDummy.baz++;
#endif
}
} }
[AttributeUsage(AttributeTargets.All)] [AttributeUsage(AttributeTargets.All)]

12
ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExceptionHandling.cs

@ -418,9 +418,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{ {
Console.WriteLine(input); Console.WriteLine(input);
} }
catch (TException val) catch (TException ex)
{ {
Console.WriteLine(val.Message); Console.WriteLine(ex.Message);
throw; throw;
} }
} }
@ -452,9 +452,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{ {
Console.WriteLine(input); Console.WriteLine(input);
} }
catch (TException val) when (val.Message.Contains("Test")) catch (TException ex) when (ex.Message.Contains("Test"))
{ {
Console.WriteLine(val.Message); Console.WriteLine(ex.Message);
throw; throw;
} }
} }
@ -465,9 +465,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{ {
Console.WriteLine(input); Console.WriteLine(input);
} }
catch (TException val) when (val.Message.Contains("Test")) catch (TException ex) when (ex.Message.Contains("Test"))
{ {
Console.WriteLine("{0} {1}", val, val.ToString()); Console.WriteLine("{0} {1}", ex, ex.ToString());
} }
} }

16
ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs

@ -182,11 +182,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
GetRef<ReadOnlyStruct>().Method(); GetRef<ReadOnlyStruct>().Method();
// call on a copy, not the original ref: // call on a copy, not the original ref:
NormalStruct @ref = GetRef<NormalStruct>(); NormalStruct normalStruct = GetRef<NormalStruct>();
@ref.Method(); normalStruct.Method();
ReadOnlyStruct ref2 = GetRef<ReadOnlyStruct>(); ReadOnlyStruct readOnlyStruct = GetRef<ReadOnlyStruct>();
ref2.Method(); readOnlyStruct.Method();
} }
public void CallOnReadOnlyRefReturn() public void CallOnReadOnlyRefReturn()
@ -293,13 +293,13 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public void RefReassignment(ref NormalStruct s) public void RefReassignment(ref NormalStruct s)
{ {
ref NormalStruct @ref = ref GetRef<NormalStruct>(); ref NormalStruct reference = ref GetRef<NormalStruct>();
RefReassignment(ref @ref); RefReassignment(ref reference);
if (s.GetHashCode() == 0) if (s.GetHashCode() == 0)
{ {
@ref = ref GetRef<NormalStruct>(); reference = ref GetRef<NormalStruct>();
} }
RefReassignment(ref @ref.GetHashCode() == 4 ? ref @ref : ref s); RefReassignment(ref reference.GetHashCode() == 4 ? ref reference : ref s);
} }
public static void Main(string[] args) public static void Main(string[] args)

12
ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.cs

@ -40,9 +40,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.VBPretty
public async void AwaitDefaultYieldAwaitable() public async void AwaitDefaultYieldAwaitable()
{ {
#if LEGACY_VBC || (OPTIMIZE && !ROSLYN4) #if LEGACY_VBC || (OPTIMIZE && !ROSLYN4)
YieldAwaitable yieldAwaitable = default(YieldAwaitable); YieldAwaitable yieldAwaitable2 = default(YieldAwaitable);
YieldAwaitable yieldAwaitable2 = yieldAwaitable; YieldAwaitable yieldAwaitable = yieldAwaitable2;
await yieldAwaitable2; await yieldAwaitable;
#else #else
await default(YieldAwaitable); await default(YieldAwaitable);
#endif #endif
@ -51,9 +51,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.VBPretty
public async void AwaitDefaultHopToThreadPool() public async void AwaitDefaultHopToThreadPool()
{ {
#if LEGACY_VBC || (OPTIMIZE && !ROSLYN4) #if LEGACY_VBC || (OPTIMIZE && !ROSLYN4)
HopToThreadPoolAwaitable hopToThreadPoolAwaitable = default(HopToThreadPoolAwaitable); HopToThreadPoolAwaitable hopToThreadPoolAwaitable2 = default(HopToThreadPoolAwaitable);
HopToThreadPoolAwaitable hopToThreadPoolAwaitable2 = hopToThreadPoolAwaitable; HopToThreadPoolAwaitable hopToThreadPoolAwaitable = hopToThreadPoolAwaitable2;
await hopToThreadPoolAwaitable2; await hopToThreadPoolAwaitable;
#else #else
await default(HopToThreadPoolAwaitable); await default(HopToThreadPoolAwaitable);
#endif #endif

2
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -1282,7 +1282,7 @@ namespace ICSharpCode.Decompiler.CSharp
{ {
if (string.IsNullOrWhiteSpace(parameter.Name) && !parameter.Type.IsArgList()) if (string.IsNullOrWhiteSpace(parameter.Name) && !parameter.Type.IsArgList())
{ {
// needs to be consistent with logic in ILReader.CreateILVarable // needs to be consistent with logic in ILReader.CreateILVariable
parameter.Name = "P_" + i; parameter.Name = "P_" + i;
} }
i++; i++;

9
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -2547,15 +2547,18 @@ namespace ICSharpCode.Decompiler.CSharp
foreach (var parameter in parameters) foreach (var parameter in parameters)
{ {
var pd = astBuilder.ConvertParameter(parameter); var pd = astBuilder.ConvertParameter(parameter);
if (variables.TryGetValue(i, out var v))
{
pd.AddAnnotation(new ILVariableResolveResult(v, parameters[i].Type));
pd.Name = v.Name;
}
if (string.IsNullOrEmpty(pd.Name) && !pd.Type.IsArgList()) if (string.IsNullOrEmpty(pd.Name) && !pd.Type.IsArgList())
{ {
// needs to be consistent with logic in ILReader.CreateILVarable(ParameterDefinition) // needs to be consistent with logic in ILReader.CreateILVariable
pd.Name = "P_" + i; pd.Name = "P_" + i;
} }
if (settings.AnonymousTypes && parameter.Type.ContainsAnonymousType()) if (settings.AnonymousTypes && parameter.Type.ContainsAnonymousType())
pd.Type = null; pd.Type = null;
if (variables.TryGetValue(i, out var v))
pd.AddAnnotation(new ILVariableResolveResult(v, parameters[i].Type));
yield return pd; yield return pd;
i++; i++;
} }

9
ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs

@ -1,4 +1,4 @@
// Copyright (c) 2010-2020 AlphaSierraPapa for the SharpDevelop Team // Copyright (c) 2010-2020 AlphaSierraPapa for the SharpDevelop Team
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy of this // Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software // software and associated documentation files (the "Software"), to deal in the Software
@ -426,8 +426,9 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
/// <summary> /// <summary>
/// Determines whether the specified identifier is a keyword in the given context. /// Determines whether the specified identifier is a keyword in the given context.
/// If <paramref name="context"/> is <see langword="null" /> all keywords are treated as unconditional.
/// </summary> /// </summary>
public static bool IsKeyword(string identifier, AstNode context) public static bool IsKeyword(string identifier, AstNode context = null)
{ {
// only 2-10 char lower-case identifiers can be keywords // only 2-10 char lower-case identifiers can be keywords
if (identifier.Length > maxKeywordLength || identifier.Length < 2 || identifier[0] < 'a') if (identifier.Length > maxKeywordLength || identifier.Length < 2 || identifier[0] < 'a')
@ -440,10 +441,12 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
} }
if (queryKeywords.Contains(identifier)) if (queryKeywords.Contains(identifier))
{ {
return context.Ancestors.Any(ancestor => ancestor is QueryExpression); return context == null || context.Ancestors.Any(ancestor => ancestor is QueryExpression);
} }
if (identifier == "await") if (identifier == "await")
{ {
if (context == null)
return true;
foreach (AstNode ancestor in context.Ancestors) foreach (AstNode ancestor in context.Ancestors)
{ {
// with lambdas/anonymous methods, // with lambdas/anonymous methods,

10
ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

@ -1389,6 +1389,16 @@ namespace ICSharpCode.Decompiler.CSharp
var astBuilder = exprBuilder.astBuilder; var astBuilder = exprBuilder.astBuilder;
var method = (MethodDeclaration)astBuilder.ConvertEntity(function.ReducedMethod); var method = (MethodDeclaration)astBuilder.ConvertEntity(function.ReducedMethod);
var variables = function.Variables.Where(v => v.Kind == VariableKind.Parameter).ToDictionary(v => v.Index);
foreach (var (i, p) in method.Parameters.WithIndex())
{
if (variables.TryGetValue(i, out var v))
{
p.Name = v.Name;
}
}
if (function.Method.HasBody) if (function.Method.HasBody)
{ {
var nestedBuilder = new StatementBuilder( var nestedBuilder = new StatementBuilder(

16
ICSharpCode.Decompiler/CSharp/Transforms/CombineQueryExpressions.cs

@ -16,12 +16,12 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.CSharp.Syntax;
using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching; using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching;
using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.Decompiler.CSharp.Transforms namespace ICSharpCode.Decompiler.CSharp.Transforms
{ {
@ -54,12 +54,10 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
next = child.NextSibling; next = child.NextSibling;
CombineQueries(child, fromOrLetIdentifiers); CombineQueries(child, fromOrLetIdentifiers);
} }
QueryExpression query = node as QueryExpression; if (node is QueryExpression query)
if (query != null)
{ {
QueryFromClause fromClause = (QueryFromClause)query.Clauses.First(); QueryFromClause fromClause = (QueryFromClause)query.Clauses.First();
QueryExpression innerQuery = fromClause.Expression as QueryExpression; if (fromClause.Expression is QueryExpression innerQuery)
if (innerQuery != null)
{ {
if (TryRemoveTransparentIdentifier(query, fromClause, innerQuery, fromOrLetIdentifiers)) if (TryRemoveTransparentIdentifier(query, fromClause, innerQuery, fromOrLetIdentifiers))
{ {
@ -165,11 +163,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
{ {
RemoveTransparentIdentifierReferences(child, fromOrLetIdentifiers); RemoveTransparentIdentifierReferences(child, fromOrLetIdentifiers);
} }
MemberReferenceExpression mre = node as MemberReferenceExpression; if (node is MemberReferenceExpression mre && mre.Target is IdentifierExpression ident
if (mre != null) && CSharpDecompiler.IsTransparentIdentifier(ident.Identifier))
{
IdentifierExpression ident = mre.Target as IdentifierExpression;
if (ident != null && CSharpDecompiler.IsTransparentIdentifier(ident.Identifier))
{ {
IdentifierExpression newIdent = new IdentifierExpression(mre.MemberName); IdentifierExpression newIdent = new IdentifierExpression(mre.MemberName);
mre.TypeArguments.MoveTo(newIdent.TypeArguments); mre.TypeArguments.MoveTo(newIdent.TypeArguments);
@ -182,7 +177,6 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
} }
} }
} }
}
public class LetIdentifierAnnotation public class LetIdentifierAnnotation
{ {

21
ICSharpCode.Decompiler/CSharp/Transforms/IntroduceQueryExpressions.cs

@ -17,10 +17,10 @@
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System; using System;
using System.Diagnostics;
using System.Linq; using System.Linq;
using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.CSharp.Syntax;
using ICSharpCode.Decompiler.IL;
namespace ICSharpCode.Decompiler.CSharp.Transforms namespace ICSharpCode.Decompiler.CSharp.Transforms
{ {
@ -54,13 +54,14 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
while (IsDegenerateQuery(innerQuery)) while (IsDegenerateQuery(innerQuery))
{ {
QueryFromClause innerFromClause = (QueryFromClause)innerQuery.Clauses.First(); QueryFromClause innerFromClause = (QueryFromClause)innerQuery.Clauses.First();
if (fromClause.Identifier != innerFromClause.Identifier) ILVariable innerVariable = innerFromClause.Annotation<ILVariableResolveResult>()?.Variable;
break; ILVariable rangeVariable = fromClause.Annotation<ILVariableResolveResult>()?.Variable;
// Replace the fromClause with all clauses from the inner query // Replace the fromClause with all clauses from the inner query
fromClause.Remove(); fromClause.Remove();
QueryClause insertionPos = null; QueryClause insertionPos = null;
foreach (var clause in innerQuery.Clauses) foreach (var clause in innerQuery.Clauses)
{ {
CombineRangeVariables(clause, innerVariable, rangeVariable);
query.Clauses.InsertAfter(insertionPos, insertionPos = clause.Detach()); query.Clauses.InsertAfter(insertionPos, insertionPos = clause.Detach());
} }
fromClause = innerFromClause; fromClause = innerFromClause;
@ -69,6 +70,20 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
} }
} }
private void CombineRangeVariables(QueryClause clause, ILVariable oldVariable, ILVariable newVariable)
{
foreach (var identifier in clause.DescendantNodes().OfType<Identifier>())
{
var variable = identifier.Parent.Annotation<ILVariableResolveResult>()?.Variable;
if (variable == oldVariable)
{
identifier.Parent.RemoveAnnotations<ILVariableResolveResult>();
identifier.Parent.AddAnnotation(new ILVariableResolveResult(newVariable));
identifier.ReplaceWith(Identifier.Create(newVariable.Name));
}
}
}
bool IsDegenerateQuery(QueryExpression query) bool IsDegenerateQuery(QueryExpression query)
{ {
if (query == null) if (query == null)

1
ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs

@ -1165,6 +1165,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
function.MoveNextMethod = moveNextFunction.Method; function.MoveNextMethod = moveNextFunction.Method;
function.SequencePointCandidates = moveNextFunction.SequencePointCandidates; function.SequencePointCandidates = moveNextFunction.SequencePointCandidates;
function.CodeSize = moveNextFunction.CodeSize; function.CodeSize = moveNextFunction.CodeSize;
function.LocalVariableSignatureLength = moveNextFunction.LocalVariableSignatureLength;
function.IsIterator = IsAsyncEnumerator; function.IsIterator = IsAsyncEnumerator;
moveNextFunction.Variables.Clear(); moveNextFunction.Variables.Clear();
moveNextFunction.ReleaseRef(); moveNextFunction.ReleaseRef();

1
ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs

@ -656,6 +656,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
function.MoveNextMethod = moveNextFunction.Method; function.MoveNextMethod = moveNextFunction.Method;
function.SequencePointCandidates = moveNextFunction.SequencePointCandidates; function.SequencePointCandidates = moveNextFunction.SequencePointCandidates;
function.CodeSize = moveNextFunction.CodeSize; function.CodeSize = moveNextFunction.CodeSize;
function.LocalVariableSignatureLength = moveNextFunction.LocalVariableSignatureLength;
// Copy-propagate temporaries holding a copy of 'this'. // Copy-propagate temporaries holding a copy of 'this'.
// This is necessary because the old (pre-Roslyn) C# compiler likes to store 'this' in temporary variables. // This is necessary because the old (pre-Roslyn) C# compiler likes to store 'this' in temporary variables.

4
ICSharpCode.Decompiler/IL/ILReader.cs

@ -342,7 +342,10 @@ namespace ICSharpCode.Decompiler.IL
if (index < 0) if (index < 0)
ilVar.Name = "this"; ilVar.Name = "this";
else if (string.IsNullOrWhiteSpace(name)) else if (string.IsNullOrWhiteSpace(name))
{
ilVar.Name = "P_" + index; ilVar.Name = "P_" + index;
ilVar.HasGeneratedName = true;
}
else else
ilVar.Name = name; ilVar.Name = name;
return ilVar; return ilVar;
@ -706,6 +709,7 @@ namespace ICSharpCode.Decompiler.IL
var function = new ILFunction(this.method, body.GetCodeSize(), this.genericContext, mainContainer, kind); var function = new ILFunction(this.method, body.GetCodeSize(), this.genericContext, mainContainer, kind);
function.Variables.AddRange(parameterVariables); function.Variables.AddRange(parameterVariables);
function.Variables.AddRange(localVariables); function.Variables.AddRange(localVariables);
function.LocalVariableSignatureLength = localVariables.Length;
Debug.Assert(stackVariables != null); Debug.Assert(stackVariables != null);
function.Variables.AddRange(stackVariables); function.Variables.AddRange(stackVariables);
function.Variables.AddRange(variableByExceptionHandler.Values); function.Variables.AddRange(variableByExceptionHandler.Values);

5
ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs

@ -67,6 +67,11 @@ namespace ICSharpCode.Decompiler.IL
/// </summary> /// </summary>
public readonly ILVariableCollection Variables; public readonly ILVariableCollection Variables;
/// <summary>
/// Gets
/// </summary>
public int LocalVariableSignatureLength;
/// <summary> /// <summary>
/// Gets the scope in which the local function is declared. /// Gets the scope in which the local function is declared.
/// Returns null, if this is not a local function. /// Returns null, if this is not a local function.

547
ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs

@ -18,6 +18,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -25,6 +26,7 @@ using System.Reflection.Metadata;
using Humanizer.Inflections; using Humanizer.Inflections;
using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.CSharp.OutputVisitor; using ICSharpCode.Decompiler.CSharp.OutputVisitor;
using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.TypeSystem.Implementation; using ICSharpCode.Decompiler.TypeSystem.Implementation;
@ -32,7 +34,7 @@ using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.Decompiler.IL.Transforms namespace ICSharpCode.Decompiler.IL.Transforms
{ {
public class AssignVariableNames : IILTransform public class AssignVariableNames : ILVisitor<AssignVariableNames.VariableScope, Unit>, IILTransform
{ {
static readonly Dictionary<string, string> typeNameToVariableNameDict = new Dictionary<string, string> { static readonly Dictionary<string, string> typeNameToVariableNameDict = new Dictionary<string, string> {
{ "System.Boolean", "flag" }, { "System.Boolean", "flag" },
@ -53,52 +55,83 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}; };
ILTransformContext context; ILTransformContext context;
List<string> currentLowerCaseTypeOrMemberNames; const char maxLoopVariableName = 'n';
public class VariableScope
{
readonly ILTransformContext context;
readonly VariableScope parentScope;
readonly ILFunction function;
readonly Dictionary<MethodDefinitionHandle, string> localFunctions = new();
readonly Dictionary<ILVariable, string> variableMapping = new(ILVariableEqualityComparer.Instance);
readonly string[] assignedLocalSignatureIndices;
IImmutableSet<string> currentLowerCaseTypeOrMemberNames;
Dictionary<string, int> reservedVariableNames; Dictionary<string, int> reservedVariableNames;
HashSet<ILVariable> loopCounters; HashSet<ILVariable> loopCounters;
const char maxLoopVariableName = 'n';
int numDisplayClassLocals; int numDisplayClassLocals;
public void Run(ILFunction function, ILTransformContext context) public VariableScope(ILFunction function, ILTransformContext context, VariableScope parentScope = null)
{ {
this.function = function;
this.context = context; this.context = context;
this.parentScope = parentScope;
numDisplayClassLocals = 0;
assignedLocalSignatureIndices = new string[function.LocalVariableSignatureLength];
reservedVariableNames = new Dictionary<string, int>(); reservedVariableNames = new Dictionary<string, int>();
currentLowerCaseTypeOrMemberNames = new List<string>();
var currentLowerCaseMemberNames = CollectAllLowerCaseMemberNames(function.Method.DeclaringTypeDefinition); // find all loop counters in the current function
foreach (var name in currentLowerCaseMemberNames) loopCounters = new HashSet<ILVariable>();
foreach (var inst in TreeTraversal.PreOrder((ILInstruction)function, i => i.Children))
{
if (inst is ILFunction && inst != function)
break;
if (inst is BlockContainer { Kind: ContainerKind.For, Blocks: [.., var incrementBlock] })
{
foreach (var i in incrementBlock.Instructions)
{
if (HighLevelLoopTransform.MatchIncrement(i, out var variable))
loopCounters.Add(variable);
}
}
}
// if this is the root scope, we also collect all lower-case type and member names
// and fixed parameter names to avoid conflicts when naming local variables.
if (parentScope == null)
{
var currentLowerCaseTypeOrMemberNames = new HashSet<string>(StringComparer.Ordinal);
foreach (var name in CollectAllLowerCaseMemberNames(function.Method.DeclaringTypeDefinition))
currentLowerCaseTypeOrMemberNames.Add(name); currentLowerCaseTypeOrMemberNames.Add(name);
var currentLowerCaseTypeNames = CollectAllLowerCaseTypeNames(function.Method.DeclaringTypeDefinition); foreach (var name in CollectAllLowerCaseTypeNames(function.Method.DeclaringTypeDefinition))
foreach (var name in currentLowerCaseTypeNames)
{ {
currentLowerCaseTypeOrMemberNames.Add(name); currentLowerCaseTypeOrMemberNames.Add(name);
AddExistingName(reservedVariableNames, name); AddExistingName(reservedVariableNames, name);
} }
loopCounters = CollectLoopCounters(function); this.currentLowerCaseTypeOrMemberNames = currentLowerCaseTypeOrMemberNames.ToImmutableHashSet();
foreach (var f in function.Descendants.OfType<ILFunction>())
{ // handle implicit parameters of set or event accessors
if (f.Method != null) if (function.Method != null && IsSetOrEventAccessor(function.Method) && function.Parameters.Count > 0)
{
if (IsSetOrEventAccessor(f.Method) && f.Method.Parameters.Count > 0)
{ {
for (int i = 0; i < f.Method.Parameters.Count - 1; i++) for (int i = 0; i < function.Method.Parameters.Count - 1; i++)
{ {
AddExistingName(reservedVariableNames, f.Method.Parameters[i].Name); AddExistingName(reservedVariableNames, function.Method.Parameters[i].Name);
} }
var lastParameter = f.Method.Parameters.Last(); var lastParameter = function.Method.Parameters.Last();
switch (f.Method.AccessorOwner) switch (function.Method.AccessorOwner)
{ {
case IProperty prop: case IProperty prop:
if (f.Method.AccessorKind == MethodSemanticsAttributes.Setter) if (function.Method.AccessorKind == MethodSemanticsAttributes.Setter)
{ {
if (prop.Parameters.Any(p => p.Name == "value")) if (prop.Parameters.Any(p => p.Name == "value"))
{ {
f.Warnings.Add("Parameter named \"value\" already present in property signature!"); function.Warnings.Add("Parameter named \"value\" already present in property signature!");
break; break;
} }
var variableForLastParameter = f.Variables.FirstOrDefault(v => v.Function == f var variableForLastParameter = function.Variables.FirstOrDefault(v => v.Function == function
&& v.Kind == VariableKind.Parameter && v.Kind == VariableKind.Parameter
&& v.Index == f.Method.Parameters.Count - 1); && v.Index == function.Method.Parameters.Count - 1);
if (variableForLastParameter == null) if (variableForLastParameter == null)
{ {
AddExistingName(reservedVariableNames, lastParameter.Name); AddExistingName(reservedVariableNames, lastParameter.Name);
@ -114,11 +147,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
break; break;
case IEvent ev: case IEvent ev:
if (f.Method.AccessorKind != MethodSemanticsAttributes.Raiser) if (function.Method.AccessorKind != MethodSemanticsAttributes.Raiser)
{ {
var variableForLastParameter = f.Variables.FirstOrDefault(v => v.Function == f var variableForLastParameter = function.Variables.FirstOrDefault(v => v.Function == function
&& v.Kind == VariableKind.Parameter && v.Kind == VariableKind.Parameter
&& v.Index == f.Method.Parameters.Count - 1); && v.Index == function.Method.Parameters.Count - 1);
if (variableForLastParameter == null) if (variableForLastParameter == null)
{ {
AddExistingName(reservedVariableNames, lastParameter.Name); AddExistingName(reservedVariableNames, lastParameter.Name);
@ -140,42 +173,22 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
else else
{ {
foreach (var p in f.Method.Parameters) var variables = function.Variables.Where(v => v.Kind == VariableKind.Parameter && v.Index >= 0).ToDictionary(v => v.Index);
AddExistingName(reservedVariableNames, p.Name); foreach (var (i, p) in function.Parameters.WithIndex())
}
}
else
{ {
foreach (var p in f.Variables.Where(v => v.Kind == VariableKind.Parameter)) string name = p.Name;
AddExistingName(reservedVariableNames, p.Name); if (string.IsNullOrWhiteSpace(name) && p.Type != SpecialType.ArgList)
}
}
numDisplayClassLocals = 0;
PerformAssignment(function);
}
static IEnumerable<string> CollectAllLowerCaseMemberNames(ITypeDefinition type)
{ {
foreach (var item in type.GetMembers(m => IsLowerCase(m.Name))) // needs to be consistent with logic in ILReader.CreateILVariable
yield return item.Name; name = "P_" + i;
}
static IEnumerable<string> CollectAllLowerCaseTypeNames(ITypeDefinition type)
{
var ns = type.ParentModule.Compilation.GetNamespaceByFullName(type.Namespace);
foreach (var item in ns.Types)
{
if (IsLowerCase(item.Name))
yield return item.Name;
} }
if (variables.TryGetValue(i, out var v))
variableMapping[v] = name;
AddExistingName(reservedVariableNames, name);
} }
static bool IsLowerCase(string name)
{
return name.Length > 0 && char.ToLower(name[0]) == name[0];
} }
bool IsSetOrEventAccessor(IMethod method) static bool IsSetOrEventAccessor(IMethod method)
{ {
switch (method.AccessorKind) switch (method.AccessorKind)
{ {
@ -187,95 +200,106 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false; return false;
} }
} }
}
void PerformAssignment(ILFunction function) else
{ {
var localFunctionMapping = new Dictionary<MethodDefinitionHandle, string>(); this.currentLowerCaseTypeOrMemberNames = parentScope.currentLowerCaseTypeOrMemberNames;
var variableMapping = new Dictionary<ILVariable, string>(ILVariableEqualityComparer.Instance); var variables = function.Variables.Where(v => v.Kind == VariableKind.Parameter).ToDictionary(v => v.Index);
var assignedLocalSignatureIndices = new Dictionary<(ILFunction, int), string>();
foreach (var inst in function.Descendants) foreach (var (i, p) in function.Parameters.WithIndex())
{
if (inst is ILFunction { Kind: ILFunctionKind.LocalFunction } localFunction)
{
// assign names to local functions
if (!LocalFunctionDecompiler.ParseLocalFunctionName(localFunction.Name, out _, out var newName) || !IsValidName(newName))
newName = null;
if (newName == null)
{ {
string nameWithoutNumber = "f"; if (function.Kind is ILFunctionKind.Delegate or ILFunctionKind.ExpressionTree
if (!reservedVariableNames.TryGetValue(nameWithoutNumber, out int currentIndex)) && CSharpDecompiler.IsTransparentIdentifier(p.Name))
{ {
currentIndex = 1; AddExistingName(reservedVariableNames, p.Name);
if (variables.TryGetValue(i, out var v))
variableMapping[v] = p.Name;
} }
int count = Math.Max(1, currentIndex) + 1; string nameWithoutNumber = SplitName(p.Name, out int newIndex);
reservedVariableNames[nameWithoutNumber] = count; if (!parentScope.IsReservedVariableName(nameWithoutNumber, out _))
if (count > 1)
{ {
newName = nameWithoutNumber + count.ToString(); AddExistingName(reservedVariableNames, p.Name);
if (variables.TryGetValue(i, out var v))
variableMapping[v] = p.Name;
} }
else else if (variables.TryGetValue(i, out var v))
{ {
newName = nameWithoutNumber; v.HasGeneratedName = true;
} }
} }
localFunction.Name = newName;
localFunction.ReducedMethod.Name = newName;
localFunctionMapping[(MethodDefinitionHandle)localFunction.ReducedMethod.MetadataToken] = newName;
} }
else if (inst is IInstructionWithVariableOperand i) }
public void Add(MethodDefinitionHandle localFunction, string name)
{ {
var v = i.Variable; this.localFunctions[localFunction] = name;
// if there is already a valid name for the variable slot, just use it }
if (variableMapping.TryGetValue(v, out string name))
public string TryGetExistingName(MethodDefinitionHandle localFunction)
{ {
v.Name = name; if (localFunctions.TryGetValue(localFunction, out var name))
continue; return name;
return parentScope?.TryGetExistingName(localFunction);
} }
switch (v.Kind)
public string TryGetExistingName(ILVariable v)
{ {
case VariableKind.Parameter: if (variableMapping.TryGetValue(v, out var name))
// Parameter names are handled in ILReader.CreateILVariable return name;
// and CSharpDecompiler.FixParameterNames return parentScope?.TryGetExistingName(v);
break; }
case VariableKind.InitializerTarget: // keep generated names
AddExistingName(reservedVariableNames, v.Name); public string TryGetExistingName(ILFunction function, int index)
break;
case VariableKind.DisplayClassLocal:
v.Name = "CS$<>8__locals" + (numDisplayClassLocals++);
break;
case VariableKind.Local when v.Index != null:
if (assignedLocalSignatureIndices.TryGetValue((v.Function, v.Index.Value), out name))
{ {
// make sure all local ILVariables that refer to the same slot in the locals signature if (this.function == function)
// are assigned the same name. {
v.Name = name; return this.assignedLocalSignatureIndices[index];
} }
else else
{ {
v.Name = AssignName(v, variableMapping); return parentScope?.TryGetExistingName(function, index);
// Remember the newly assigned name:
assignedLocalSignatureIndices.Add((v.Function, v.Index.Value), v.Name);
} }
break;
default:
v.Name = AssignName(v, variableMapping);
break;
} }
public void AssignNameToLocalSignatureIndex(ILFunction function, int index, string name)
{
var scope = this;
while (scope != null && scope.function != function)
scope = scope.parentScope;
Debug.Assert(scope != null);
scope.assignedLocalSignatureIndices[index] = name;
} }
else if (inst is (Call or LdFtn) and IInstructionWithMethodOperand m)
public bool IsReservedVariableName(string name, out int index)
{ {
// update references to local functions if (reservedVariableNames.TryGetValue(name, out index))
if (m.Method is LocalFunctionMethod lf return true;
&& localFunctionMapping.TryGetValue((MethodDefinitionHandle)lf.MetadataToken, out var name)) return parentScope?.IsReservedVariableName(name, out index) ?? false;
}
public void ReserveVariableName(string name, int index = 1)
{ {
lf.Name = name; reservedVariableNames[name] = index;
} }
public string NextDisplayClassLocal()
{
return parentScope?.NextDisplayClassLocal() ?? "CS$<>8__locals" + (numDisplayClassLocals++);
} }
public bool IsLoopCounter(ILVariable v)
{
return loopCounters.Contains(v) || (parentScope?.IsLoopCounter(v) == true);
} }
public string AssignNameIfUnassigned(ILVariable v)
{
if (variableMapping.TryGetValue(v, out var name))
return name;
return AssignName(v);
} }
string AssignName(ILVariable v, Dictionary<ILVariable, string> variableMapping) public string AssignName(ILVariable v)
{ {
// variable has no valid name // variable has no valid name
string newName = v.Name; string newName = v.Name;
@ -287,9 +311,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
// use the existing name and update index appended to future conflicts // use the existing name and update index appended to future conflicts
string nameWithoutNumber = SplitName(newName, out int newIndex); string nameWithoutNumber = SplitName(newName, out int newIndex);
if (reservedVariableNames.TryGetValue(nameWithoutNumber, out int lastUsedIndex)) if (IsReservedVariableName(nameWithoutNumber, out int lastUsedIndex))
{ {
if (v.Type.IsKnownType(KnownTypeCode.Int32) && loopCounters.Contains(v)) if (v.Type.IsKnownType(KnownTypeCode.Int32) && IsLoopCounter(v))
{ {
// special case for loop counters, // special case for loop counters,
// we don't want them to be named i, i2, ..., but i, j, ... // we don't want them to be named i, i2, ..., but i, j, ...
@ -298,7 +322,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
newIndex = 1; newIndex = 1;
} }
} }
if (reservedVariableNames.TryGetValue(nameWithoutNumber, out lastUsedIndex)) if (IsReservedVariableName(nameWithoutNumber, out lastUsedIndex))
{ {
// name without number was already used // name without number was already used
if (newIndex > lastUsedIndex) if (newIndex > lastUsedIndex)
@ -314,61 +338,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
newName = nameWithoutNumber + newIndex.ToString(); newName = nameWithoutNumber + newIndex.ToString();
} }
// update the last used index // update the last used index
reservedVariableNames[nameWithoutNumber] = newIndex; ReserveVariableName(nameWithoutNumber, newIndex);
variableMapping.Add(v, newName); variableMapping.Add(v, newName);
return newName; return newName;
} }
/// <remarks>
/// Must be in sync with <see cref="GetNameFromInstruction" />.
/// </remarks>
internal static bool IsSupportedInstruction(object arg)
{
switch (arg)
{
case GetPinnableReference _:
case LdObj _:
case LdFlda _:
case LdsFlda _:
case CallInstruction _:
return true;
default:
return false;
}
}
internal static bool IsValidName(string varName)
{
if (string.IsNullOrWhiteSpace(varName))
return false;
if (!(char.IsLetter(varName[0]) || varName[0] == '_'))
return false;
for (int i = 1; i < varName.Length; i++)
{
if (!(char.IsLetterOrDigit(varName[i]) || varName[i] == '_'))
return false;
}
return true;
}
HashSet<ILVariable> CollectLoopCounters(ILFunction function)
{
var loopCounters = new HashSet<ILVariable>();
foreach (BlockContainer possibleLoop in function.Descendants.OfType<BlockContainer>())
{
if (possibleLoop.Kind != ContainerKind.For)
continue;
foreach (var inst in possibleLoop.Blocks.Last().Instructions)
{
if (HighLevelLoopTransform.MatchIncrement(inst, out var variable))
loopCounters.Add(variable);
}
}
return loopCounters;
}
string GenerateNameForVariable(ILVariable variable) string GenerateNameForVariable(ILVariable variable)
{ {
string proposedName = null; string proposedName = null;
@ -380,7 +354,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// For loop variables, use i,j,k,l,m,n // For loop variables, use i,j,k,l,m,n
for (char c = 'i'; c <= maxLoopVariableName; c++) for (char c = 'i'; c <= maxLoopVariableName; c++)
{ {
if (!reservedVariableNames.ContainsKey(c.ToString())) if (!IsReservedVariableName(c.ToString(), out _))
{ {
proposedName = c.ToString(); proposedName = c.ToString();
break; break;
@ -462,6 +436,195 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// for generated names remove number-suffixes // for generated names remove number-suffixes
return SplitName(proposedName, out _); return SplitName(proposedName, out _);
} }
}
public void Run(ILFunction function, ILTransformContext context)
{
this.context = context;
function.AcceptVisitor(this, null);
}
Unit VisitChildren(ILInstruction inst, VariableScope context)
{
foreach (var child in inst.Children)
{
child.AcceptVisitor(this, context);
}
return default;
}
protected override Unit Default(ILInstruction inst, VariableScope context)
{
if (inst is IInstructionWithVariableOperand { Variable: var v })
{
// if there is already a valid name for the variable slot, just use it
string name = context.TryGetExistingName(v);
if (!string.IsNullOrEmpty(name))
{
v.Name = name;
return VisitChildren(inst, context);
}
switch (v.Kind)
{
case VariableKind.Parameter when !v.HasGeneratedName && v.Function.Kind == ILFunctionKind.TopLevelFunction:
// Parameter names of top-level functions are handled in ILReader.CreateILVariable
// and CSharpDecompiler.FixParameterNames
break;
case VariableKind.InitializerTarget: // keep generated names
case VariableKind.NamedArgument:
context.ReserveVariableName(v.Name);
break;
case VariableKind.UsingLocal when v.AddressCount == 0 && v.LoadCount == 0:
// using variables that are not read, will not be declared in source source
break;
case VariableKind.DisplayClassLocal:
v.Name = context.NextDisplayClassLocal();
break;
case VariableKind.Local when v.Index != null:
name = context.TryGetExistingName(v.Function, v.Index.Value);
if (name != null)
{
// make sure all local ILVariables that refer to the same slot in the locals signature
// are assigned the same name.
v.Name = name;
}
else
{
v.Name = context.AssignName(v);
context.AssignNameToLocalSignatureIndex(v.Function, v.Index.Value, v.Name);
}
break;
default:
v.Name = context.AssignName(v);
break;
}
}
return VisitChildren(inst, context);
}
protected internal override Unit VisitILFunction(ILFunction function, VariableScope context)
{
if (function.Kind == ILFunctionKind.LocalFunction)
{
// assign names to local functions
if (!LocalFunctionDecompiler.ParseLocalFunctionName(function.Name, out _, out var newName) || !IsValidName(newName))
newName = null;
if (newName == null)
{
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();
}
else
{
newName = nameWithoutNumber;
}
}
function.Name = newName;
function.ReducedMethod.Name = newName;
context.Add((MethodDefinitionHandle)function.ReducedMethod.MetadataToken, newName);
}
var nestedContext = new VariableScope(function, this.context, context);
base.VisitILFunction(function, nestedContext);
if (function.Kind != ILFunctionKind.TopLevelFunction)
{
foreach (var p in function.Variables.Where(v => v.Kind == VariableKind.Parameter && v.Index >= 0))
{
p.Name = nestedContext.AssignNameIfUnassigned(p);
}
}
return default;
}
protected internal override Unit VisitCall(Call inst, VariableScope context)
{
if (inst.Method is LocalFunctionMethod m)
{
string name = context.TryGetExistingName((MethodDefinitionHandle)m.MetadataToken);
if (!string.IsNullOrEmpty(name))
m.Name = name;
}
return base.VisitCall(inst, context);
}
protected internal override Unit VisitLdFtn(LdFtn inst, VariableScope context)
{
if (inst.Method is LocalFunctionMethod m)
{
string name = context.TryGetExistingName((MethodDefinitionHandle)m.MetadataToken);
if (!string.IsNullOrEmpty(name))
m.Name = name;
}
return base.VisitLdFtn(inst, context);
}
static IEnumerable<string> CollectAllLowerCaseMemberNames(ITypeDefinition type)
{
foreach (var item in type.GetMembers(m => IsLowerCase(m.Name)))
yield return item.Name;
}
static IEnumerable<string> CollectAllLowerCaseTypeNames(ITypeDefinition type)
{
var ns = type.ParentModule.Compilation.GetNamespaceByFullName(type.Namespace);
foreach (var item in ns.Types)
{
if (IsLowerCase(item.Name))
yield return item.Name;
}
}
static bool IsLowerCase(string name)
{
return name.Length > 0 && char.ToLower(name[0]) == name[0];
}
/// <remarks>
/// Must be in sync with <see cref="GetNameFromInstruction" />.
/// </remarks>
internal static bool IsSupportedInstruction(object arg)
{
switch (arg)
{
case GetPinnableReference _:
case LdObj _:
case LdFlda _:
case LdsFlda _:
case CallInstruction _:
return true;
default:
return false;
}
}
internal static bool IsValidName(string varName)
{
if (string.IsNullOrWhiteSpace(varName))
return false;
if (!(char.IsLetter(varName[0]) || varName[0] == '_'))
return false;
for (int i = 1; i < varName.Length; i++)
{
if (!(char.IsLetterOrDigit(varName[i]) || varName[i] == '_'))
return false;
}
return true;
}
static string GetNameFromInstruction(ILInstruction inst) static string GetNameFromInstruction(ILInstruction inst)
{ {
@ -566,22 +729,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
type = NullableType.GetUnderlyingType(((TypeWithElementType)type).ElementType); type = NullableType.GetUnderlyingType(((TypeWithElementType)type).ElementType);
} }
string name = type.Kind switch { string name;
TypeKind.Array => "array",
TypeKind.Pointer => "ptr",
TypeKind.TypeParameter => "val",
TypeKind.Unknown => "val",
TypeKind.Dynamic => "val",
TypeKind.ByReference => "reference",
TypeKind.Tuple => "tuple",
TypeKind.NInt => "num",
TypeKind.NUInt => "num",
_ => null
};
if (name != null)
{
return name;
}
if (type.IsAnonymousType()) if (type.IsAnonymousType())
{ {
name = "anon"; name = "anon";
@ -590,13 +738,28 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{ {
name = "ex"; name = "ex";
} }
else if (type.Name.EndsWith("EventArgs", StringComparison.Ordinal))
{
name = "e";
}
else if (type.IsCSharpNativeIntegerType()) else if (type.IsCSharpNativeIntegerType())
{ {
name = "num"; name = "num";
} }
else if (!typeNameToVariableNameDict.TryGetValue(type.FullName, out name)) else if (!typeNameToVariableNameDict.TryGetValue(type.FullName, out name))
{ {
name = type.Name; name = type.Kind switch {
TypeKind.Array => "array",
TypeKind.Pointer => "ptr",
TypeKind.TypeParameter => "val",
TypeKind.Unknown => "val",
TypeKind.Dynamic => "val",
TypeKind.ByReference => "reference",
TypeKind.Tuple => "tuple",
TypeKind.NInt => "num",
TypeKind.NUInt => "num",
_ => type.Name
};
// remove the 'I' for interfaces // remove the 'I' for interfaces
if (name.Length >= 3 && name[0] == 'I' && char.IsUpper(name[1]) && char.IsLower(name[2])) if (name.Length >= 3 && name[0] == 'I' && char.IsUpper(name[1]) && char.IsLower(name[2]))
name = name.Substring(1); name = name.Substring(1);
@ -657,11 +820,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (name.Length == 0) if (name.Length == 0)
return "obj"; return "obj";
else string lowerCaseName = char.ToLower(name[0]) + name.Substring(1);
return char.ToLower(name[0]) + name.Substring(1); if (CSharp.OutputVisitor.CSharpOutputVisitor.IsKeyword(lowerCaseName))
return null;
return lowerCaseName;
} }
internal static IType GuessType(IType variableType, ILInstruction inst, ILTransformContext context) static IType GuessType(IType variableType, ILInstruction inst, ILTransformContext context)
{ {
if (!variableType.IsKnownType(KnownTypeCode.Object)) if (!variableType.IsKnownType(KnownTypeCode.Object))
return variableType; return variableType;

2
ICSharpCode.Decompiler/Util/CollectionExtensions.cs

@ -225,7 +225,7 @@ namespace ICSharpCode.Decompiler.Util
yield return func(index++, element); yield return func(index++, element);
} }
public static IEnumerable<(int, T)> WithIndex<T>(this ICollection<T> source) public static IEnumerable<(int, T)> WithIndex<T>(this IEnumerable<T> source)
{ {
int index = 0; int index = 0;
foreach (var item in source) foreach (var item in source)

Loading…
Cancel
Save