summaryrefslogtreecommitdiff
path: root/ICSharpCode.Decompiler/Ast/NameVariables.cs
diff options
context:
space:
mode:
Diffstat (limited to 'ICSharpCode.Decompiler/Ast/NameVariables.cs')
-rw-r--r--ICSharpCode.Decompiler/Ast/NameVariables.cs347
1 files changed, 347 insertions, 0 deletions
diff --git a/ICSharpCode.Decompiler/Ast/NameVariables.cs b/ICSharpCode.Decompiler/Ast/NameVariables.cs
new file mode 100644
index 00000000..3da808cb
--- /dev/null
+++ b/ICSharpCode.Decompiler/Ast/NameVariables.cs
@@ -0,0 +1,347 @@
+// 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<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" }
+ };
+
+
+ public static void AssignNamesToVariables(DecompilerContext context, IEnumerable<ILVariable> parameters, IEnumerable<ILVariable> 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<string> fieldNamesInCurrentType;
+ Dictionary<string, int> typeNames = new Dictionary<string, int>();
+
+ 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<ILWhileLoop>()) {
+ 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<ILExpression>()
+ 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<ILExpression>()
+ 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);
+ }
+ }
+}