mirror of https://github.com/icsharpcode/ILSpy.git
				
				
			
			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.
		
		
		
		
		
			
		
			
				
					
					
						
							347 lines
						
					
					
						
							12 KiB
						
					
					
				
			
		
		
	
	
							347 lines
						
					
					
						
							12 KiB
						
					
					
				// 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); | 
						|
		} | 
						|
	} | 
						|
}
 | 
						|
 |