Browse Source

Add VariableScope and rework AssignVariableNames step to support renaming parameters of nested ILFunctions in the future.

pull/3416/head
Siegfried Pammer 4 months ago
parent
commit
03aecf047d
  1. 6
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs
  2. 7
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  3. 10
      ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
  4. 1
      ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs
  5. 1
      ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs
  6. 4
      ICSharpCode.Decompiler/IL/ILReader.cs
  7. 5
      ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs
  8. 684
      ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs
  9. 2
      ICSharpCode.Decompiler/Util/CollectionExtensions.cs

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

@ -377,10 +377,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);
}); });
} }
} }

7
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -2547,6 +2547,11 @@ 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.CreateILVarable(ParameterDefinition)
@ -2554,8 +2559,6 @@ namespace ICSharpCode.Decompiler.CSharp
} }
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++;
} }

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(

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.

684
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;
Dictionary<string, int> reservedVariableNames;
HashSet<ILVariable> loopCounters;
const char maxLoopVariableName = 'n'; const char maxLoopVariableName = 'n';
int numDisplayClassLocals;
public void Run(ILFunction function, ILTransformContext context) public class VariableScope
{ {
this.context = context; 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;
HashSet<ILVariable> loopCounters;
int numDisplayClassLocals;
public VariableScope(ILFunction function, ILTransformContext context, VariableScope parentScope = null)
{
this.function = function;
this.context = context;
this.parentScope = parentScope;
numDisplayClassLocals = 0;
assignedLocalSignatureIndices = new string[function.LocalVariableSignatureLength];
reservedVariableNames = new Dictionary<string, int>();
// find all loop counters in the current function
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);
}
}
}
reservedVariableNames = new Dictionary<string, int>(); // if this is the root scope, we also collect all lower-case type and member names
currentLowerCaseTypeOrMemberNames = new List<string>(); // and fixed parameter names to avoid conflicts when naming local variables.
var currentLowerCaseMemberNames = CollectAllLowerCaseMemberNames(function.Method.DeclaringTypeDefinition); if (parentScope == null)
foreach (var name in currentLowerCaseMemberNames)
currentLowerCaseTypeOrMemberNames.Add(name);
var currentLowerCaseTypeNames = CollectAllLowerCaseTypeNames(function.Method.DeclaringTypeDefinition);
foreach (var name in currentLowerCaseTypeNames)
{
currentLowerCaseTypeOrMemberNames.Add(name);
AddExistingName(reservedVariableNames, name);
}
loopCounters = CollectLoopCounters(function);
foreach (var f in function.Descendants.OfType<ILFunction>())
{
if (f.Method != null)
{ {
if (IsSetOrEventAccessor(f.Method) && f.Method.Parameters.Count > 0) var currentLowerCaseTypeOrMemberNames = new HashSet<string>(StringComparer.Ordinal);
foreach (var name in CollectAllLowerCaseMemberNames(function.Method.DeclaringTypeDefinition))
currentLowerCaseTypeOrMemberNames.Add(name);
foreach (var name in CollectAllLowerCaseTypeNames(function.Method.DeclaringTypeDefinition))
{ {
for (int i = 0; i < f.Method.Parameters.Count - 1; i++) currentLowerCaseTypeOrMemberNames.Add(name);
AddExistingName(reservedVariableNames, name);
}
this.currentLowerCaseTypeOrMemberNames = currentLowerCaseTypeOrMemberNames.ToImmutableHashSet();
// handle implicit parameters of set or event accessors
if (function.Method != null && IsSetOrEventAccessor(function.Method) && function.Parameters.Count > 0)
{
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,183 +173,394 @@ 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())
{
string name = p.Name;
if (string.IsNullOrWhiteSpace(name) && p.Type != SpecialType.ArgList)
{
// needs to be consistent with logic in ILReader.CreateILVarable
name = "P_" + i;
}
if (variables.TryGetValue(i, out var v))
variableMapping[v] = name;
AddExistingName(reservedVariableNames, name);
}
}
static bool IsSetOrEventAccessor(IMethod method)
{
switch (method.AccessorKind)
{
case MethodSemanticsAttributes.Setter:
case MethodSemanticsAttributes.Adder:
case MethodSemanticsAttributes.Remover:
return true;
default:
return false;
}
} }
} }
else else
{ {
foreach (var p in f.Variables.Where(v => v.Kind == VariableKind.Parameter)) this.currentLowerCaseTypeOrMemberNames = parentScope.currentLowerCaseTypeOrMemberNames;
AddExistingName(reservedVariableNames, p.Name); var variables = function.Variables.Where(v => v.Kind == VariableKind.Parameter).ToDictionary(v => v.Index);
foreach (var (i, p) in function.Parameters.WithIndex())
{
if (function.Kind is ILFunctionKind.Delegate or ILFunctionKind.ExpressionTree
&& CSharpDecompiler.IsTransparentIdentifier(p.Name))
{
AddExistingName(reservedVariableNames, p.Name);
if (variables.TryGetValue(i, out var v))
variableMapping[v] = p.Name;
}
if (!parentScope.IsReservedVariableName(p.Name, out _))
{
AddExistingName(reservedVariableNames, p.Name);
if (variables.TryGetValue(i, out var v))
variableMapping[v] = p.Name;
}
}
} }
} }
numDisplayClassLocals = 0;
PerformAssignment(function);
}
static IEnumerable<string> CollectAllLowerCaseMemberNames(ITypeDefinition type) public void Add(MethodDefinitionHandle localFunction, string name)
{ {
foreach (var item in type.GetMembers(m => IsLowerCase(m.Name))) this.localFunctions[localFunction] = name;
yield return item.Name; }
}
static IEnumerable<string> CollectAllLowerCaseTypeNames(ITypeDefinition type) public string TryGetExistingName(MethodDefinitionHandle localFunction)
{
var ns = type.ParentModule.Compilation.GetNamespaceByFullName(type.Namespace);
foreach (var item in ns.Types)
{ {
if (IsLowerCase(item.Name)) if (localFunctions.TryGetValue(localFunction, out var name))
yield return item.Name; return name;
return parentScope?.TryGetExistingName(localFunction);
} }
}
static bool IsLowerCase(string name) public string TryGetExistingName(ILVariable v)
{ {
return name.Length > 0 && char.ToLower(name[0]) == name[0]; if (variableMapping.TryGetValue(v, out var name))
} return name;
return parentScope?.TryGetExistingName(v);
}
bool IsSetOrEventAccessor(IMethod method) public string TryGetExistingName(ILFunction function, int index)
{
switch (method.AccessorKind)
{ {
case MethodSemanticsAttributes.Setter: if (this.function == function)
case MethodSemanticsAttributes.Adder: {
case MethodSemanticsAttributes.Remover: return this.assignedLocalSignatureIndices[index];
}
else
{
return parentScope?.TryGetExistingName(function, index);
}
}
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;
}
public bool IsReservedVariableName(string name, out int index)
{
if (reservedVariableNames.TryGetValue(name, out index))
return true; return true;
default: return parentScope?.IsReservedVariableName(name, out index) ?? false;
return false;
} }
}
void PerformAssignment(ILFunction function) public void ReserveVariableName(string name, int index = 1)
{ {
var localFunctionMapping = new Dictionary<MethodDefinitionHandle, string>(); reservedVariableNames[name] = index;
var variableMapping = new Dictionary<ILVariable, string>(ILVariableEqualityComparer.Instance); }
var assignedLocalSignatureIndices = new Dictionary<(ILFunction, int), string>();
foreach (var inst in function.Descendants) public string NextDisplayClassLocal()
{ {
if (inst is ILFunction { Kind: ILFunctionKind.LocalFunction } localFunction) return parentScope?.NextDisplayClassLocal() ?? "CS$<>8__locals" + (numDisplayClassLocals++);
}
public bool IsLoopCounter(ILVariable v)
{
return loopCounters.Contains(v) || (parentScope?.IsLoopCounter(v) == true);
}
public string AssignName(ILVariable v)
{
// variable has no valid name
string newName = v.Name;
if (v.HasGeneratedName || !IsValidName(newName))
{
// don't use the name from the debug symbols if it looks like a generated name
// generate a new one based on how the variable is used
newName = GenerateNameForVariable(v);
}
// use the existing name and update index appended to future conflicts
string nameWithoutNumber = SplitName(newName, out int newIndex);
if (IsReservedVariableName(nameWithoutNumber, out int lastUsedIndex))
{ {
// assign names to local functions if (v.Type.IsKnownType(KnownTypeCode.Int32) && IsLoopCounter(v))
if (!LocalFunctionDecompiler.ParseLocalFunctionName(localFunction.Name, out _, out var newName) || !IsValidName(newName))
newName = null;
if (newName == null)
{ {
string nameWithoutNumber = "f"; // special case for loop counters,
if (!reservedVariableNames.TryGetValue(nameWithoutNumber, out int currentIndex)) // we don't want them to be named i, i2, ..., but i, j, ...
newName = GenerateNameForVariable(v);
nameWithoutNumber = newName;
newIndex = 1;
}
}
if (IsReservedVariableName(nameWithoutNumber, out lastUsedIndex))
{
// name without number was already used
if (newIndex > lastUsedIndex)
{
// new index is larger than last, so we can use it
}
else
{
// new index is smaller or equal, so we use the next value
newIndex = lastUsedIndex + 1;
}
// resolve conflicts by appending the index to the new name:
newName = nameWithoutNumber + newIndex.ToString();
}
// update the last used index
ReserveVariableName(nameWithoutNumber, newIndex);
variableMapping.Add(v, newName);
return newName;
}
string GenerateNameForVariable(ILVariable variable)
{
string proposedName = null;
if (variable.Type.IsKnownType(KnownTypeCode.Int32))
{
// test whether the variable might be a loop counter
if (loopCounters.Contains(variable))
{
// For loop variables, use i,j,k,l,m,n
for (char c = 'i'; c <= maxLoopVariableName; c++)
{
if (!IsReservedVariableName(c.ToString(), out _))
{
proposedName = c.ToString();
break;
}
}
}
}
// The ComponentResourceManager inside InitializeComponent must be named "resources",
// otherwise the WinForms designer won't load the Form.
if (CSharp.CSharpDecompiler.IsWindowsFormsInitializeComponentMethod(context.Function.Method) && variable.Type.FullName == "System.ComponentModel.ComponentResourceManager")
{
proposedName = "resources";
}
if (string.IsNullOrEmpty(proposedName))
{
var proposedNameForAddress = variable.AddressInstructions.OfType<LdLoca>()
.Select(arg => arg.Parent is CallInstruction c ? c.GetParameter(arg.ChildIndex)?.Name : null)
.Where(arg => !string.IsNullOrWhiteSpace(arg))
.Except(currentLowerCaseTypeOrMemberNames).ToList();
if (proposedNameForAddress.Count > 0)
{
proposedName = proposedNameForAddress[0];
}
}
if (string.IsNullOrEmpty(proposedName))
{
var proposedNameForStores = new HashSet<string>();
foreach (var store in variable.StoreInstructions)
{
if (store is StLoc stloc)
{ {
currentIndex = 1; var name = GetNameFromInstruction(stloc.Value);
if (!currentLowerCaseTypeOrMemberNames.Contains(name))
proposedNameForStores.Add(name);
} }
int count = Math.Max(1, currentIndex) + 1; else if (store is MatchInstruction match && match.SlotInfo == MatchInstruction.SubPatternsSlot)
reservedVariableNames[nameWithoutNumber] = count;
if (count > 1)
{ {
newName = nameWithoutNumber + count.ToString(); var name = GetNameFromInstruction(match.TestedOperand);
if (!currentLowerCaseTypeOrMemberNames.Contains(name))
proposedNameForStores.Add(name);
} }
else else if (store is PinnedRegion pinnedRegion)
{ {
newName = nameWithoutNumber; var name = GetNameFromInstruction(pinnedRegion.Init);
if (!currentLowerCaseTypeOrMemberNames.Contains(name))
proposedNameForStores.Add(name);
} }
} }
localFunction.Name = newName; if (proposedNameForStores.Count == 1)
localFunction.ReducedMethod.Name = newName;
localFunctionMapping[(MethodDefinitionHandle)localFunction.ReducedMethod.MetadataToken] = newName;
}
else if (inst is IInstructionWithVariableOperand i)
{
var v = i.Variable;
// if there is already a valid name for the variable slot, just use it
if (variableMapping.TryGetValue(v, out string name))
{ {
v.Name = name; proposedName = proposedNameForStores.Single();
continue;
} }
switch (v.Kind) }
if (string.IsNullOrEmpty(proposedName))
{
var proposedNameForLoads = variable.LoadInstructions
.Select(arg => GetNameForArgument(arg.Parent, arg.ChildIndex))
.Except(currentLowerCaseTypeOrMemberNames).ToList();
if (proposedNameForLoads.Count == 1)
{ {
case VariableKind.Parameter: proposedName = proposedNameForLoads[0];
// Parameter names are handled in ILReader.CreateILVariable
// and CSharpDecompiler.FixParameterNames
break;
case VariableKind.InitializerTarget: // keep generated names
AddExistingName(reservedVariableNames, v.Name);
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
// are assigned the same name.
v.Name = name;
}
else
{
v.Name = AssignName(v, variableMapping);
// Remember the newly assigned name:
assignedLocalSignatureIndices.Add((v.Function, v.Index.Value), v.Name);
}
break;
default:
v.Name = AssignName(v, variableMapping);
break;
} }
} }
else if (inst is (Call or LdFtn) and IInstructionWithMethodOperand m) if (string.IsNullOrEmpty(proposedName) && variable.Kind == VariableKind.StackSlot)
{ {
// update references to local functions var proposedNameForStoresFromNewObj = variable.StoreInstructions.OfType<StLoc>()
if (m.Method is LocalFunctionMethod lf .Select(expr => GetNameByType(GuessType(variable.Type, expr.Value, context)))
&& localFunctionMapping.TryGetValue((MethodDefinitionHandle)lf.MetadataToken, out var name)) .Except(currentLowerCaseTypeOrMemberNames).ToList();
if (proposedNameForStoresFromNewObj.Count == 1)
{ {
lf.Name = name; proposedName = proposedNameForStoresFromNewObj[0];
} }
} }
if (string.IsNullOrEmpty(proposedName))
{
proposedName = GetNameByType(variable.Type);
}
// for generated names remove number-suffixes
return SplitName(proposedName, out _);
} }
} }
string AssignName(ILVariable v, Dictionary<ILVariable, string> variableMapping) public void Run(ILFunction function, ILTransformContext context)
{
this.context = context;
function.AcceptVisitor(this, null);
}
protected override Unit Default(ILInstruction inst, VariableScope context)
{ {
// variable has no valid name foreach (var child in inst.Children)
string newName = v.Name;
if (v.HasGeneratedName || !IsValidName(newName))
{ {
// don't use the name from the debug symbols if it looks like a generated name child.AcceptVisitor(this, context);
// generate a new one based on how the variable is used
newName = GenerateNameForVariable(v);
} }
// use the existing name and update index appended to future conflicts
string nameWithoutNumber = SplitName(newName, out int newIndex); if (inst is not IInstructionWithVariableOperand { Variable: var v })
if (reservedVariableNames.TryGetValue(nameWithoutNumber, out int lastUsedIndex)) return default;
// if there is already a valid name for the variable slot, just use it
string name = context.TryGetExistingName(v);
if (!string.IsNullOrEmpty(name))
{ {
if (v.Type.IsKnownType(KnownTypeCode.Int32) && loopCounters.Contains(v)) v.Name = name;
{ return default;
// special case for loop counters,
// we don't want them to be named i, i2, ..., but i, j, ...
newName = GenerateNameForVariable(v);
nameWithoutNumber = newName;
newIndex = 1;
}
} }
if (reservedVariableNames.TryGetValue(nameWithoutNumber, out lastUsedIndex))
switch (v.Kind)
{ {
// name without number was already used case VariableKind.Parameter when !v.HasGeneratedName && v.Function.Kind == ILFunctionKind.TopLevelFunction:
if (newIndex > lastUsedIndex) // Parameter names of top-level functions are handled in ILReader.CreateILVariable
{ // and CSharpDecompiler.FixParameterNames
// new index is larger than last, so we can use it break;
} case VariableKind.InitializerTarget: // keep generated names
else case VariableKind.NamedArgument:
context.ReserveVariableName(v.Name);
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 default;
}
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)
{ {
// new index is smaller or equal, so we use the next value string nameWithoutNumber = "f";
newIndex = lastUsedIndex + 1; 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;
}
} }
// resolve conflicts by appending the index to the new name: function.Name = newName;
newName = nameWithoutNumber + newIndex.ToString(); function.ReducedMethod.Name = newName;
context.Add((MethodDefinitionHandle)function.ReducedMethod.MetadataToken, newName);
}
return base.VisitILFunction(function, new VariableScope(function, this.context, context));
}
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;
} }
// update the last used index
reservedVariableNames[nameWithoutNumber] = newIndex; return base.VisitCall(inst, context);
variableMapping.Add(v, newName); }
return newName;
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> /// <remarks>
@ -351,118 +595,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true; 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 proposedName = null;
if (variable.Type.IsKnownType(KnownTypeCode.Int32))
{
// test whether the variable might be a loop counter
if (loopCounters.Contains(variable))
{
// For loop variables, use i,j,k,l,m,n
for (char c = 'i'; c <= maxLoopVariableName; c++)
{
if (!reservedVariableNames.ContainsKey(c.ToString()))
{
proposedName = c.ToString();
break;
}
}
}
}
// The ComponentResourceManager inside InitializeComponent must be named "resources",
// otherwise the WinForms designer won't load the Form.
if (CSharp.CSharpDecompiler.IsWindowsFormsInitializeComponentMethod(context.Function.Method) && variable.Type.FullName == "System.ComponentModel.ComponentResourceManager")
{
proposedName = "resources";
}
if (string.IsNullOrEmpty(proposedName))
{
var proposedNameForAddress = variable.AddressInstructions.OfType<LdLoca>()
.Select(arg => arg.Parent is CallInstruction c ? c.GetParameter(arg.ChildIndex)?.Name : null)
.Where(arg => !string.IsNullOrWhiteSpace(arg))
.Except(currentLowerCaseTypeOrMemberNames).ToList();
if (proposedNameForAddress.Count > 0)
{
proposedName = proposedNameForAddress[0];
}
}
if (string.IsNullOrEmpty(proposedName))
{
var proposedNameForStores = new HashSet<string>();
foreach (var store in variable.StoreInstructions)
{
if (store is StLoc stloc)
{
var name = GetNameFromInstruction(stloc.Value);
if (!currentLowerCaseTypeOrMemberNames.Contains(name))
proposedNameForStores.Add(name);
}
else if (store is MatchInstruction match && match.SlotInfo == MatchInstruction.SubPatternsSlot)
{
var name = GetNameFromInstruction(match.TestedOperand);
if (!currentLowerCaseTypeOrMemberNames.Contains(name))
proposedNameForStores.Add(name);
}
else if (store is PinnedRegion pinnedRegion)
{
var name = GetNameFromInstruction(pinnedRegion.Init);
if (!currentLowerCaseTypeOrMemberNames.Contains(name))
proposedNameForStores.Add(name);
}
}
if (proposedNameForStores.Count == 1)
{
proposedName = proposedNameForStores.Single();
}
}
if (string.IsNullOrEmpty(proposedName))
{
var proposedNameForLoads = variable.LoadInstructions
.Select(arg => GetNameForArgument(arg.Parent, arg.ChildIndex))
.Except(currentLowerCaseTypeOrMemberNames).ToList();
if (proposedNameForLoads.Count == 1)
{
proposedName = proposedNameForLoads[0];
}
}
if (string.IsNullOrEmpty(proposedName) && variable.Kind == VariableKind.StackSlot)
{
var proposedNameForStoresFromNewObj = variable.StoreInstructions.OfType<StLoc>()
.Select(expr => GetNameByType(GuessType(variable.Type, expr.Value, context)))
.Except(currentLowerCaseTypeOrMemberNames).ToList();
if (proposedNameForStoresFromNewObj.Count == 1)
{
proposedName = proposedNameForStoresFromNewObj[0];
}
}
if (string.IsNullOrEmpty(proposedName))
{
proposedName = GetNameByType(variable.Type);
}
// for generated names remove number-suffixes
return SplitName(proposedName, out _);
}
static string GetNameFromInstruction(ILInstruction inst) static string GetNameFromInstruction(ILInstruction inst)
{ {
switch (inst) switch (inst)
@ -663,7 +795,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return lowerCaseName; 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