// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team // // 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 ICSharpCode.Decompiler.ILAst; using Mono.Cecil; namespace ICSharpCode.Decompiler.Ast { public class NameVariables { static readonly Dictionary typeNameToVariableNameDict = new Dictionary { { "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" } }; public static void AssignNamesToVariables(DecompilerContext context, IEnumerable parameters, IEnumerable variables, ILBlock methodBody) { NameVariables nv = new NameVariables(); nv.context = context; nv.fieldNamesInCurrentType = context.CurrentType.Fields.Select(f => f.Name).ToList(); // First mark existing variable names as reserved. foreach (string name in context.ReservedVariableNames) nv.AddExistingName(name); foreach (var p in parameters) nv.AddExistingName(p.Name); foreach (var v in variables) { if (v.IsGenerated) { // don't introduce names for variables generated by ILSpy - keep "expr"/"arg" nv.AddExistingName(v.Name); } else if (v.OriginalVariable != null && context.Settings.UseDebugSymbols) { string varName = v.OriginalVariable.Name; if (string.IsNullOrEmpty(varName) || varName.StartsWith("V_", StringComparison.Ordinal) || !IsValidName(varName)) { // 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 = nv.GetAlternativeName(varName); } } else { v.Name = null; } } // Now generate names: foreach (ILVariable p in parameters) { if (string.IsNullOrEmpty(p.Name)) p.Name = nv.GenerateNameForVariable(p, methodBody); } foreach (ILVariable varDef in variables) { if (string.IsNullOrEmpty(varDef.Name)) varDef.Name = nv.GenerateNameForVariable(varDef, methodBody); } } 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; } DecompilerContext context; List fieldNamesInCurrentType; Dictionary typeNames = new Dictionary(); public void AddExistingName(string name) { if (string.IsNullOrEmpty(name)) return; int number; string nameWithoutDigits = SplitName(name, out number); int existingNumber; if (typeNames.TryGetValue(nameWithoutDigits, out existingNumber)) { typeNames[nameWithoutDigits] = Math.Max(number, existingNumber); } else { typeNames.Add(nameWithoutDigits, number); } } 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; } const char maxLoopVariableName = 'n'; public string GetAlternativeName(string oldVariableName) { if (oldVariableName.Length == 1 && oldVariableName[0] >= 'i' && oldVariableName[0] <= maxLoopVariableName) { for (char c = 'i'; c <= maxLoopVariableName; c++) { if (!typeNames.ContainsKey(c.ToString())) { typeNames.Add(c.ToString(), 1); return c.ToString(); } } } int number; string nameWithoutDigits = SplitName(oldVariableName, out number); if (!typeNames.ContainsKey(nameWithoutDigits)) { typeNames.Add(nameWithoutDigits, number - 1); } int count = ++typeNames[nameWithoutDigits]; if (count != 1) { return nameWithoutDigits + count.ToString(); } else { return nameWithoutDigits; } } string GenerateNameForVariable(ILVariable variable, ILBlock methodBody) { string proposedName = null; if (variable.Type == context.CurrentType.Module.TypeSystem.Int32) { // test whether the variable might be a loop counter bool isLoopCounter = false; foreach (ILWhileLoop loop in methodBody.GetSelfAndChildrenRecursive()) { ILExpression expr = loop.Condition; while (expr != null && expr.Code == ILCode.LogicNot) expr = expr.Arguments[0]; if (expr != null) { switch (expr.Code) { case ILCode.Clt: case ILCode.Clt_Un: case ILCode.Cgt: case ILCode.Cgt_Un: case ILCode.Cle: case ILCode.Cle_Un: case ILCode.Cge: case ILCode.Cge_Un: ILVariable loadVar; if (expr.Arguments[0].Match(ILCode.Ldloc, out loadVar) && loadVar == variable) { isLoopCounter = true; } break; } } } if (isLoopCounter) { // For loop variables, use i,j,k,l,m,n for (char c = 'i'; c <= maxLoopVariableName; c++) { if (!typeNames.ContainsKey(c.ToString())) { proposedName = c.ToString(); break; } } } } if (string.IsNullOrEmpty(proposedName)) { var proposedNameForStores = (from expr in methodBody.GetSelfAndChildrenRecursive() where expr.Code == ILCode.Stloc && expr.Operand == variable select GetNameFromExpression(expr.Arguments.Single()) ).Except(fieldNamesInCurrentType).ToList(); if (proposedNameForStores.Count == 1) { proposedName = proposedNameForStores[0]; } } if (string.IsNullOrEmpty(proposedName)) { var proposedNameForLoads = (from expr in methodBody.GetSelfAndChildrenRecursive() from i in Enumerable.Range(0, expr.Arguments.Count) let arg = expr.Arguments[i] where arg.Code == ILCode.Ldloc && arg.Operand == variable select GetNameForArgument(expr, i) ).Except(fieldNamesInCurrentType).ToList(); if (proposedNameForLoads.Count == 1) { proposedName = proposedNameForLoads[0]; } } if (string.IsNullOrEmpty(proposedName)) { proposedName = GetNameByType(variable.Type); } // remove any numbers from the proposed name int number; proposedName = SplitName(proposedName, out number); if (!typeNames.ContainsKey(proposedName)) { typeNames.Add(proposedName, 0); } int count = ++typeNames[proposedName]; if (count > 1) { return proposedName + count.ToString(); } else { return proposedName; } } static string GetNameFromExpression(ILExpression expr) { switch (expr.Code) { case ILCode.Ldfld: case ILCode.Ldsfld: return CleanUpVariableName(((FieldReference)expr.Operand).Name); case ILCode.Call: case ILCode.Callvirt: case ILCode.CallGetter: case ILCode.CallvirtGetter: MethodReference mr = (MethodReference)expr.Operand; if (mr.Name.StartsWith("get_", StringComparison.OrdinalIgnoreCase) && mr.Parameters.Count == 0) { // use name from properties, but not from indexers return CleanUpVariableName(mr.Name.Substring(4)); } else if (mr.Name.StartsWith("Get", StringComparison.OrdinalIgnoreCase) && mr.Name.Length >= 4 && char.IsUpper(mr.Name[3])) { // use name from Get-methods return CleanUpVariableName(mr.Name.Substring(3)); } break; } return null; } static string GetNameForArgument(ILExpression parent, int i) { switch (parent.Code) { case ILCode.Stfld: case ILCode.Stsfld: if (i == parent.Arguments.Count - 1) // last argument is stored value return CleanUpVariableName(((FieldReference)parent.Operand).Name); else break; case ILCode.Call: case ILCode.Callvirt: case ILCode.Newobj: case ILCode.CallGetter: case ILCode.CallvirtGetter: case ILCode.CallSetter: case ILCode.CallvirtSetter: MethodReference methodRef = (MethodReference)parent.Operand; if (methodRef.Parameters.Count == 1 && i == parent.Arguments.Count - 1) { // argument might be value of a setter if (methodRef.Name.StartsWith("set_", StringComparison.OrdinalIgnoreCase)) { return CleanUpVariableName(methodRef.Name.Substring(4)); } else if (methodRef.Name.StartsWith("Set", StringComparison.OrdinalIgnoreCase) && methodRef.Name.Length >= 4 && char.IsUpper(methodRef.Name[3])) { return CleanUpVariableName(methodRef.Name.Substring(3)); } } MethodDefinition methodDef = methodRef.Resolve(); if (methodDef != null) { var p = methodDef.Parameters.ElementAtOrDefault((parent.Code != ILCode.Newobj && methodDef.HasThis) ? i - 1 : i); if (p != null && !string.IsNullOrEmpty(p.Name)) return CleanUpVariableName(p.Name); } break; case ILCode.Ret: return "result"; } return null; } string GetNameByType(TypeReference type) { type = TypeAnalysis.UnpackModifiers(type); GenericInstanceType git = type as GenericInstanceType; if (git != null && git.ElementType.FullName == "System.Nullable`1" && git.GenericArguments.Count == 1) { type = ((GenericInstanceType)type).GenericArguments[0]; } string name; if (type.IsArray) { name = "array"; } else if (type.IsPointer || type.IsByReference) { name = "ptr"; } 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 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] == '_') name = name.Substring(1); if (name.Length == 0) return "obj"; else return char.ToLower(name[0]) + name.Substring(1); } } }