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.

988 lines
31 KiB

  1. // Copyright (c) 2017 Siegfried Pammer
  2. //
  3. // Permission is hereby granted, free of charge, to any person obtaining a copy of this
  4. // software and associated documentation files (the "Software"), to deal in the Software
  5. // without restriction, including without limitation the rights to use, copy, modify, merge,
  6. // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
  7. // to whom the Software is furnished to do so, subject to the following conditions:
  8. //
  9. // The above copyright notice and this permission notice shall be included in all copies or
  10. // substantial portions of the Software.
  11. //
  12. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  13. // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  14. // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
  15. // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  16. // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  17. // DEALINGS IN THE SOFTWARE.
  18. using System;
  19. using System.Collections.Generic;
  20. using System.Collections.Immutable;
  21. using System.Diagnostics;
  22. using System.Linq;
  23. using System.Reflection;
  24. using System.Reflection.Metadata;
  25. using Humanizer.Inflections;
  26. using ICSharpCode.Decompiler.CSharp;
  27. using ICSharpCode.Decompiler.CSharp.OutputVisitor;
  28. using ICSharpCode.Decompiler.TypeSystem;
  29. using ICSharpCode.Decompiler.TypeSystem.Implementation;
  30. using ICSharpCode.Decompiler.Util;
  31. namespace ICSharpCode.Decompiler.IL.Transforms
  32. {
  33. public class AssignVariableNames : ILVisitor<AssignVariableNames.VariableScope, Unit>, IILTransform
  34. {
  35. static readonly Dictionary<string, string> typeNameToVariableNameDict = new Dictionary<string, string> {
  36. { "System.Boolean", "flag" },
  37. { "System.Byte", "b" },
  38. { "System.SByte", "b" },
  39. { "System.Int16", "num" },
  40. { "System.Int32", "num" },
  41. { "System.Int64", "num" },
  42. { "System.UInt16", "num" },
  43. { "System.UInt32", "num" },
  44. { "System.UInt64", "num" },
  45. { "System.Single", "num" },
  46. { "System.Double", "num" },
  47. { "System.Decimal", "num" },
  48. { "System.String", "text" },
  49. { "System.Object", "obj" },
  50. { "System.Char", "c" }
  51. };
  52. ILTransformContext context;
  53. const char maxLoopVariableName = 'n';
  54. public class VariableScope
  55. {
  56. readonly ILTransformContext context;
  57. readonly VariableScope parentScope;
  58. readonly ILFunction function;
  59. readonly Dictionary<MethodDefinitionHandle, string> localFunctions = new();
  60. readonly Dictionary<ILVariable, string> variableMapping = new(ILVariableEqualityComparer.Instance);
  61. readonly string[] assignedLocalSignatureIndices;
  62. IImmutableSet<string> currentLowerCaseTypeOrMemberNames;
  63. Dictionary<string, int> reservedVariableNames;
  64. HashSet<ILVariable> loopCounters;
  65. int numDisplayClassLocals;
  66. public VariableScope(ILFunction function, ILTransformContext context, VariableScope parentScope = null)
  67. {
  68. this.function = function;
  69. this.context = context;
  70. this.parentScope = parentScope;
  71. numDisplayClassLocals = 0;
  72. assignedLocalSignatureIndices = new string[function.LocalVariableSignatureLength];
  73. reservedVariableNames = new Dictionary<string, int>();
  74. // find all loop counters in the current function
  75. loopCounters = new HashSet<ILVariable>();
  76. foreach (var inst in TreeTraversal.PreOrder((ILInstruction)function, i => i.Children))
  77. {
  78. if (inst is ILFunction && inst != function)
  79. break;
  80. if (inst is BlockContainer { Kind: ContainerKind.For, Blocks: [.., var incrementBlock] })
  81. {
  82. foreach (var i in incrementBlock.Instructions)
  83. {
  84. if (HighLevelLoopTransform.MatchIncrement(i, out var variable))
  85. loopCounters.Add(variable);
  86. }
  87. }
  88. }
  89. // if this is the root scope, we also collect all lower-case type and member names
  90. // and fixed parameter names to avoid conflicts when naming local variables.
  91. if (parentScope == null)
  92. {
  93. var currentLowerCaseTypeOrMemberNames = new HashSet<string>(StringComparer.Ordinal);
  94. foreach (var name in CollectAllLowerCaseMemberNames(function.Method.DeclaringTypeDefinition))
  95. currentLowerCaseTypeOrMemberNames.Add(name);
  96. foreach (var name in CollectAllLowerCaseTypeNames(function.Method.DeclaringTypeDefinition))
  97. {
  98. currentLowerCaseTypeOrMemberNames.Add(name);
  99. AddExistingName(reservedVariableNames, name);
  100. }
  101. this.currentLowerCaseTypeOrMemberNames = currentLowerCaseTypeOrMemberNames.ToImmutableHashSet();
  102. // handle implicit parameters of set or event accessors
  103. if (function.Method != null && IsSetOrEventAccessor(function.Method) && function.Parameters.Count > 0)
  104. {
  105. for (int i = 0; i < function.Method.Parameters.Count - 1; i++)
  106. {
  107. AddExistingName(reservedVariableNames, function.Method.Parameters[i].Name);
  108. }
  109. var lastParameter = function.Method.Parameters.Last();
  110. switch (function.Method.AccessorOwner)
  111. {
  112. case IProperty prop:
  113. if (function.Method.AccessorKind == MethodSemanticsAttributes.Setter)
  114. {
  115. if (prop.Parameters.Any(p => p.Name == "value"))
  116. {
  117. function.Warnings.Add("Parameter named \"value\" already present in property signature!");
  118. break;
  119. }
  120. var variableForLastParameter = function.Variables.FirstOrDefault(v => v.Function == function
  121. && v.Kind == VariableKind.Parameter
  122. && v.Index == function.Method.Parameters.Count - 1);
  123. if (variableForLastParameter == null)
  124. {
  125. AddExistingName(reservedVariableNames, lastParameter.Name);
  126. }
  127. else
  128. {
  129. if (variableForLastParameter.Name != "value")
  130. {
  131. variableForLastParameter.Name = "value";
  132. }
  133. AddExistingName(reservedVariableNames, variableForLastParameter.Name);
  134. }
  135. }
  136. break;
  137. case IEvent ev:
  138. if (function.Method.AccessorKind != MethodSemanticsAttributes.Raiser)
  139. {
  140. var variableForLastParameter = function.Variables.FirstOrDefault(v => v.Function == function
  141. && v.Kind == VariableKind.Parameter
  142. && v.Index == function.Method.Parameters.Count - 1);
  143. if (variableForLastParameter == null)
  144. {
  145. AddExistingName(reservedVariableNames, lastParameter.Name);
  146. }
  147. else
  148. {
  149. if (variableForLastParameter.Name != "value")
  150. {
  151. variableForLastParameter.Name = "value";
  152. }
  153. AddExistingName(reservedVariableNames, variableForLastParameter.Name);
  154. }
  155. }
  156. break;
  157. default:
  158. AddExistingName(reservedVariableNames, lastParameter.Name);
  159. break;
  160. }
  161. }
  162. else
  163. {
  164. var variables = function.Variables.Where(v => v.Kind == VariableKind.Parameter && v.Index >= 0).ToDictionary(v => v.Index);
  165. foreach (var (i, p) in function.Parameters.WithIndex())
  166. {
  167. string name = p.Name;
  168. if (string.IsNullOrWhiteSpace(name) && p.Type != SpecialType.ArgList)
  169. {
  170. // needs to be consistent with logic in ILReader.CreateILVariable
  171. name = "P_" + i;
  172. }
  173. if (variables.TryGetValue(i, out var v))
  174. variableMapping[v] = name;
  175. AddExistingName(reservedVariableNames, name);
  176. }
  177. }
  178. static bool IsSetOrEventAccessor(IMethod method)
  179. {
  180. switch (method.AccessorKind)
  181. {
  182. case MethodSemanticsAttributes.Setter:
  183. case MethodSemanticsAttributes.Adder:
  184. case MethodSemanticsAttributes.Remover:
  185. return true;
  186. default:
  187. return false;
  188. }
  189. }
  190. }
  191. else
  192. {
  193. this.currentLowerCaseTypeOrMemberNames = parentScope.currentLowerCaseTypeOrMemberNames;
  194. var variables = function.Variables.Where(v => v.Kind == VariableKind.Parameter).ToDictionary(v => v.Index);
  195. foreach (var (i, p) in function.Parameters.WithIndex())
  196. {
  197. if (function.Kind is ILFunctionKind.Delegate or ILFunctionKind.ExpressionTree
  198. && CSharpDecompiler.IsTransparentIdentifier(p.Name))
  199. {
  200. AddExistingName(reservedVariableNames, p.Name);
  201. if (variables.TryGetValue(i, out var v))
  202. variableMapping[v] = p.Name;
  203. }
  204. if (!parentScope.IsReservedVariableName(p.Name, out _))
  205. {
  206. AddExistingName(reservedVariableNames, p.Name);
  207. if (variables.TryGetValue(i, out var v))
  208. variableMapping[v] = p.Name;
  209. }
  210. else if (variables.TryGetValue(i, out var v))
  211. {
  212. v.HasGeneratedName = true;
  213. }
  214. }
  215. }
  216. }
  217. public void Add(MethodDefinitionHandle localFunction, string name)
  218. {
  219. this.localFunctions[localFunction] = name;
  220. }
  221. public string TryGetExistingName(MethodDefinitionHandle localFunction)
  222. {
  223. if (localFunctions.TryGetValue(localFunction, out var name))
  224. return name;
  225. return parentScope?.TryGetExistingName(localFunction);
  226. }
  227. public string TryGetExistingName(ILVariable v)
  228. {
  229. if (variableMapping.TryGetValue(v, out var name))
  230. return name;
  231. return parentScope?.TryGetExistingName(v);
  232. }
  233. public string TryGetExistingName(ILFunction function, int index)
  234. {
  235. if (this.function == function)
  236. {
  237. return this.assignedLocalSignatureIndices[index];
  238. }
  239. else
  240. {
  241. return parentScope?.TryGetExistingName(function, index);
  242. }
  243. }
  244. public void AssignNameToLocalSignatureIndex(ILFunction function, int index, string name)
  245. {
  246. var scope = this;
  247. while (scope != null && scope.function != function)
  248. scope = scope.parentScope;
  249. Debug.Assert(scope != null);
  250. scope.assignedLocalSignatureIndices[index] = name;
  251. }
  252. public bool IsReservedVariableName(string name, out int index)
  253. {
  254. if (reservedVariableNames.TryGetValue(name, out index))
  255. return true;
  256. return parentScope?.IsReservedVariableName(name, out index) ?? false;
  257. }
  258. public void ReserveVariableName(string name, int index = 1)
  259. {
  260. reservedVariableNames[name] = index;
  261. }
  262. public string NextDisplayClassLocal()
  263. {
  264. return parentScope?.NextDisplayClassLocal() ?? "CS$<>8__locals" + (numDisplayClassLocals++);
  265. }
  266. public bool IsLoopCounter(ILVariable v)
  267. {
  268. return loopCounters.Contains(v) || (parentScope?.IsLoopCounter(v) == true);
  269. }
  270. public string AssignNameIfUnassigned(ILVariable v)
  271. {
  272. if (variableMapping.TryGetValue(v, out var name))
  273. return name;
  274. return AssignName(v);
  275. }
  276. public string AssignName(ILVariable v)
  277. {
  278. // variable has no valid name
  279. string newName = v.Name;
  280. if (v.HasGeneratedName || !IsValidName(newName))
  281. {
  282. // don't use the name from the debug symbols if it looks like a generated name
  283. // generate a new one based on how the variable is used
  284. newName = GenerateNameForVariable(v);
  285. }
  286. // use the existing name and update index appended to future conflicts
  287. string nameWithoutNumber = SplitName(newName, out int newIndex);
  288. if (IsReservedVariableName(nameWithoutNumber, out int lastUsedIndex))
  289. {
  290. if (v.Type.IsKnownType(KnownTypeCode.Int32) && IsLoopCounter(v))
  291. {
  292. // special case for loop counters,
  293. // we don't want them to be named i, i2, ..., but i, j, ...
  294. newName = GenerateNameForVariable(v);
  295. nameWithoutNumber = newName;
  296. newIndex = 1;
  297. }
  298. }
  299. if (IsReservedVariableName(nameWithoutNumber, out lastUsedIndex))
  300. {
  301. // name without number was already used
  302. if (newIndex > lastUsedIndex)
  303. {
  304. // new index is larger than last, so we can use it
  305. }
  306. else
  307. {
  308. // new index is smaller or equal, so we use the next value
  309. newIndex = lastUsedIndex + 1;
  310. }
  311. // resolve conflicts by appending the index to the new name:
  312. newName = nameWithoutNumber + newIndex.ToString();
  313. }
  314. // update the last used index
  315. ReserveVariableName(nameWithoutNumber, newIndex);
  316. variableMapping.Add(v, newName);
  317. return newName;
  318. }
  319. string GenerateNameForVariable(ILVariable variable)
  320. {
  321. string proposedName = null;
  322. if (variable.Type.IsKnownType(KnownTypeCode.Int32))
  323. {
  324. // test whether the variable might be a loop counter
  325. if (loopCounters.Contains(variable))
  326. {
  327. // For loop variables, use i,j,k,l,m,n
  328. for (char c = 'i'; c <= maxLoopVariableName; c++)
  329. {
  330. if (!IsReservedVariableName(c.ToString(), out _))
  331. {
  332. proposedName = c.ToString();
  333. break;
  334. }
  335. }
  336. }
  337. }
  338. // The ComponentResourceManager inside InitializeComponent must be named "resources",
  339. // otherwise the WinForms designer won't load the Form.
  340. if (CSharp.CSharpDecompiler.IsWindowsFormsInitializeComponentMethod(context.Function.Method) && variable.Type.FullName == "System.ComponentModel.ComponentResourceManager")
  341. {
  342. proposedName = "resources";
  343. }
  344. if (string.IsNullOrEmpty(proposedName))
  345. {
  346. var proposedNameForAddress = variable.AddressInstructions.OfType<LdLoca>()
  347. .Select(arg => arg.Parent is CallInstruction c ? c.GetParameter(arg.ChildIndex)?.Name : null)
  348. .Where(arg => !string.IsNullOrWhiteSpace(arg))
  349. .Except(currentLowerCaseTypeOrMemberNames).ToList();
  350. if (proposedNameForAddress.Count > 0)
  351. {
  352. proposedName = proposedNameForAddress[0];
  353. }
  354. }
  355. if (string.IsNullOrEmpty(proposedName))
  356. {
  357. var proposedNameForStores = new HashSet<string>();
  358. foreach (var store in variable.StoreInstructions)
  359. {
  360. if (store is StLoc stloc)
  361. {
  362. var name = GetNameFromInstruction(stloc.Value);
  363. if (!currentLowerCaseTypeOrMemberNames.Contains(name))
  364. proposedNameForStores.Add(name);
  365. }
  366. else if (store is MatchInstruction match && match.SlotInfo == MatchInstruction.SubPatternsSlot)
  367. {
  368. var name = GetNameFromInstruction(match.TestedOperand);
  369. if (!currentLowerCaseTypeOrMemberNames.Contains(name))
  370. proposedNameForStores.Add(name);
  371. }
  372. else if (store is PinnedRegion pinnedRegion)
  373. {
  374. var name = GetNameFromInstruction(pinnedRegion.Init);
  375. if (!currentLowerCaseTypeOrMemberNames.Contains(name))
  376. proposedNameForStores.Add(name);
  377. }
  378. }
  379. if (proposedNameForStores.Count == 1)
  380. {
  381. proposedName = proposedNameForStores.Single();
  382. }
  383. }
  384. if (string.IsNullOrEmpty(proposedName))
  385. {
  386. var proposedNameForLoads = variable.LoadInstructions
  387. .Select(arg => GetNameForArgument(arg.Parent, arg.ChildIndex))
  388. .Except(currentLowerCaseTypeOrMemberNames).ToList();
  389. if (proposedNameForLoads.Count == 1)
  390. {
  391. proposedName = proposedNameForLoads[0];
  392. }
  393. }
  394. if (string.IsNullOrEmpty(proposedName) && variable.Kind == VariableKind.StackSlot)
  395. {
  396. var proposedNameForStoresFromNewObj = variable.StoreInstructions.OfType<StLoc>()
  397. .Select(expr => GetNameByType(GuessType(variable.Type, expr.Value, context)))
  398. .Except(currentLowerCaseTypeOrMemberNames).ToList();
  399. if (proposedNameForStoresFromNewObj.Count == 1)
  400. {
  401. proposedName = proposedNameForStoresFromNewObj[0];
  402. }
  403. }
  404. if (string.IsNullOrEmpty(proposedName))
  405. {
  406. proposedName = GetNameByType(variable.Type);
  407. }
  408. // for generated names remove number-suffixes
  409. return SplitName(proposedName, out _);
  410. }
  411. }
  412. public void Run(ILFunction function, ILTransformContext context)
  413. {
  414. this.context = context;
  415. function.AcceptVisitor(this, null);
  416. }
  417. Unit VisitChildren(ILInstruction inst, VariableScope context)
  418. {
  419. foreach (var child in inst.Children)
  420. {
  421. child.AcceptVisitor(this, context);
  422. }
  423. return default;
  424. }
  425. protected override Unit Default(ILInstruction inst, VariableScope context)
  426. {
  427. if (inst is IInstructionWithVariableOperand { Variable: var v })
  428. {
  429. // if there is already a valid name for the variable slot, just use it
  430. string name = context.TryGetExistingName(v);
  431. if (!string.IsNullOrEmpty(name))
  432. {
  433. v.Name = name;
  434. return VisitChildren(inst, context);
  435. }
  436. switch (v.Kind)
  437. {
  438. case VariableKind.Parameter when !v.HasGeneratedName && v.Function.Kind == ILFunctionKind.TopLevelFunction:
  439. // Parameter names of top-level functions are handled in ILReader.CreateILVariable
  440. // and CSharpDecompiler.FixParameterNames
  441. break;
  442. case VariableKind.InitializerTarget: // keep generated names
  443. case VariableKind.NamedArgument:
  444. context.ReserveVariableName(v.Name);
  445. break;
  446. case VariableKind.UsingLocal when v.AddressCount == 0 && v.LoadCount == 0:
  447. // using variables that are not read, will not be declared in source source
  448. break;
  449. case VariableKind.DisplayClassLocal:
  450. v.Name = context.NextDisplayClassLocal();
  451. break;
  452. case VariableKind.Local when v.Index != null:
  453. name = context.TryGetExistingName(v.Function, v.Index.Value);
  454. if (name != null)
  455. {
  456. // make sure all local ILVariables that refer to the same slot in the locals signature
  457. // are assigned the same name.
  458. v.Name = name;
  459. }
  460. else
  461. {
  462. v.Name = context.AssignName(v);
  463. context.AssignNameToLocalSignatureIndex(v.Function, v.Index.Value, v.Name);
  464. }
  465. break;
  466. default:
  467. v.Name = context.AssignName(v);
  468. break;
  469. }
  470. }
  471. return VisitChildren(inst, context);
  472. }
  473. protected internal override Unit VisitILFunction(ILFunction function, VariableScope context)
  474. {
  475. if (function.Kind == ILFunctionKind.LocalFunction)
  476. {
  477. // assign names to local functions
  478. if (!LocalFunctionDecompiler.ParseLocalFunctionName(function.Name, out _, out var newName) || !IsValidName(newName))
  479. newName = null;
  480. if (newName == null)
  481. {
  482. string nameWithoutNumber = "f";
  483. if (!context.IsReservedVariableName(nameWithoutNumber, out int currentIndex))
  484. {
  485. currentIndex = 1;
  486. }
  487. int count = Math.Max(1, currentIndex) + 1;
  488. context.ReserveVariableName(nameWithoutNumber, count);
  489. if (count > 1)
  490. {
  491. newName = nameWithoutNumber + count.ToString();
  492. }
  493. else
  494. {
  495. newName = nameWithoutNumber;
  496. }
  497. }
  498. function.Name = newName;
  499. function.ReducedMethod.Name = newName;
  500. context.Add((MethodDefinitionHandle)function.ReducedMethod.MetadataToken, newName);
  501. }
  502. var nestedContext = new VariableScope(function, this.context, context);
  503. base.VisitILFunction(function, nestedContext);
  504. if (function.Kind != ILFunctionKind.TopLevelFunction)
  505. {
  506. foreach (var p in function.Variables.Where(v => v.Kind == VariableKind.Parameter && v.Index >= 0))
  507. {
  508. p.Name = nestedContext.AssignNameIfUnassigned(p);
  509. }
  510. }
  511. return default;
  512. }
  513. protected internal override Unit VisitCall(Call inst, VariableScope context)
  514. {
  515. if (inst.Method is LocalFunctionMethod m)
  516. {
  517. string name = context.TryGetExistingName((MethodDefinitionHandle)m.MetadataToken);
  518. if (!string.IsNullOrEmpty(name))
  519. m.Name = name;
  520. }
  521. return base.VisitCall(inst, context);
  522. }
  523. protected internal override Unit VisitLdFtn(LdFtn inst, VariableScope context)
  524. {
  525. if (inst.Method is LocalFunctionMethod m)
  526. {
  527. string name = context.TryGetExistingName((MethodDefinitionHandle)m.MetadataToken);
  528. if (!string.IsNullOrEmpty(name))
  529. m.Name = name;
  530. }
  531. return base.VisitLdFtn(inst, context);
  532. }
  533. static IEnumerable<string> CollectAllLowerCaseMemberNames(ITypeDefinition type)
  534. {
  535. foreach (var item in type.GetMembers(m => IsLowerCase(m.Name)))
  536. yield return item.Name;
  537. }
  538. static IEnumerable<string> CollectAllLowerCaseTypeNames(ITypeDefinition type)
  539. {
  540. var ns = type.ParentModule.Compilation.GetNamespaceByFullName(type.Namespace);
  541. foreach (var item in ns.Types)
  542. {
  543. if (IsLowerCase(item.Name))
  544. yield return item.Name;
  545. }
  546. }
  547. static bool IsLowerCase(string name)
  548. {
  549. return name.Length > 0 && char.ToLower(name[0]) == name[0];
  550. }
  551. /// <remarks>
  552. /// Must be in sync with <see cref="GetNameFromInstruction" />.
  553. /// </remarks>
  554. internal static bool IsSupportedInstruction(object arg)
  555. {
  556. switch (arg)
  557. {
  558. case GetPinnableReference _:
  559. case LdObj _:
  560. case LdFlda _:
  561. case LdsFlda _:
  562. case CallInstruction _:
  563. return true;
  564. default:
  565. return false;
  566. }
  567. }
  568. internal static bool IsValidName(string varName)
  569. {
  570. if (string.IsNullOrWhiteSpace(varName))
  571. return false;
  572. if (!(char.IsLetter(varName[0]) || varName[0] == '_'))
  573. return false;
  574. for (int i = 1; i < varName.Length; i++)
  575. {
  576. if (!(char.IsLetterOrDigit(varName[i]) || varName[i] == '_'))
  577. return false;
  578. }
  579. return true;
  580. }
  581. static string GetNameFromInstruction(ILInstruction inst)
  582. {
  583. switch (inst)
  584. {
  585. case GetPinnableReference getPinnableReference:
  586. return GetNameFromInstruction(getPinnableReference.Argument);
  587. case LdObj ldobj:
  588. return GetNameFromInstruction(ldobj.Target);
  589. case LdFlda ldflda:
  590. if (ldflda.Field.IsCompilerGeneratedOrIsInCompilerGeneratedClass())
  591. return GetNameFromInstruction(ldflda.Target);
  592. return CleanUpVariableName(ldflda.Field.Name);
  593. case LdsFlda ldsflda:
  594. return CleanUpVariableName(ldsflda.Field.Name);
  595. case CallInstruction call:
  596. if (call is NewObj)
  597. break;
  598. IMethod m = call.Method;
  599. if (ExcludeMethodFromCandidates(m))
  600. break;
  601. if (m.Name.StartsWith("get_", StringComparison.OrdinalIgnoreCase) && m.Parameters.Count == 0)
  602. {
  603. // use name from properties, but not from indexers
  604. return CleanUpVariableName(m.Name.Substring(4));
  605. }
  606. else if (m.Name.StartsWith("Get", StringComparison.OrdinalIgnoreCase) && m.Name.Length >= 4 && char.IsUpper(m.Name[3]))
  607. {
  608. // use name from Get-methods
  609. return CleanUpVariableName(m.Name.Substring(3));
  610. }
  611. break;
  612. case DynamicInvokeMemberInstruction dynInvokeMember:
  613. if (dynInvokeMember.Name.StartsWith("Get", StringComparison.OrdinalIgnoreCase)
  614. && dynInvokeMember.Name.Length >= 4 && char.IsUpper(dynInvokeMember.Name[3]))
  615. {
  616. // use name from Get-methods
  617. return CleanUpVariableName(dynInvokeMember.Name.Substring(3));
  618. }
  619. break;
  620. }
  621. return null;
  622. }
  623. static string GetNameForArgument(ILInstruction parent, int i)
  624. {
  625. switch (parent)
  626. {
  627. case StObj stobj:
  628. IField field;
  629. if (stobj.Target is LdFlda ldflda)
  630. field = ldflda.Field;
  631. else if (stobj.Target is LdsFlda ldsflda)
  632. field = ldsflda.Field;
  633. else
  634. break;
  635. return CleanUpVariableName(field.Name);
  636. case CallInstruction call:
  637. IMethod m = call.Method;
  638. if (ExcludeMethodFromCandidates(m))
  639. return null;
  640. if (m.Parameters.Count == 1 && i == call.Arguments.Count - 1)
  641. {
  642. // argument might be value of a setter
  643. if (m.Name.StartsWith("set_", StringComparison.OrdinalIgnoreCase))
  644. {
  645. return CleanUpVariableName(m.Name.Substring(4));
  646. }
  647. else if (m.Name.StartsWith("Set", StringComparison.OrdinalIgnoreCase) && m.Name.Length >= 4 && char.IsUpper(m.Name[3]))
  648. {
  649. return CleanUpVariableName(m.Name.Substring(3));
  650. }
  651. }
  652. var p = call.GetParameter(i);
  653. if (p != null && !string.IsNullOrEmpty(p.Name))
  654. return CleanUpVariableName(p.Name);
  655. break;
  656. case Leave _:
  657. return "result";
  658. }
  659. return null;
  660. }
  661. static bool ExcludeMethodFromCandidates(IMethod m)
  662. {
  663. if (m.SymbolKind == SymbolKind.Operator)
  664. return true;
  665. if (m.Name == "ToString")
  666. return true;
  667. if (m.Name == "Concat" && m.DeclaringType.IsKnownType(KnownTypeCode.String))
  668. return true;
  669. if (m.Name == "GetPinnableReference")
  670. return true;
  671. return false;
  672. }
  673. static string GetNameByType(IType type)
  674. {
  675. type = NullableType.GetUnderlyingType(type);
  676. while (type is ModifiedType || type is PinnedType)
  677. {
  678. type = NullableType.GetUnderlyingType(((TypeWithElementType)type).ElementType);
  679. }
  680. string name = type.Kind switch {
  681. TypeKind.Array => "array",
  682. TypeKind.Pointer => "ptr",
  683. TypeKind.TypeParameter => "val",
  684. TypeKind.Unknown => "val",
  685. TypeKind.Dynamic => "val",
  686. TypeKind.ByReference => "reference",
  687. TypeKind.Tuple => "tuple",
  688. TypeKind.NInt => "num",
  689. TypeKind.NUInt => "num",
  690. _ => null
  691. };
  692. if (name != null)
  693. {
  694. return name;
  695. }
  696. if (type.IsAnonymousType())
  697. {
  698. name = "anon";
  699. }
  700. else if (type.Name.EndsWith("Exception", StringComparison.Ordinal))
  701. {
  702. name = "ex";
  703. }
  704. else if (type.IsCSharpNativeIntegerType())
  705. {
  706. name = "num";
  707. }
  708. else if (!typeNameToVariableNameDict.TryGetValue(type.FullName, out name))
  709. {
  710. name = type.Name;
  711. // remove the 'I' for interfaces
  712. if (name.Length >= 3 && name[0] == 'I' && char.IsUpper(name[1]) && char.IsLower(name[2]))
  713. name = name.Substring(1);
  714. name = CleanUpVariableName(name) ?? "obj";
  715. }
  716. return name;
  717. }
  718. static void AddExistingName(Dictionary<string, int> reservedVariableNames, string name)
  719. {
  720. if (string.IsNullOrEmpty(name))
  721. return;
  722. string nameWithoutDigits = SplitName(name, out int number);
  723. if (reservedVariableNames.TryGetValue(nameWithoutDigits, out int existingNumber))
  724. {
  725. reservedVariableNames[nameWithoutDigits] = Math.Max(number, existingNumber);
  726. }
  727. else
  728. {
  729. reservedVariableNames.Add(nameWithoutDigits, number);
  730. }
  731. }
  732. static string SplitName(string name, out int number)
  733. {
  734. // First, identify whether the name already ends with a number:
  735. int pos = name.Length;
  736. while (pos > 0 && name[pos - 1] >= '0' && name[pos - 1] <= '9')
  737. pos--;
  738. if (pos < name.Length)
  739. {
  740. if (int.TryParse(name.Substring(pos), out number))
  741. {
  742. return name.Substring(0, pos);
  743. }
  744. }
  745. number = 1;
  746. return name;
  747. }
  748. static string CleanUpVariableName(string name)
  749. {
  750. // remove the backtick (generics)
  751. int pos = name.IndexOf('`');
  752. if (pos >= 0)
  753. name = name.Substring(0, pos);
  754. // remove field prefix:
  755. if (name.Length > 2 && name.StartsWith("m_", StringComparison.Ordinal))
  756. name = name.Substring(2);
  757. else if (name.Length > 1 && name[0] == '_' && (char.IsLetter(name[1]) || name[1] == '_'))
  758. name = name.Substring(1);
  759. if (TextWriterTokenWriter.ContainsNonPrintableIdentifierChar(name))
  760. {
  761. return null;
  762. }
  763. if (name.Length == 0)
  764. return "obj";
  765. string lowerCaseName = char.ToLower(name[0]) + name.Substring(1);
  766. if (CSharp.OutputVisitor.CSharpOutputVisitor.IsKeyword(lowerCaseName))
  767. return null;
  768. return lowerCaseName;
  769. }
  770. static IType GuessType(IType variableType, ILInstruction inst, ILTransformContext context)
  771. {
  772. if (!variableType.IsKnownType(KnownTypeCode.Object))
  773. return variableType;
  774. IType inferredType = inst.InferType(context.TypeSystem);
  775. if (inferredType.Kind != TypeKind.Unknown)
  776. return inferredType;
  777. else
  778. return variableType;
  779. }
  780. static Dictionary<string, int> CollectReservedVariableNames(ILFunction function,
  781. ILVariable existingVariable, bool mustResolveConflicts)
  782. {
  783. var reservedVariableNames = new Dictionary<string, int>();
  784. var rootFunction = function.Ancestors.OfType<ILFunction>().Single(f => f.Parent == null);
  785. foreach (var f in rootFunction.Descendants.OfType<ILFunction>())
  786. {
  787. foreach (var p in rootFunction.Parameters)
  788. {
  789. AddExistingName(reservedVariableNames, p.Name);
  790. }
  791. foreach (var v in f.Variables.Where(v => v.Kind != VariableKind.Parameter))
  792. {
  793. if (v != existingVariable)
  794. AddExistingName(reservedVariableNames, v.Name);
  795. }
  796. }
  797. if (mustResolveConflicts)
  798. {
  799. var memberNames = CollectAllLowerCaseMemberNames(function.Method.DeclaringTypeDefinition)
  800. .Concat(CollectAllLowerCaseTypeNames(function.Method.DeclaringTypeDefinition));
  801. foreach (var name in memberNames)
  802. AddExistingName(reservedVariableNames, name);
  803. }
  804. return reservedVariableNames;
  805. }
  806. internal static string GenerateForeachVariableName(ILFunction function, ILInstruction valueContext,
  807. ILVariable existingVariable = null, bool mustResolveConflicts = false)
  808. {
  809. if (function == null)
  810. throw new ArgumentNullException(nameof(function));
  811. if (existingVariable != null && !existingVariable.HasGeneratedName)
  812. {
  813. return existingVariable.Name;
  814. }
  815. var reservedVariableNames = CollectReservedVariableNames(function, existingVariable, mustResolveConflicts);
  816. string baseName = GetNameFromInstruction(valueContext);
  817. if (string.IsNullOrEmpty(baseName))
  818. {
  819. if (valueContext is LdLoc ldloc && ldloc.Variable.Kind == VariableKind.Parameter)
  820. {
  821. baseName = ldloc.Variable.Name;
  822. }
  823. }
  824. string proposedName = "item";
  825. if (!string.IsNullOrEmpty(baseName))
  826. {
  827. if (!IsPlural(baseName, ref proposedName))
  828. {
  829. if (baseName.Length > 4 && baseName.EndsWith("List", StringComparison.Ordinal))
  830. {
  831. proposedName = baseName.Substring(0, baseName.Length - 4);
  832. }
  833. else if (baseName.Equals("list", StringComparison.OrdinalIgnoreCase))
  834. {
  835. proposedName = "item";
  836. }
  837. else if (baseName.EndsWith("children", StringComparison.OrdinalIgnoreCase))
  838. {
  839. proposedName = baseName.Remove(baseName.Length - 3);
  840. }
  841. }
  842. }
  843. // remove any numbers from the proposed name
  844. proposedName = SplitName(proposedName, out int number);
  845. if (!reservedVariableNames.ContainsKey(proposedName))
  846. {
  847. reservedVariableNames.Add(proposedName, 0);
  848. }
  849. int count = ++reservedVariableNames[proposedName];
  850. Debug.Assert(!string.IsNullOrWhiteSpace(proposedName));
  851. if (count > 1)
  852. {
  853. return proposedName + count.ToString();
  854. }
  855. else
  856. {
  857. return proposedName;
  858. }
  859. }
  860. internal static string GenerateVariableName(ILFunction function, IType type,
  861. ILInstruction valueContext = null, ILVariable existingVariable = null,
  862. bool mustResolveConflicts = false)
  863. {
  864. if (function == null)
  865. throw new ArgumentNullException(nameof(function));
  866. var reservedVariableNames = CollectReservedVariableNames(function, existingVariable, mustResolveConflicts);
  867. string baseName = valueContext != null ? GetNameFromInstruction(valueContext) ?? GetNameByType(type) : GetNameByType(type);
  868. string proposedName = "obj";
  869. if (!string.IsNullOrEmpty(baseName))
  870. {
  871. if (!IsPlural(baseName, ref proposedName))
  872. {
  873. if (baseName.Length > 4 && baseName.EndsWith("List", StringComparison.Ordinal))
  874. {
  875. proposedName = baseName.Substring(0, baseName.Length - 4);
  876. }
  877. else if (baseName.Equals("list", StringComparison.OrdinalIgnoreCase))
  878. {
  879. proposedName = "item";
  880. }
  881. else if (baseName.EndsWith("children", StringComparison.OrdinalIgnoreCase))
  882. {
  883. proposedName = baseName.Remove(baseName.Length - 3);
  884. }
  885. else
  886. {
  887. proposedName = baseName;
  888. }
  889. }
  890. }
  891. // remove any numbers from the proposed name
  892. proposedName = SplitName(proposedName, out int number);
  893. if (!reservedVariableNames.ContainsKey(proposedName))
  894. {
  895. reservedVariableNames.Add(proposedName, 0);
  896. }
  897. int count = ++reservedVariableNames[proposedName];
  898. Debug.Assert(!string.IsNullOrWhiteSpace(proposedName));
  899. if (count > 1)
  900. {
  901. return proposedName + count.ToString();
  902. }
  903. else
  904. {
  905. return proposedName;
  906. }
  907. }
  908. private static bool IsPlural(string baseName, ref string proposedName)
  909. {
  910. var newName = Vocabularies.Default.Singularize(baseName, inputIsKnownToBePlural: false);
  911. if (string.IsNullOrWhiteSpace(newName) || newName == baseName)
  912. return false;
  913. proposedName = newName;
  914. return true;
  915. }
  916. }
  917. }