.NET Decompiler with support for PDB generation, ReadyToRun, Metadata (&more) - cross-platform!
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

562 lines
19 KiB

// Copyright (c) 2017 Siegfried Pammer
//
// 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
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Humanizer;
using ICSharpCode.Decompiler.IL;
using ICSharpCode.Decompiler.Semantics;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.TypeSystem.Implementation;
using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.Decompiler.IL.Transforms
{
public class AssignVariableNames : IILTransform
{
static readonly Dictionary<string, string> typeNameToVariableNameDict = new Dictionary<string, string> {
{ "System.Boolean", "flag" },
{ "System.Byte", "b" },
{ "System.SByte", "b" },
{ "System.Int16", "num" },
{ "System.Int32", "num" },
{ "System.Int64", "num" },
{ "System.UInt16", "num" },
{ "System.UInt32", "num" },
{ "System.UInt64", "num" },
{ "System.Single", "num" },
{ "System.Double", "num" },
{ "System.Decimal", "num" },
{ "System.String", "text" },
{ "System.Object", "obj" },
{ "System.Char", "c" }
};
ILTransformContext context;
string[] currentFieldNames;
Dictionary<string, int> reservedVariableNames;
HashSet<ILVariable> loopCounters;
const char maxLoopVariableName = 'n';
public void Run(ILFunction function, ILTransformContext context)
{
this.context = context;
currentFieldNames = function.Method.DeclaringTypeDefinition.Fields.Select(f => f.Name).ToArray();
reservedVariableNames = new Dictionary<string, int>();
loopCounters = CollectLoopCounters(function);
foreach (var f in function.Descendants.OfType<ILFunction>()) {
if (f.Method != null) {
if (IsSetOrEventAccessor(f.Method) && f.Method.Parameters.Count > 0) {
for (int i = 0; i < f.Method.Parameters.Count - 1; i++) {
AddExistingName(reservedVariableNames, f.Method.Parameters[i].Name);
}
var lastParameter = f.Method.Parameters.Last();
switch (f.Method.AccessorOwner) {
case IProperty prop:
if (prop.Setter == f.Method) {
if (prop.Parameters.Any(p => p.Name == "value")) {
f.Warnings.Add("Parameter named \"value\" already present in property signature!");
break;
}
var variableForLastParameter = f.Variables.FirstOrDefault(v => v.Function == f
&& v.Kind == VariableKind.Parameter
&& v.Index == f.Method.Parameters.Count - 1);
if (variableForLastParameter == null) {
AddExistingName(reservedVariableNames, lastParameter.Name);
} else {
if (variableForLastParameter.Name != "value") {
variableForLastParameter.Name = "value";
}
AddExistingName(reservedVariableNames, variableForLastParameter.Name);
}
}
break;
case IEvent ev:
if (f.Method != ev.InvokeAccessor) {
var variableForLastParameter = f.Variables.FirstOrDefault(v => v.Function == f
&& v.Kind == VariableKind.Parameter
&& v.Index == f.Method.Parameters.Count - 1);
if (variableForLastParameter == null) {
AddExistingName(reservedVariableNames, lastParameter.Name);
} else {
if (variableForLastParameter.Name != "value") {
variableForLastParameter.Name = "value";
}
AddExistingName(reservedVariableNames, variableForLastParameter.Name);
}
}
break;
default:
AddExistingName(reservedVariableNames, lastParameter.Name);
break;
}
} else {
foreach (var p in f.Method.Parameters)
AddExistingName(reservedVariableNames, p.Name);
}
} else {
foreach (var p in f.Variables.Where(v => v.Kind == VariableKind.Parameter))
AddExistingName(reservedVariableNames, p.Name);
}
}
foreach (ILFunction f in function.Descendants.OfType<ILFunction>().Reverse()) {
PerformAssignment(f);
}
}
bool IsSetOrEventAccessor(IMethod method)
{
if (method.AccessorOwner is IProperty p)
return p.Setter == method;
if (method.AccessorOwner is IEvent e)
return e.InvokeAccessor != method;
return false;
}
void PerformAssignment(ILFunction function)
{
// remove unused variables before assigning names
function.Variables.RemoveDead();
foreach (var v in function.Variables) {
switch (v.Kind) {
case VariableKind.Parameter: // ignore
break;
case VariableKind.InitializerTarget: // keep generated names
AddExistingName(reservedVariableNames, v.Name);
break;
default:
if (v.HasGeneratedName || !IsValidName(v.Name) || ConflictWithLocal(v)) {
// don't use the name from the debug symbols if it looks like a generated name
v.Name = null;
} else {
// use the name from the debug symbols
// (but ensure we don't use the same name for two variables)
v.Name = GetAlternativeName(v.Name);
}
break;
}
}
// Now generate names:
var mapping = new Dictionary<ILVariable, string>(ILVariableEqualityComparer.Instance);
foreach (var inst in function.Descendants.OfType<IInstructionWithVariableOperand>()) {
var v = inst.Variable;
if (!mapping.TryGetValue(v, out string name)) {
if (string.IsNullOrEmpty(v.Name))
v.Name = GenerateNameForVariable(v);
mapping.Add(v, v.Name);
} else {
v.Name = name;
}
}
}
/// <remarks>
/// Must be in sync with <see cref="GetNameFromInstruction" />.
/// </remarks>
internal static bool IsSupportedInstruction(object arg)
{
switch (arg) {
case LdObj ldobj:
case LdFlda ldflda:
case LdsFlda ldsflda:
case CallInstruction call:
return true;
default:
return false;
}
}
bool ConflictWithLocal(ILVariable v)
{
if (v.Kind == VariableKind.UsingLocal || v.Kind == VariableKind.ForeachLocal) {
if (reservedVariableNames.ContainsKey(v.Name))
return true;
}
return false;
}
static bool IsValidName(string varName)
{
if (string.IsNullOrEmpty(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;
}
public string GetAlternativeName(string oldVariableName)
{
if (oldVariableName.Length == 1 && oldVariableName[0] >= 'i' && oldVariableName[0] <= maxLoopVariableName) {
for (char c = 'i'; c <= maxLoopVariableName; c++) {
if (!reservedVariableNames.ContainsKey(c.ToString())) {
reservedVariableNames.Add(c.ToString(), 1);
return c.ToString();
}
}
}
string nameWithoutDigits = SplitName(oldVariableName, out int number);
if (!reservedVariableNames.ContainsKey(nameWithoutDigits)) {
reservedVariableNames.Add(nameWithoutDigits, number - 1);
}
int count = ++reservedVariableNames[nameWithoutDigits];
if (count != 1) {
return nameWithoutDigits + count.ToString();
} else {
return nameWithoutDigits;
}
}
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;
}
}
}
}
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(currentFieldNames).ToList();
if (proposedNameForAddress.Count > 0) {
proposedName = proposedNameForAddress[0];
}
}
if (string.IsNullOrEmpty(proposedName)) {
var proposedNameForStores = variable.StoreInstructions.OfType<StLoc>()
.Select(expr => GetNameFromInstruction(expr.Value))
.Except(currentFieldNames).ToList();
if (proposedNameForStores.Count == 1) {
proposedName = proposedNameForStores[0];
}
}
if (string.IsNullOrEmpty(proposedName)) {
var proposedNameForLoads = variable.LoadInstructions
.Select(arg => GetNameForArgument(arg.Parent, arg.ChildIndex))
.Except(currentFieldNames).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(currentFieldNames).ToList();
if (proposedNameForStoresFromNewObj.Count == 1) {
proposedName = proposedNameForStoresFromNewObj[0];
}
}
if (string.IsNullOrEmpty(proposedName)) {
proposedName = GetNameByType(variable.Type);
}
// remove any numbers from the proposed name
proposedName = SplitName(proposedName, out int number);
if (!reservedVariableNames.ContainsKey(proposedName)) {
reservedVariableNames.Add(proposedName, 0);
}
int count = ++reservedVariableNames[proposedName];
if (count > 1) {
return proposedName + count.ToString();
} else {
return proposedName;
}
}
static string GetNameFromInstruction(ILInstruction inst)
{
switch (inst) {
case LdObj ldobj:
return GetNameFromInstruction(ldobj.Target);
case LdFlda ldflda:
return CleanUpVariableName(ldflda.Field.Name);
case LdsFlda ldsflda:
return CleanUpVariableName(ldsflda.Field.Name);
case CallInstruction call:
if (call is NewObj) break;
IMethod m = call.Method;
if (m.Name.StartsWith("get_", StringComparison.OrdinalIgnoreCase) && m.Parameters.Count == 0) {
// use name from properties, but not from indexers
return CleanUpVariableName(m.Name.Substring(4));
} else if (m.Name.StartsWith("Get", StringComparison.OrdinalIgnoreCase) && m.Name.Length >= 4 && char.IsUpper(m.Name[3])) {
// use name from Get-methods
return CleanUpVariableName(m.Name.Substring(3));
}
break;
}
return null;
}
static string GetNameForArgument(ILInstruction parent, int i)
{
switch (parent) {
case StObj stobj:
IField field;
if (stobj.Target is LdFlda ldflda)
field = ldflda.Field;
else if (stobj.Target is LdsFlda ldsflda)
field = ldsflda.Field;
else
break;
return CleanUpVariableName(field.Name);
case CallInstruction call:
IMethod m = call.Method;
if (m.Parameters.Count == 1 && i == call.Arguments.Count - 1) {
// argument might be value of a setter
if (m.Name.StartsWith("set_", StringComparison.OrdinalIgnoreCase)) {
return CleanUpVariableName(m.Name.Substring(4));
} else if (m.Name.StartsWith("Set", StringComparison.OrdinalIgnoreCase) && m.Name.Length >= 4 && char.IsUpper(m.Name[3])) {
return CleanUpVariableName(m.Name.Substring(3));
}
}
var p = call.GetParameter(i);
if (p != null && !string.IsNullOrEmpty(p.Name))
return CleanUpVariableName(p.Name);
break;
case Leave ret:
return "result";
}
return null;
}
static string GetNameByType(IType type)
{
type = NullableType.GetUnderlyingType(type);
while (type is ModifiedType || type is PinnedType) {
type = NullableType.GetUnderlyingType(((TypeWithElementType)type).ElementType);
}
string name;
if (type is ArrayType) {
name = "array";
} else if (type is PointerType) {
name = "ptr";
} else if (type.Kind == TypeKind.TypeParameter || type.Kind == TypeKind.Unknown || type.Kind == TypeKind.Dynamic) {
name = "val";
} else if (type.Kind == TypeKind.ByReference) {
name = "reference";
} else if (type.IsAnonymousType()) {
name = "anon";
} else if (type.Name.EndsWith("Exception", StringComparison.Ordinal)) {
name = "ex";
} else if (!typeNameToVariableNameDict.TryGetValue(type.FullName, out name)) {
name = type.Name;
// remove the 'I' for interfaces
if (name.Length >= 3 && name[0] == 'I' && char.IsUpper(name[1]) && char.IsLower(name[2]))
name = name.Substring(1);
name = CleanUpVariableName(name);
}
return name;
}
static void AddExistingName(Dictionary<string, int> reservedVariableNames, string name)
{
if (string.IsNullOrEmpty(name))
return;
string nameWithoutDigits = SplitName(name, out int number);
if (reservedVariableNames.TryGetValue(nameWithoutDigits, out int existingNumber)) {
reservedVariableNames[nameWithoutDigits] = Math.Max(number, existingNumber);
} else {
reservedVariableNames.Add(nameWithoutDigits, number);
}
}
static string SplitName(string name, out int number)
{
// First, identify whether the name already ends with a number:
int pos = name.Length;
while (pos > 0 && name[pos - 1] >= '0' && name[pos - 1] <= '9')
pos--;
if (pos < name.Length) {
if (int.TryParse(name.Substring(pos), out number)) {
return name.Substring(0, pos);
}
}
number = 1;
return name;
}
static string CleanUpVariableName(string name)
{
// remove the backtick (generics)
int pos = name.IndexOf('`');
if (pos >= 0)
name = name.Substring(0, pos);
// remove field prefix:
if (name.Length > 2 && name.StartsWith("m_", StringComparison.Ordinal))
name = name.Substring(2);
else if (name.Length > 1 && name[0] == '_' && (char.IsLetter(name[1]) || name[1] == '_'))
name = name.Substring(1);
if (name.Length == 0)
return "obj";
else
return char.ToLower(name[0]) + name.Substring(1);
}
internal static IType GuessType(IType variableType, ILInstruction inst, ILTransformContext context)
{
if (!variableType.IsKnownType(KnownTypeCode.Object))
return variableType;
IType inferredType = inst.InferType();
if (inferredType.Kind != TypeKind.Unknown)
return inferredType;
else
return variableType;
}
static Dictionary<string, int> CollectReservedVariableNames(ILFunction function, ILVariable existingVariable)
{
var reservedVariableNames = new Dictionary<string, int>();
var rootFunction = function.Ancestors.OfType<ILFunction>().Single(f => f.Parent == null);
foreach (var f in rootFunction.Descendants.OfType<ILFunction>()) {
foreach (var p in rootFunction.Parameters) {
AddExistingName(reservedVariableNames, p.Name);
}
foreach (var v in f.Variables.Where(v => v.Kind != VariableKind.Parameter)) {
if (v != existingVariable)
AddExistingName(reservedVariableNames, v.Name);
}
}
foreach (var f in rootFunction.Method.DeclaringTypeDefinition.Fields.Select(f => f.Name))
AddExistingName(reservedVariableNames, f);
return reservedVariableNames;
}
internal static string GenerateForeachVariableName(ILFunction function, ILInstruction valueContext, ILVariable existingVariable = null)
{
if (function == null)
throw new ArgumentNullException(nameof(function));
var reservedVariableNames = CollectReservedVariableNames(function, existingVariable);
string baseName = GetNameFromInstruction(valueContext);
if (string.IsNullOrEmpty(baseName)) {
if (valueContext is LdLoc ldloc && ldloc.Variable.Kind == VariableKind.Parameter) {
baseName = ldloc.Variable.Name;
}
}
string proposedName = "item";
if (!string.IsNullOrEmpty(baseName)) {
if (!IsPlural(baseName, ref proposedName)) {
if (baseName.Length > 4 && baseName.EndsWith("List", StringComparison.Ordinal)) {
proposedName = baseName.Substring(0, baseName.Length - 4);
} else if (baseName.Equals("list", StringComparison.OrdinalIgnoreCase)) {
proposedName = "item";
} else if (baseName.EndsWith("children", StringComparison.OrdinalIgnoreCase)) {
proposedName = baseName.Remove(baseName.Length - 3);
}
}
}
// remove any numbers from the proposed name
proposedName = SplitName(proposedName, out int number);
if (!reservedVariableNames.ContainsKey(proposedName)) {
reservedVariableNames.Add(proposedName, 0);
}
int count = ++reservedVariableNames[proposedName];
if (count > 1) {
return proposedName + count.ToString();
} else {
return proposedName;
}
}
internal static string GenerateVariableName(ILFunction function, IType type, ILInstruction valueContext = null, ILVariable existingVariable = null)
{
if (function == null)
throw new ArgumentNullException(nameof(function));
var reservedVariableNames = CollectReservedVariableNames(function, existingVariable);
string baseName = valueContext != null ? GetNameFromInstruction(valueContext) ?? GetNameByType(type) : GetNameByType(type);
string proposedName = "obj";
if (!string.IsNullOrEmpty(baseName)) {
if (!IsPlural(baseName, ref proposedName)) {
if (baseName.Length > 4 && baseName.EndsWith("List", StringComparison.Ordinal)) {
proposedName = baseName.Substring(0, baseName.Length - 4);
} else if (baseName.Equals("list", StringComparison.OrdinalIgnoreCase)) {
proposedName = "item";
} else if (baseName.EndsWith("children", StringComparison.OrdinalIgnoreCase)) {
proposedName = baseName.Remove(baseName.Length - 3);
} else {
proposedName = baseName;
}
}
}
// remove any numbers from the proposed name
proposedName = SplitName(proposedName, out int number);
if (!reservedVariableNames.ContainsKey(proposedName)) {
reservedVariableNames.Add(proposedName, 0);
}
int count = ++reservedVariableNames[proposedName];
if (count > 1) {
return proposedName + count.ToString();
} else {
return proposedName;
}
}
private static bool IsPlural(string baseName, ref string proposedName)
{
var newName = baseName.Singularize(inputIsKnownToBePlural: false);
if (newName == baseName)
return false;
proposedName = newName;
return true;
}
}
}