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.

795 lines
24 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.Linq;
  21. using System.Reflection;
  22. using System.Reflection.Metadata;
  23. using Humanizer.Inflections;
  24. using ICSharpCode.Decompiler.CSharp.OutputVisitor;
  25. using ICSharpCode.Decompiler.TypeSystem;
  26. using ICSharpCode.Decompiler.TypeSystem.Implementation;
  27. using ICSharpCode.Decompiler.Util;
  28. namespace ICSharpCode.Decompiler.IL.Transforms
  29. {
  30. public class AssignVariableNames : IILTransform
  31. {
  32. static readonly Dictionary<string, string> typeNameToVariableNameDict = new Dictionary<string, string> {
  33. { "System.Boolean", "flag" },
  34. { "System.Byte", "b" },
  35. { "System.SByte", "b" },
  36. { "System.Int16", "num" },
  37. { "System.Int32", "num" },
  38. { "System.Int64", "num" },
  39. { "System.UInt16", "num" },
  40. { "System.UInt32", "num" },
  41. { "System.UInt64", "num" },
  42. { "System.Single", "num" },
  43. { "System.Double", "num" },
  44. { "System.Decimal", "num" },
  45. { "System.String", "text" },
  46. { "System.Object", "obj" },
  47. { "System.Char", "c" }
  48. };
  49. ILTransformContext context;
  50. string[] currentFieldNames;
  51. Dictionary<string, int> reservedVariableNames;
  52. Dictionary<MethodDefinitionHandle, string> localFunctionMapping;
  53. HashSet<ILVariable> loopCounters;
  54. const char maxLoopVariableName = 'n';
  55. public void Run(ILFunction function, ILTransformContext context)
  56. {
  57. this.context = context;
  58. currentFieldNames = function.Method.DeclaringTypeDefinition.Fields.Select(f => f.Name).ToArray();
  59. reservedVariableNames = new Dictionary<string, int>();
  60. localFunctionMapping = new Dictionary<MethodDefinitionHandle, string>();
  61. loopCounters = CollectLoopCounters(function);
  62. foreach (var f in function.Descendants.OfType<ILFunction>())
  63. {
  64. if (f.Method != null)
  65. {
  66. if (IsSetOrEventAccessor(f.Method) && f.Method.Parameters.Count > 0)
  67. {
  68. for (int i = 0; i < f.Method.Parameters.Count - 1; i++)
  69. {
  70. AddExistingName(reservedVariableNames, f.Method.Parameters[i].Name);
  71. }
  72. var lastParameter = f.Method.Parameters.Last();
  73. switch (f.Method.AccessorOwner)
  74. {
  75. case IProperty prop:
  76. if (f.Method.AccessorKind == MethodSemanticsAttributes.Setter)
  77. {
  78. if (prop.Parameters.Any(p => p.Name == "value"))
  79. {
  80. f.Warnings.Add("Parameter named \"value\" already present in property signature!");
  81. break;
  82. }
  83. var variableForLastParameter = f.Variables.FirstOrDefault(v => v.Function == f
  84. && v.Kind == VariableKind.Parameter
  85. && v.Index == f.Method.Parameters.Count - 1);
  86. if (variableForLastParameter == null)
  87. {
  88. AddExistingName(reservedVariableNames, lastParameter.Name);
  89. }
  90. else
  91. {
  92. if (variableForLastParameter.Name != "value")
  93. {
  94. variableForLastParameter.Name = "value";
  95. }
  96. AddExistingName(reservedVariableNames, variableForLastParameter.Name);
  97. }
  98. }
  99. break;
  100. case IEvent ev:
  101. if (f.Method.AccessorKind != MethodSemanticsAttributes.Raiser)
  102. {
  103. var variableForLastParameter = f.Variables.FirstOrDefault(v => v.Function == f
  104. && v.Kind == VariableKind.Parameter
  105. && v.Index == f.Method.Parameters.Count - 1);
  106. if (variableForLastParameter == null)
  107. {
  108. AddExistingName(reservedVariableNames, lastParameter.Name);
  109. }
  110. else
  111. {
  112. if (variableForLastParameter.Name != "value")
  113. {
  114. variableForLastParameter.Name = "value";
  115. }
  116. AddExistingName(reservedVariableNames, variableForLastParameter.Name);
  117. }
  118. }
  119. break;
  120. default:
  121. AddExistingName(reservedVariableNames, lastParameter.Name);
  122. break;
  123. }
  124. }
  125. else
  126. {
  127. foreach (var p in f.Method.Parameters)
  128. AddExistingName(reservedVariableNames, p.Name);
  129. }
  130. }
  131. else
  132. {
  133. foreach (var p in f.Variables.Where(v => v.Kind == VariableKind.Parameter))
  134. AddExistingName(reservedVariableNames, p.Name);
  135. }
  136. }
  137. foreach (ILFunction f in function.Descendants.OfType<ILFunction>().Reverse())
  138. {
  139. PerformAssignment(f);
  140. }
  141. }
  142. bool IsSetOrEventAccessor(IMethod method)
  143. {
  144. switch (method.AccessorKind)
  145. {
  146. case MethodSemanticsAttributes.Setter:
  147. case MethodSemanticsAttributes.Adder:
  148. case MethodSemanticsAttributes.Remover:
  149. return true;
  150. default:
  151. return false;
  152. }
  153. }
  154. void PerformAssignment(ILFunction function)
  155. {
  156. // remove unused variables before assigning names
  157. function.Variables.RemoveDead();
  158. int numDisplayClassLocals = 0;
  159. Dictionary<int, string> assignedLocalSignatureIndices = new Dictionary<int, string>();
  160. foreach (var v in function.Variables.OrderBy(v => v.Name))
  161. {
  162. switch (v.Kind)
  163. {
  164. case VariableKind.Parameter: // ignore
  165. break;
  166. case VariableKind.InitializerTarget: // keep generated names
  167. AddExistingName(reservedVariableNames, v.Name);
  168. break;
  169. case VariableKind.DisplayClassLocal:
  170. v.Name = "CS$<>8__locals" + (numDisplayClassLocals++);
  171. break;
  172. case VariableKind.Local when v.Index != null:
  173. if (assignedLocalSignatureIndices.TryGetValue(v.Index.Value, out string name))
  174. {
  175. // make sure all local ILVariables that refer to the same slot in the locals signature
  176. // are assigned the same name.
  177. v.Name = name;
  178. }
  179. else
  180. {
  181. AssignName();
  182. // Remember the newly assigned name:
  183. assignedLocalSignatureIndices.Add(v.Index.Value, v.Name);
  184. }
  185. break;
  186. default:
  187. AssignName();
  188. break;
  189. }
  190. void AssignName()
  191. {
  192. if (v.HasGeneratedName || !IsValidName(v.Name) || ConflictWithLocal(v))
  193. {
  194. // don't use the name from the debug symbols if it looks like a generated name
  195. v.Name = null;
  196. }
  197. else
  198. {
  199. // use the name from the debug symbols
  200. // (but ensure we don't use the same name for two variables)
  201. v.Name = GetAlternativeName(v.Name);
  202. }
  203. }
  204. }
  205. foreach (var localFunction in function.LocalFunctions)
  206. {
  207. if (!LocalFunctionDecompiler.ParseLocalFunctionName(localFunction.Name, out _, out var newName) || !IsValidName(newName))
  208. newName = null;
  209. localFunction.Name = newName;
  210. localFunction.ReducedMethod.Name = newName;
  211. }
  212. // Now generate names:
  213. var mapping = new Dictionary<ILVariable, string>(ILVariableEqualityComparer.Instance);
  214. foreach (var inst in function.Descendants.OfType<IInstructionWithVariableOperand>())
  215. {
  216. var v = inst.Variable;
  217. if (!mapping.TryGetValue(v, out string name))
  218. {
  219. if (string.IsNullOrEmpty(v.Name))
  220. v.Name = GenerateNameForVariable(v);
  221. mapping.Add(v, v.Name);
  222. }
  223. else
  224. {
  225. v.Name = name;
  226. }
  227. }
  228. foreach (var localFunction in function.LocalFunctions)
  229. {
  230. var newName = localFunction.Name;
  231. if (newName == null)
  232. {
  233. newName = GetAlternativeName("f");
  234. }
  235. localFunction.Name = newName;
  236. localFunction.ReducedMethod.Name = newName;
  237. localFunctionMapping[(MethodDefinitionHandle)localFunction.ReducedMethod.MetadataToken] = newName;
  238. }
  239. foreach (var inst in function.Descendants)
  240. {
  241. LocalFunctionMethod localFunction;
  242. switch (inst)
  243. {
  244. case Call call:
  245. localFunction = call.Method as LocalFunctionMethod;
  246. break;
  247. case LdFtn ldftn:
  248. localFunction = ldftn.Method as LocalFunctionMethod;
  249. break;
  250. default:
  251. localFunction = null;
  252. break;
  253. }
  254. if (localFunction == null || !localFunctionMapping.TryGetValue((MethodDefinitionHandle)localFunction.MetadataToken, out var name))
  255. continue;
  256. localFunction.Name = name;
  257. }
  258. }
  259. /// <remarks>
  260. /// Must be in sync with <see cref="GetNameFromInstruction" />.
  261. /// </remarks>
  262. internal static bool IsSupportedInstruction(object arg)
  263. {
  264. switch (arg)
  265. {
  266. case LdObj ldobj:
  267. case LdFlda ldflda:
  268. case LdsFlda ldsflda:
  269. case CallInstruction call:
  270. return true;
  271. default:
  272. return false;
  273. }
  274. }
  275. bool ConflictWithLocal(ILVariable v)
  276. {
  277. if (v.Kind == VariableKind.UsingLocal || v.Kind == VariableKind.ForeachLocal)
  278. {
  279. if (reservedVariableNames.ContainsKey(v.Name))
  280. return true;
  281. }
  282. return false;
  283. }
  284. static bool IsValidName(string varName)
  285. {
  286. if (string.IsNullOrEmpty(varName))
  287. return false;
  288. if (!(char.IsLetter(varName[0]) || varName[0] == '_'))
  289. return false;
  290. for (int i = 1; i < varName.Length; i++)
  291. {
  292. if (!(char.IsLetterOrDigit(varName[i]) || varName[i] == '_'))
  293. return false;
  294. }
  295. return true;
  296. }
  297. public string GetAlternativeName(string oldVariableName)
  298. {
  299. if (oldVariableName.Length == 1 && oldVariableName[0] >= 'i' && oldVariableName[0] <= maxLoopVariableName)
  300. {
  301. for (char c = 'i'; c <= maxLoopVariableName; c++)
  302. {
  303. if (!reservedVariableNames.ContainsKey(c.ToString()))
  304. {
  305. reservedVariableNames.Add(c.ToString(), 1);
  306. return c.ToString();
  307. }
  308. }
  309. }
  310. string nameWithoutDigits = SplitName(oldVariableName, out int number);
  311. if (!reservedVariableNames.ContainsKey(nameWithoutDigits))
  312. {
  313. reservedVariableNames.Add(nameWithoutDigits, number - 1);
  314. }
  315. int count = ++reservedVariableNames[nameWithoutDigits];
  316. string nameWithDigits = nameWithoutDigits + count.ToString();
  317. if (oldVariableName == nameWithDigits)
  318. {
  319. return oldVariableName;
  320. }
  321. if (count != 1)
  322. {
  323. return nameWithDigits;
  324. }
  325. else
  326. {
  327. return nameWithoutDigits;
  328. }
  329. }
  330. HashSet<ILVariable> CollectLoopCounters(ILFunction function)
  331. {
  332. var loopCounters = new HashSet<ILVariable>();
  333. foreach (BlockContainer possibleLoop in function.Descendants.OfType<BlockContainer>())
  334. {
  335. if (possibleLoop.Kind != ContainerKind.For)
  336. continue;
  337. foreach (var inst in possibleLoop.Blocks.Last().Instructions)
  338. {
  339. if (HighLevelLoopTransform.MatchIncrement(inst, out var variable))
  340. loopCounters.Add(variable);
  341. }
  342. }
  343. return loopCounters;
  344. }
  345. string GenerateNameForVariable(ILVariable variable)
  346. {
  347. string proposedName = null;
  348. if (variable.Type.IsKnownType(KnownTypeCode.Int32))
  349. {
  350. // test whether the variable might be a loop counter
  351. if (loopCounters.Contains(variable))
  352. {
  353. // For loop variables, use i,j,k,l,m,n
  354. for (char c = 'i'; c <= maxLoopVariableName; c++)
  355. {
  356. if (!reservedVariableNames.ContainsKey(c.ToString()))
  357. {
  358. proposedName = c.ToString();
  359. break;
  360. }
  361. }
  362. }
  363. }
  364. // The ComponentResourceManager inside InitializeComponent must be named "resources",
  365. // otherwise the WinForms designer won't load the Form.
  366. if (CSharp.CSharpDecompiler.IsWindowsFormsInitializeComponentMethod(context.Function.Method) && variable.Type.FullName == "System.ComponentModel.ComponentResourceManager")
  367. {
  368. proposedName = "resources";
  369. }
  370. if (string.IsNullOrEmpty(proposedName))
  371. {
  372. var proposedNameForAddress = variable.AddressInstructions.OfType<LdLoca>()
  373. .Select(arg => arg.Parent is CallInstruction c ? c.GetParameter(arg.ChildIndex)?.Name : null)
  374. .Where(arg => !string.IsNullOrWhiteSpace(arg))
  375. .Except(currentFieldNames).ToList();
  376. if (proposedNameForAddress.Count > 0)
  377. {
  378. proposedName = proposedNameForAddress[0];
  379. }
  380. }
  381. if (string.IsNullOrEmpty(proposedName))
  382. {
  383. var proposedNameForStores = variable.StoreInstructions.OfType<StLoc>()
  384. .Select(expr => GetNameFromInstruction(expr.Value))
  385. .Except(currentFieldNames).ToList();
  386. if (proposedNameForStores.Count == 1)
  387. {
  388. proposedName = proposedNameForStores[0];
  389. }
  390. }
  391. if (string.IsNullOrEmpty(proposedName))
  392. {
  393. var proposedNameForLoads = variable.LoadInstructions
  394. .Select(arg => GetNameForArgument(arg.Parent, arg.ChildIndex))
  395. .Except(currentFieldNames).ToList();
  396. if (proposedNameForLoads.Count == 1)
  397. {
  398. proposedName = proposedNameForLoads[0];
  399. }
  400. }
  401. if (string.IsNullOrEmpty(proposedName) && variable.Kind == VariableKind.StackSlot)
  402. {
  403. var proposedNameForStoresFromNewObj = variable.StoreInstructions.OfType<StLoc>()
  404. .Select(expr => GetNameByType(GuessType(variable.Type, expr.Value, context)))
  405. .Except(currentFieldNames).ToList();
  406. if (proposedNameForStoresFromNewObj.Count == 1)
  407. {
  408. proposedName = proposedNameForStoresFromNewObj[0];
  409. }
  410. }
  411. if (string.IsNullOrEmpty(proposedName))
  412. {
  413. proposedName = GetNameByType(variable.Type);
  414. }
  415. // remove any numbers from the proposed name
  416. proposedName = SplitName(proposedName, out int number);
  417. if (!reservedVariableNames.ContainsKey(proposedName))
  418. {
  419. reservedVariableNames.Add(proposedName, 0);
  420. }
  421. int count = ++reservedVariableNames[proposedName];
  422. if (count > 1)
  423. {
  424. return proposedName + count.ToString();
  425. }
  426. else
  427. {
  428. return proposedName;
  429. }
  430. }
  431. static string GetNameFromInstruction(ILInstruction inst)
  432. {
  433. switch (inst)
  434. {
  435. case LdObj ldobj:
  436. return GetNameFromInstruction(ldobj.Target);
  437. case LdFlda ldflda:
  438. return CleanUpVariableName(ldflda.Field.Name);
  439. case LdsFlda ldsflda:
  440. return CleanUpVariableName(ldsflda.Field.Name);
  441. case CallInstruction call:
  442. if (call is NewObj)
  443. break;
  444. IMethod m = call.Method;
  445. if (ExcludeMethodFromCandidates(m))
  446. break;
  447. if (m.Name.StartsWith("get_", StringComparison.OrdinalIgnoreCase) && m.Parameters.Count == 0)
  448. {
  449. // use name from properties, but not from indexers
  450. return CleanUpVariableName(m.Name.Substring(4));
  451. }
  452. else if (m.Name.StartsWith("Get", StringComparison.OrdinalIgnoreCase) && m.Name.Length >= 4 && char.IsUpper(m.Name[3]))
  453. {
  454. // use name from Get-methods
  455. return CleanUpVariableName(m.Name.Substring(3));
  456. }
  457. break;
  458. case DynamicInvokeMemberInstruction dynInvokeMember:
  459. if (dynInvokeMember.Name.StartsWith("Get", StringComparison.OrdinalIgnoreCase)
  460. && dynInvokeMember.Name.Length >= 4 && char.IsUpper(dynInvokeMember.Name[3]))
  461. {
  462. // use name from Get-methods
  463. return CleanUpVariableName(dynInvokeMember.Name.Substring(3));
  464. }
  465. break;
  466. }
  467. return null;
  468. }
  469. static string GetNameForArgument(ILInstruction parent, int i)
  470. {
  471. switch (parent)
  472. {
  473. case StObj stobj:
  474. IField field;
  475. if (stobj.Target is LdFlda ldflda)
  476. field = ldflda.Field;
  477. else if (stobj.Target is LdsFlda ldsflda)
  478. field = ldsflda.Field;
  479. else
  480. break;
  481. return CleanUpVariableName(field.Name);
  482. case CallInstruction call:
  483. IMethod m = call.Method;
  484. if (ExcludeMethodFromCandidates(m))
  485. return null;
  486. if (m.Parameters.Count == 1 && i == call.Arguments.Count - 1)
  487. {
  488. // argument might be value of a setter
  489. if (m.Name.StartsWith("set_", StringComparison.OrdinalIgnoreCase))
  490. {
  491. return CleanUpVariableName(m.Name.Substring(4));
  492. }
  493. else if (m.Name.StartsWith("Set", StringComparison.OrdinalIgnoreCase) && m.Name.Length >= 4 && char.IsUpper(m.Name[3]))
  494. {
  495. return CleanUpVariableName(m.Name.Substring(3));
  496. }
  497. }
  498. var p = call.GetParameter(i);
  499. if (p != null && !string.IsNullOrEmpty(p.Name))
  500. return CleanUpVariableName(p.Name);
  501. break;
  502. case Leave _:
  503. return "result";
  504. }
  505. return null;
  506. }
  507. static bool ExcludeMethodFromCandidates(IMethod m)
  508. {
  509. if (m.SymbolKind == SymbolKind.Operator)
  510. return true;
  511. if (m.Name == "ToString")
  512. return true;
  513. if (m.Name == "Concat" && m.DeclaringType.IsKnownType(KnownTypeCode.String))
  514. return true;
  515. return false;
  516. }
  517. static string GetNameByType(IType type)
  518. {
  519. type = NullableType.GetUnderlyingType(type);
  520. while (type is ModifiedType || type is PinnedType)
  521. {
  522. type = NullableType.GetUnderlyingType(((TypeWithElementType)type).ElementType);
  523. }
  524. string name = type.Kind switch
  525. {
  526. TypeKind.Array => "array",
  527. TypeKind.Pointer => "ptr",
  528. TypeKind.TypeParameter => "val",
  529. TypeKind.Unknown => "val",
  530. TypeKind.Dynamic => "val",
  531. TypeKind.ByReference => "reference",
  532. TypeKind.Tuple => "tuple",
  533. TypeKind.NInt => "num",
  534. TypeKind.NUInt => "num",
  535. _ => null
  536. };
  537. if (name != null)
  538. {
  539. return name;
  540. }
  541. if (type.IsAnonymousType())
  542. {
  543. name = "anon";
  544. }
  545. else if (type.Name.EndsWith("Exception", StringComparison.Ordinal))
  546. {
  547. name = "ex";
  548. }
  549. else if (type.IsCSharpNativeIntegerType())
  550. {
  551. name = "num";
  552. }
  553. else if (!typeNameToVariableNameDict.TryGetValue(type.FullName, out name))
  554. {
  555. name = type.Name;
  556. // remove the 'I' for interfaces
  557. if (name.Length >= 3 && name[0] == 'I' && char.IsUpper(name[1]) && char.IsLower(name[2]))
  558. name = name.Substring(1);
  559. name = CleanUpVariableName(name) ?? "obj";
  560. }
  561. return name;
  562. }
  563. static void AddExistingName(Dictionary<string, int> reservedVariableNames, string name)
  564. {
  565. if (string.IsNullOrEmpty(name))
  566. return;
  567. string nameWithoutDigits = SplitName(name, out int number);
  568. if (reservedVariableNames.TryGetValue(nameWithoutDigits, out int existingNumber))
  569. {
  570. reservedVariableNames[nameWithoutDigits] = Math.Max(number, existingNumber);
  571. }
  572. else
  573. {
  574. reservedVariableNames.Add(nameWithoutDigits, number);
  575. }
  576. }
  577. static string SplitName(string name, out int number)
  578. {
  579. // First, identify whether the name already ends with a number:
  580. int pos = name.Length;
  581. while (pos > 0 && name[pos - 1] >= '0' && name[pos - 1] <= '9')
  582. pos--;
  583. if (pos < name.Length)
  584. {
  585. if (int.TryParse(name.Substring(pos), out number))
  586. {
  587. return name.Substring(0, pos);
  588. }
  589. }
  590. number = 1;
  591. return name;
  592. }
  593. static string CleanUpVariableName(string name)
  594. {
  595. // remove the backtick (generics)
  596. int pos = name.IndexOf('`');
  597. if (pos >= 0)
  598. name = name.Substring(0, pos);
  599. // remove field prefix:
  600. if (name.Length > 2 && name.StartsWith("m_", StringComparison.Ordinal))
  601. name = name.Substring(2);
  602. else if (name.Length > 1 && name[0] == '_' && (char.IsLetter(name[1]) || name[1] == '_'))
  603. name = name.Substring(1);
  604. if (TextWriterTokenWriter.ContainsNonPrintableIdentifierChar(name))
  605. {
  606. return null;
  607. }
  608. if (name.Length == 0)
  609. return "obj";
  610. else
  611. return char.ToLower(name[0]) + name.Substring(1);
  612. }
  613. internal static IType GuessType(IType variableType, ILInstruction inst, ILTransformContext context)
  614. {
  615. if (!variableType.IsKnownType(KnownTypeCode.Object))
  616. return variableType;
  617. IType inferredType = inst.InferType(context.TypeSystem);
  618. if (inferredType.Kind != TypeKind.Unknown)
  619. return inferredType;
  620. else
  621. return variableType;
  622. }
  623. static Dictionary<string, int> CollectReservedVariableNames(ILFunction function, ILVariable existingVariable)
  624. {
  625. var reservedVariableNames = new Dictionary<string, int>();
  626. var rootFunction = function.Ancestors.OfType<ILFunction>().Single(f => f.Parent == null);
  627. foreach (var f in rootFunction.Descendants.OfType<ILFunction>())
  628. {
  629. foreach (var p in rootFunction.Parameters)
  630. {
  631. AddExistingName(reservedVariableNames, p.Name);
  632. }
  633. foreach (var v in f.Variables.Where(v => v.Kind != VariableKind.Parameter))
  634. {
  635. if (v != existingVariable)
  636. AddExistingName(reservedVariableNames, v.Name);
  637. }
  638. }
  639. foreach (var f in rootFunction.Method.DeclaringTypeDefinition.GetFields().Select(f => f.Name))
  640. AddExistingName(reservedVariableNames, f);
  641. return reservedVariableNames;
  642. }
  643. internal static string GenerateForeachVariableName(ILFunction function, ILInstruction valueContext, ILVariable existingVariable = null)
  644. {
  645. if (function == null)
  646. throw new ArgumentNullException(nameof(function));
  647. if (existingVariable != null && !existingVariable.HasGeneratedName)
  648. {
  649. return existingVariable.Name;
  650. }
  651. var reservedVariableNames = CollectReservedVariableNames(function, existingVariable);
  652. string baseName = GetNameFromInstruction(valueContext);
  653. if (string.IsNullOrEmpty(baseName))
  654. {
  655. if (valueContext is LdLoc ldloc && ldloc.Variable.Kind == VariableKind.Parameter)
  656. {
  657. baseName = ldloc.Variable.Name;
  658. }
  659. }
  660. string proposedName = "item";
  661. if (!string.IsNullOrEmpty(baseName))
  662. {
  663. if (!IsPlural(baseName, ref proposedName))
  664. {
  665. if (baseName.Length > 4 && baseName.EndsWith("List", StringComparison.Ordinal))
  666. {
  667. proposedName = baseName.Substring(0, baseName.Length - 4);
  668. }
  669. else if (baseName.Equals("list", StringComparison.OrdinalIgnoreCase))
  670. {
  671. proposedName = "item";
  672. }
  673. else if (baseName.EndsWith("children", StringComparison.OrdinalIgnoreCase))
  674. {
  675. proposedName = baseName.Remove(baseName.Length - 3);
  676. }
  677. }
  678. }
  679. // remove any numbers from the proposed name
  680. proposedName = SplitName(proposedName, out int number);
  681. if (!reservedVariableNames.ContainsKey(proposedName))
  682. {
  683. reservedVariableNames.Add(proposedName, 0);
  684. }
  685. int count = ++reservedVariableNames[proposedName];
  686. if (count > 1)
  687. {
  688. return proposedName + count.ToString();
  689. }
  690. else
  691. {
  692. return proposedName;
  693. }
  694. }
  695. internal static string GenerateVariableName(ILFunction function, IType type, ILInstruction valueContext = null, ILVariable existingVariable = null)
  696. {
  697. if (function == null)
  698. throw new ArgumentNullException(nameof(function));
  699. var reservedVariableNames = CollectReservedVariableNames(function, existingVariable);
  700. string baseName = valueContext != null ? GetNameFromInstruction(valueContext) ?? GetNameByType(type) : GetNameByType(type);
  701. string proposedName = "obj";
  702. if (!string.IsNullOrEmpty(baseName))
  703. {
  704. if (!IsPlural(baseName, ref proposedName))
  705. {
  706. if (baseName.Length > 4 && baseName.EndsWith("List", StringComparison.Ordinal))
  707. {
  708. proposedName = baseName.Substring(0, baseName.Length - 4);
  709. }
  710. else if (baseName.Equals("list", StringComparison.OrdinalIgnoreCase))
  711. {
  712. proposedName = "item";
  713. }
  714. else if (baseName.EndsWith("children", StringComparison.OrdinalIgnoreCase))
  715. {
  716. proposedName = baseName.Remove(baseName.Length - 3);
  717. }
  718. else
  719. {
  720. proposedName = baseName;
  721. }
  722. }
  723. }
  724. // remove any numbers from the proposed name
  725. proposedName = SplitName(proposedName, out int number);
  726. if (!reservedVariableNames.ContainsKey(proposedName))
  727. {
  728. reservedVariableNames.Add(proposedName, 0);
  729. }
  730. int count = ++reservedVariableNames[proposedName];
  731. if (count > 1)
  732. {
  733. return proposedName + count.ToString();
  734. }
  735. else
  736. {
  737. return proposedName;
  738. }
  739. }
  740. private static bool IsPlural(string baseName, ref string proposedName)
  741. {
  742. var newName = Vocabularies.Default.Singularize(baseName, inputIsKnownToBePlural: false);
  743. if (newName == baseName)
  744. return false;
  745. proposedName = newName;
  746. return true;
  747. }
  748. }
  749. }