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.

890 lines
33 KiB

5 years ago
  1. // Copyright (c) 2019 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 System.Text.RegularExpressions;
  26. using ICSharpCode.Decompiler.CSharp;
  27. using ICSharpCode.Decompiler.Metadata;
  28. using ICSharpCode.Decompiler.TypeSystem;
  29. using ICSharpCode.Decompiler.TypeSystem.Implementation;
  30. using ICSharpCode.Decompiler.Util;
  31. namespace ICSharpCode.Decompiler.IL.Transforms
  32. {
  33. /// <summary>
  34. /// Decompiler step for C# 7.0 local functions
  35. /// </summary>
  36. public class LocalFunctionDecompiler : IILTransform
  37. {
  38. ILTransformContext context;
  39. ITypeResolveContext resolveContext;
  40. struct LocalFunctionInfo
  41. {
  42. public List<ILInstruction> UseSites;
  43. public IMethod Method;
  44. public ILFunction Definition;
  45. /// <summary>
  46. /// Used to store all synthesized call-site arguments grouped by the parameter index.
  47. /// We use a dictionary instead of a simple array, because -1 is used for the this parameter
  48. /// and there might be many non-synthesized arguments in between.
  49. /// </summary>
  50. public Dictionary<int, List<ILInstruction>> LocalFunctionArguments;
  51. }
  52. /// <summary>
  53. /// The transform works like this:
  54. ///
  55. /// <para>
  56. /// local functions can either be used in method calls, i.e., call and callvirt instructions,
  57. /// or can be used as part of the "delegate construction" pattern, i.e.,
  58. /// <c>newobj Delegate(&lt;target-expression&gt;, ldftn &lt;method&gt;)</c>.
  59. /// </para>
  60. /// As local functions can be declared practically anywhere, we have to take a look at
  61. /// all use-sites and infer the declaration location from that. Use-sites can be call,
  62. /// callvirt and ldftn instructions.
  63. /// After all use-sites are collected we construct the ILAst of the local function
  64. /// and add it to the parent function.
  65. /// Then all use-sites of the local-function are transformed to a call to the
  66. /// <c>LocalFunctionMethod</c> or a ldftn of the <c>LocalFunctionMethod</c>.
  67. /// In a next step we handle all nested local functions.
  68. /// After all local functions are transformed, we move all local functions that capture
  69. /// any variables to their respective declaration scope.
  70. /// </summary>
  71. public void Run(ILFunction function, ILTransformContext context)
  72. {
  73. if (!context.Settings.LocalFunctions)
  74. return;
  75. // Disable the transform if we are decompiling a display-class or local function method:
  76. // This happens if a local function or display class is selected in the ILSpy tree view.
  77. if (IsLocalFunctionMethod(function.Method, context) || IsLocalFunctionDisplayClass(
  78. function.Method.ParentModule.MetadataFile,
  79. (TypeDefinitionHandle)function.Method.DeclaringTypeDefinition.MetadataToken,
  80. context)
  81. )
  82. {
  83. return;
  84. }
  85. this.context = context;
  86. this.resolveContext = new SimpleTypeResolveContext(function.Method);
  87. var localFunctions = new Dictionary<MethodDefinitionHandle, LocalFunctionInfo>();
  88. // Find all local functions declared inside this method, including nested local functions or local functions declared in lambdas.
  89. FindUseSites(function, context, localFunctions);
  90. ReplaceReferencesToDisplayClassThis(localFunctions.Values);
  91. DetermineCaptureAndDeclarationScopes(localFunctions.Values);
  92. PropagateClosureParameterArguments(localFunctions);
  93. TransformUseSites(localFunctions.Values);
  94. }
  95. private void ReplaceReferencesToDisplayClassThis(Dictionary<MethodDefinitionHandle, LocalFunctionInfo>.ValueCollection localFunctions)
  96. {
  97. foreach (var info in localFunctions)
  98. {
  99. var localFunction = info.Definition;
  100. if (localFunction.Method.IsStatic)
  101. continue;
  102. var thisVar = localFunction.Variables.SingleOrDefault(VariableKindExtensions.IsThis);
  103. if (thisVar == null)
  104. continue;
  105. var compatibleArgument = FindCompatibleArgument(
  106. info,
  107. info.UseSites.OfType<CallInstruction>().Select(u => u.Arguments[0]).ToArray(),
  108. ignoreStructure: true
  109. );
  110. if (compatibleArgument == null)
  111. continue;
  112. context.Step($"Replace 'this' with {compatibleArgument}", localFunction);
  113. localFunction.AcceptVisitor(new DelegateConstruction.ReplaceDelegateTargetVisitor(compatibleArgument, thisVar));
  114. DetermineCaptureAndDeclarationScope(info, -1, compatibleArgument);
  115. }
  116. }
  117. private void DetermineCaptureAndDeclarationScopes(Dictionary<MethodDefinitionHandle, LocalFunctionInfo>.ValueCollection localFunctions)
  118. {
  119. foreach (var info in localFunctions)
  120. {
  121. context.CancellationToken.ThrowIfCancellationRequested();
  122. if (info.Definition == null)
  123. {
  124. context.Function.Warnings.Add($"Could not decode local function '{info.Method}'");
  125. continue;
  126. }
  127. context.StepStartGroup($"Determine and move to declaration scope of " + info.Definition.Name, info.Definition);
  128. try
  129. {
  130. var localFunction = info.Definition;
  131. foreach (var useSite in info.UseSites)
  132. {
  133. DetermineCaptureAndDeclarationScope(info, useSite);
  134. if (context.Function.Method.IsConstructor)
  135. {
  136. if (localFunction.DeclarationScope == null)
  137. {
  138. localFunction.DeclarationScope = BlockContainer.FindClosestContainer(useSite);
  139. }
  140. else
  141. {
  142. localFunction.DeclarationScope = FindCommonAncestorInstruction<BlockContainer>(useSite, localFunction.DeclarationScope);
  143. if (localFunction.DeclarationScope == null)
  144. {
  145. localFunction.DeclarationScope = (BlockContainer)context.Function.Body;
  146. }
  147. }
  148. }
  149. }
  150. if (localFunction.DeclarationScope == null)
  151. {
  152. localFunction.DeclarationScope = (BlockContainer)context.Function.Body;
  153. }
  154. ILFunction declaringFunction = GetDeclaringFunction(localFunction);
  155. if (declaringFunction != context.Function)
  156. {
  157. context.Step($"Move {localFunction.Name} from {context.Function.Name} to {declaringFunction.Name}", localFunction);
  158. context.Function.LocalFunctions.Remove(localFunction);
  159. declaringFunction.LocalFunctions.Add(localFunction);
  160. }
  161. if (TryValidateSkipCount(info, out int skipCount) && skipCount != localFunction.ReducedMethod.NumberOfCompilerGeneratedTypeParameters)
  162. {
  163. Debug.Assert(false);
  164. context.Function.Warnings.Add($"Could not decode local function '{info.Method}'");
  165. if (declaringFunction != context.Function)
  166. {
  167. declaringFunction.LocalFunctions.Remove(localFunction);
  168. }
  169. }
  170. }
  171. finally
  172. {
  173. context.StepEndGroup(keepIfEmpty: true);
  174. }
  175. }
  176. }
  177. private void TransformUseSites(Dictionary<MethodDefinitionHandle, LocalFunctionInfo>.ValueCollection localFunctions)
  178. {
  179. foreach (var info in localFunctions)
  180. {
  181. context.CancellationToken.ThrowIfCancellationRequested();
  182. if (info.Definition == null)
  183. continue;
  184. context.StepStartGroup($"TransformUseSites of " + info.Definition.Name, info.Definition);
  185. try
  186. {
  187. foreach (var useSite in info.UseSites)
  188. {
  189. context.Step($"Transform use-site at IL_{useSite.StartILOffset:x4}", useSite);
  190. switch (useSite)
  191. {
  192. case NewObj newObj:
  193. TransformToLocalFunctionReference(info.Definition, newObj);
  194. break;
  195. case CallInstruction call:
  196. TransformToLocalFunctionInvocation(info.Definition.ReducedMethod, call);
  197. break;
  198. case LdFtn fnptr:
  199. var specializeMethod = info.Definition.ReducedMethod
  200. .Specialize(fnptr.Method.Substitution);
  201. var replacement = new LdFtn(specializeMethod).WithILRange(fnptr);
  202. fnptr.ReplaceWith(replacement);
  203. break;
  204. default:
  205. throw new NotSupportedException();
  206. }
  207. }
  208. }
  209. finally
  210. {
  211. context.StepEndGroup();
  212. }
  213. }
  214. }
  215. private void PropagateClosureParameterArguments(Dictionary<MethodDefinitionHandle, LocalFunctionInfo> localFunctions)
  216. {
  217. foreach (var localFunction in context.Function.Descendants.OfType<ILFunction>())
  218. {
  219. if (localFunction.Kind != ILFunctionKind.LocalFunction)
  220. continue;
  221. context.CancellationToken.ThrowIfCancellationRequested();
  222. var token = (MethodDefinitionHandle)localFunction.Method.MetadataToken;
  223. var info = localFunctions[token];
  224. foreach (var useSite in info.UseSites)
  225. {
  226. switch (useSite)
  227. {
  228. case NewObj newObj:
  229. AddAsArgument(-1, newObj.Arguments[0]);
  230. break;
  231. case CallInstruction call:
  232. int firstArgumentIndex;
  233. if (info.Method.IsStatic)
  234. {
  235. firstArgumentIndex = 0;
  236. }
  237. else
  238. {
  239. firstArgumentIndex = 1;
  240. AddAsArgument(-1, call.Arguments[0]);
  241. }
  242. for (int i = call.Arguments.Count - 1; i >= firstArgumentIndex; i--)
  243. {
  244. AddAsArgument(i - firstArgumentIndex, call.Arguments[i]);
  245. }
  246. break;
  247. case LdFtn _:
  248. // &LocalFunction is only possible, if the local function is declared static,
  249. // this means that the local function can be declared in the top-level scope.
  250. // Thus, there are no closure parameters that need propagation.
  251. break;
  252. default:
  253. throw new NotSupportedException();
  254. }
  255. }
  256. context.StepStartGroup($"PropagateClosureParameterArguments of " + info.Definition.Name, info.Definition);
  257. try
  258. {
  259. foreach (var (index, arguments) in info.LocalFunctionArguments)
  260. {
  261. var targetVariable = info.Definition.Variables.SingleOrDefault(p => p.Kind == VariableKind.Parameter && p.Index == index);
  262. if (targetVariable == null)
  263. continue;
  264. var compatibleArgument = FindCompatibleArgument(info, arguments);
  265. if (compatibleArgument == null)
  266. continue;
  267. context.Step($"Replace '{targetVariable}' with '{compatibleArgument}'", info.Definition);
  268. info.Definition.AcceptVisitor(new DelegateConstruction.ReplaceDelegateTargetVisitor(compatibleArgument, targetVariable));
  269. }
  270. }
  271. finally
  272. {
  273. context.StepEndGroup(keepIfEmpty: true);
  274. }
  275. void AddAsArgument(int index, ILInstruction argument)
  276. {
  277. switch (argument)
  278. {
  279. case LdLoc _:
  280. case LdLoca _:
  281. case LdFlda _:
  282. case LdObj _:
  283. if (index >= 0 && !IsClosureParameter(info.Method.Parameters[index], resolveContext))
  284. return;
  285. break;
  286. default:
  287. if (index >= 0 && IsClosureParameter(info.Method.Parameters[index], resolveContext))
  288. info.Definition.Warnings.Add("Could not transform parameter " + index + ": unsupported argument pattern");
  289. return;
  290. }
  291. if (!info.LocalFunctionArguments.TryGetValue(index, out var arguments))
  292. {
  293. arguments = new List<ILInstruction>();
  294. info.LocalFunctionArguments.Add(index, arguments);
  295. }
  296. arguments.Add(argument);
  297. }
  298. }
  299. }
  300. private ILInstruction FindCompatibleArgument(LocalFunctionInfo info, IList<ILInstruction> arguments, bool ignoreStructure = false)
  301. {
  302. foreach (var arg in arguments)
  303. {
  304. if (arg is IInstructionWithVariableOperand ld2 && (ignoreStructure || info.Definition.IsDescendantOf(ld2.Variable.Function)))
  305. return arg;
  306. var v = ResolveAncestorScopeReference(arg);
  307. if (v != null)
  308. return new LdLoc(v);
  309. }
  310. return null;
  311. }
  312. private ILVariable ResolveAncestorScopeReference(ILInstruction inst)
  313. {
  314. if (!inst.MatchLdFld(out var target, out var field))
  315. return null;
  316. if (field.Type.Kind != TypeKind.Class)
  317. return null;
  318. if (!(TransformDisplayClassUsage.IsPotentialClosure(context, field.Type.GetDefinition()) || context.Function.Method.DeclaringType.Equals(field.Type)))
  319. return null;
  320. foreach (var v in context.Function.Descendants.OfType<ILFunction>().SelectMany(f => f.Variables))
  321. {
  322. if (!(TransformDisplayClassUsage.IsClosure(context, v, out var varType, out _) && varType.Equals(field.Type)))
  323. continue;
  324. return v;
  325. }
  326. return null;
  327. }
  328. private ILFunction GetDeclaringFunction(ILFunction localFunction)
  329. {
  330. if (localFunction.DeclarationScope == null)
  331. return null;
  332. ILInstruction inst = localFunction.DeclarationScope;
  333. while (inst != null)
  334. {
  335. if (inst is ILFunction declaringFunction)
  336. return declaringFunction;
  337. inst = inst.Parent;
  338. }
  339. return null;
  340. }
  341. bool TryValidateSkipCount(LocalFunctionInfo info, out int skipCount)
  342. {
  343. skipCount = 0;
  344. var localFunction = info.Definition;
  345. if (localFunction.Method.TypeParameters.Count == 0)
  346. return true;
  347. var parentMethod = ((ILFunction)localFunction.Parent).Method;
  348. var method = localFunction.Method;
  349. skipCount = parentMethod.DeclaringType.TypeParameterCount - method.DeclaringType.TypeParameterCount;
  350. if (skipCount > 0)
  351. return false;
  352. skipCount += parentMethod.TypeParameters.Count;
  353. if (skipCount < 0 || skipCount > method.TypeArguments.Count)
  354. return false;
  355. if (skipCount > 0)
  356. {
  357. #if DEBUG
  358. foreach (var useSite in info.UseSites)
  359. {
  360. var callerMethod = useSite.Ancestors.OfType<ILFunction>().First().Method;
  361. callerMethod = callerMethod.ReducedFrom ?? callerMethod;
  362. IMethod m;
  363. switch (useSite)
  364. {
  365. case NewObj newObj:
  366. m = ((LdFtn)newObj.Arguments[1]).Method;
  367. break;
  368. case CallInstruction call:
  369. m = call.Method;
  370. break;
  371. case LdFtn fnptr:
  372. m = fnptr.Method;
  373. break;
  374. default:
  375. throw new NotSupportedException();
  376. }
  377. var totalSkipCount = skipCount + m.DeclaringType.TypeParameterCount;
  378. var methodSkippedArgs = m.DeclaringType.TypeArguments.Concat(m.TypeArguments).Take(totalSkipCount);
  379. Debug.Assert(methodSkippedArgs.SequenceEqual(callerMethod.DeclaringType.TypeArguments.Concat(callerMethod.TypeArguments).Take(totalSkipCount)));
  380. Debug.Assert(methodSkippedArgs.All(p => p.Kind == TypeKind.TypeParameter));
  381. Debug.Assert(methodSkippedArgs.Select(p => p.Name).SequenceEqual(m.DeclaringType.TypeParameters.Concat(m.TypeParameters).Take(totalSkipCount).Select(p => p.Name)));
  382. }
  383. #endif
  384. }
  385. return true;
  386. }
  387. void FindUseSites(ILFunction function, ILTransformContext context, Dictionary<MethodDefinitionHandle, LocalFunctionInfo> localFunctions)
  388. {
  389. foreach (var inst in function.Body.Descendants)
  390. {
  391. context.CancellationToken.ThrowIfCancellationRequested();
  392. if (inst is CallInstruction call && !call.Method.IsLocalFunction && IsLocalFunctionMethod(call.Method, context))
  393. {
  394. HandleUseSite(call.Method, call);
  395. }
  396. else if (inst is LdFtn ldftn && !ldftn.Method.IsLocalFunction && IsLocalFunctionMethod(ldftn.Method, context))
  397. {
  398. if (ldftn.Parent is NewObj newObj && DelegateConstruction.MatchDelegateConstruction(newObj, out _, out _, out _))
  399. HandleUseSite(ldftn.Method, newObj);
  400. else
  401. HandleUseSite(ldftn.Method, ldftn);
  402. }
  403. }
  404. void HandleUseSite(IMethod targetMethod, ILInstruction inst)
  405. {
  406. if (!localFunctions.TryGetValue((MethodDefinitionHandle)targetMethod.MetadataToken, out var info))
  407. {
  408. context.StepStartGroup($"Read local function '{targetMethod.Name}'", inst);
  409. info = new LocalFunctionInfo() {
  410. UseSites = new List<ILInstruction>() { inst },
  411. LocalFunctionArguments = new Dictionary<int, List<ILInstruction>>(),
  412. Method = (IMethod)targetMethod.MemberDefinition,
  413. };
  414. var rootFunction = context.Function;
  415. int skipCount = GetSkipCount(rootFunction, targetMethod);
  416. info.Definition = ReadLocalFunctionDefinition(rootFunction, targetMethod, skipCount);
  417. localFunctions.Add((MethodDefinitionHandle)targetMethod.MetadataToken, info);
  418. if (info.Definition != null)
  419. {
  420. FindUseSites(info.Definition, context, localFunctions);
  421. }
  422. context.StepEndGroup();
  423. }
  424. else
  425. {
  426. info.UseSites.Add(inst);
  427. }
  428. }
  429. }
  430. ILFunction ReadLocalFunctionDefinition(ILFunction rootFunction, IMethod targetMethod, int skipCount)
  431. {
  432. var methodDefinition = context.PEFile.Metadata.GetMethodDefinition((MethodDefinitionHandle)targetMethod.MetadataToken);
  433. var genericContext = GenericContextFromTypeArguments(targetMethod, skipCount);
  434. if (genericContext == null)
  435. return null;
  436. ILFunction function;
  437. bool hasBody = methodDefinition.HasBody();
  438. if (!hasBody)
  439. {
  440. function = new ILFunction(targetMethod, 0,
  441. new TypeSystem.GenericContext(genericContext?.ClassTypeParameters, genericContext?.MethodTypeParameters),
  442. new Nop(), ILFunctionKind.LocalFunction);
  443. }
  444. else
  445. {
  446. var ilReader = context.CreateILReader();
  447. var body = context.PEFile.GetMethodBody(methodDefinition.RelativeVirtualAddress);
  448. function = ilReader.ReadIL((MethodDefinitionHandle)targetMethod.MetadataToken, body,
  449. genericContext.GetValueOrDefault(), ILFunctionKind.LocalFunction,
  450. context.CancellationToken);
  451. }
  452. // Embed the local function into the parent function's ILAst, so that "Show steps" can show
  453. // how the local function body is being transformed.
  454. rootFunction.LocalFunctions.Add(function);
  455. if (hasBody)
  456. {
  457. function.DeclarationScope = (BlockContainer)rootFunction.Body;
  458. function.CheckInvariant(ILPhase.Normal);
  459. var nestedContext = new ILTransformContext(context, function);
  460. function.RunTransforms(CSharpDecompiler.GetILTransforms().TakeWhile(t => !(t is LocalFunctionDecompiler)), nestedContext);
  461. function.DeclarationScope = null;
  462. }
  463. function.ReducedMethod = ReduceToLocalFunction(function.Method, skipCount);
  464. return function;
  465. }
  466. int GetSkipCount(ILFunction rootFunction, IMethod targetMethod)
  467. {
  468. targetMethod = (IMethod)targetMethod.MemberDefinition;
  469. var skipCount = rootFunction.Method.DeclaringType.TypeParameters.Count + rootFunction.Method.TypeParameters.Count - targetMethod.DeclaringType.TypeParameters.Count;
  470. if (skipCount < 0)
  471. {
  472. skipCount = 0;
  473. }
  474. if (targetMethod.TypeParameters.Count > 0)
  475. {
  476. var lastParams = targetMethod.Parameters.Where(p => IsClosureParameter(p, this.resolveContext)).SelectMany(p => p.Type.UnwrapByRef().TypeArguments)
  477. .Select(pt => (int?)targetMethod.TypeParameters.IndexOf(pt)).DefaultIfEmpty().Max();
  478. if (lastParams != null && lastParams.GetValueOrDefault() + 1 > skipCount)
  479. skipCount = lastParams.GetValueOrDefault() + 1;
  480. }
  481. return skipCount;
  482. }
  483. static TypeSystem.GenericContext? GenericContextFromTypeArguments(IMethod targetMethod, int skipCount)
  484. {
  485. if (skipCount < 0 || skipCount > targetMethod.TypeParameters.Count)
  486. {
  487. Debug.Assert(false);
  488. return null;
  489. }
  490. int total = targetMethod.DeclaringType.TypeParameters.Count + skipCount;
  491. if (total == 0)
  492. return default(TypeSystem.GenericContext);
  493. var classTypeParameters = new List<ITypeParameter>(targetMethod.DeclaringType.TypeParameters);
  494. var methodTypeParameters = new List<ITypeParameter>(targetMethod.TypeParameters);
  495. var skippedTypeArguments = targetMethod.DeclaringType.TypeArguments.Concat(targetMethod.TypeArguments).Take(total);
  496. int idx = 0;
  497. foreach (var skippedTA in skippedTypeArguments)
  498. {
  499. int curIdx;
  500. List<ITypeParameter> curParameters;
  501. IReadOnlyList<IType> curArgs;
  502. if (idx < classTypeParameters.Count)
  503. {
  504. curIdx = idx;
  505. curParameters = classTypeParameters;
  506. curArgs = targetMethod.DeclaringType.TypeArguments;
  507. }
  508. else
  509. {
  510. curIdx = idx - classTypeParameters.Count;
  511. curParameters = methodTypeParameters;
  512. curArgs = targetMethod.TypeArguments;
  513. }
  514. if (curArgs[curIdx].Kind != TypeKind.TypeParameter)
  515. break;
  516. curParameters[curIdx] = (ITypeParameter)skippedTA;
  517. idx++;
  518. }
  519. if (idx != total)
  520. {
  521. Debug.Assert(false);
  522. return null;
  523. }
  524. return new TypeSystem.GenericContext(classTypeParameters, methodTypeParameters);
  525. }
  526. static T FindCommonAncestorInstruction<T>(ILInstruction a, ILInstruction b)
  527. where T : ILInstruction
  528. {
  529. var ancestorsOfB = new HashSet<T>(b.Ancestors.OfType<T>());
  530. return a.Ancestors.OfType<T>().FirstOrDefault(ancestorsOfB.Contains);
  531. }
  532. internal static bool IsClosureParameter(IParameter parameter, ITypeResolveContext context)
  533. {
  534. if (parameter.Type is not ByReferenceType brt)
  535. return false;
  536. var type = brt.ElementType.GetDefinition();
  537. return type != null
  538. && type.Kind == TypeKind.Struct
  539. && TransformDisplayClassUsage.IsPotentialClosure(context.CurrentTypeDefinition, type);
  540. }
  541. internal static ILInstruction GetStatement(ILInstruction inst)
  542. {
  543. while (inst.Parent != null)
  544. {
  545. if (inst.Parent is Block b && b.Kind == BlockKind.ControlFlow)
  546. return inst;
  547. inst = inst.Parent;
  548. }
  549. return inst;
  550. }
  551. LocalFunctionMethod ReduceToLocalFunction(IMethod method, int typeParametersToRemove)
  552. {
  553. int parametersToRemove = 0;
  554. for (int i = method.Parameters.Count - 1; i >= 0; i--)
  555. {
  556. if (!IsClosureParameter(method.Parameters[i], resolveContext))
  557. break;
  558. parametersToRemove++;
  559. }
  560. return new LocalFunctionMethod(method, method.Name, CanBeStaticLocalFunction(), parametersToRemove, typeParametersToRemove);
  561. bool CanBeStaticLocalFunction()
  562. {
  563. if (!context.Settings.StaticLocalFunctions)
  564. return false;
  565. // Cannot be static because there are closure parameters that will be removed
  566. if (parametersToRemove > 0)
  567. return false;
  568. // no closure parameters, but static:
  569. // we can safely assume, this local function can be declared static
  570. if (method.IsStatic)
  571. return true;
  572. // the local function is used in conjunction with a lambda, which means,
  573. // it is defined inside the display-class type
  574. var declaringType = method.DeclaringTypeDefinition;
  575. if (!declaringType.IsCompilerGenerated())
  576. return false;
  577. // if there are no instance fields, we can make it a static local function
  578. return !declaringType.GetFields(f => !f.IsStatic).Any();
  579. }
  580. }
  581. static void TransformToLocalFunctionReference(ILFunction function, CallInstruction useSite)
  582. {
  583. ILInstruction target = useSite.Arguments[0];
  584. target.ReplaceWith(new LdNull().WithILRange(target));
  585. if (target is IInstructionWithVariableOperand withVar && withVar.Variable.Kind == VariableKind.Local)
  586. {
  587. withVar.Variable.Kind = VariableKind.DisplayClassLocal;
  588. }
  589. var fnptr = (IInstructionWithMethodOperand)useSite.Arguments[1];
  590. var specializeMethod = function.ReducedMethod.Specialize(fnptr.Method.Substitution);
  591. var replacement = new LdFtn(specializeMethod).WithILRange((ILInstruction)fnptr);
  592. useSite.Arguments[1].ReplaceWith(replacement);
  593. }
  594. void TransformToLocalFunctionInvocation(LocalFunctionMethod reducedMethod, CallInstruction useSite)
  595. {
  596. var specializeMethod = reducedMethod.Specialize(useSite.Method.Substitution);
  597. bool wasInstanceCall = !useSite.Method.IsStatic;
  598. var replacement = new Call(specializeMethod);
  599. int firstArgumentIndex = wasInstanceCall ? 1 : 0;
  600. int argumentCount = useSite.Arguments.Count;
  601. int reducedArgumentCount = argumentCount - (reducedMethod.NumberOfCompilerGeneratedParameters + firstArgumentIndex);
  602. replacement.Arguments.AddRange(useSite.Arguments.Skip(firstArgumentIndex).Take(reducedArgumentCount));
  603. // copy flags
  604. replacement.ConstrainedTo = useSite.ConstrainedTo;
  605. replacement.ILStackWasEmpty = useSite.ILStackWasEmpty;
  606. replacement.IsTail = useSite.IsTail;
  607. // copy IL ranges
  608. replacement.AddILRange(useSite);
  609. if (wasInstanceCall)
  610. {
  611. replacement.AddILRange(useSite.Arguments[0]);
  612. if (useSite.Arguments[0].MatchLdLocRef(out var variable) && variable.Kind == VariableKind.NamedArgument)
  613. {
  614. // remove the store instruction of the simple load, if it is a named argument.
  615. var storeInst = (ILInstruction)variable.StoreInstructions[0];
  616. ((Block)storeInst.Parent).Instructions.RemoveAt(storeInst.ChildIndex);
  617. }
  618. }
  619. for (int i = 0; i < reducedMethod.NumberOfCompilerGeneratedParameters; i++)
  620. {
  621. replacement.AddILRange(useSite.Arguments[argumentCount - i - 1]);
  622. }
  623. useSite.ReplaceWith(replacement);
  624. }
  625. void DetermineCaptureAndDeclarationScope(LocalFunctionInfo info, ILInstruction useSite)
  626. {
  627. switch (useSite)
  628. {
  629. case CallInstruction call:
  630. if (DelegateConstruction.MatchDelegateConstruction(useSite, out _, out _, out _))
  631. {
  632. // if this is a delegate construction, skip the use-site, because the capture scope
  633. // was already determined when analyzing "this".
  634. break;
  635. }
  636. int firstArgumentIndex = info.Definition.Method.IsStatic ? 0 : 1;
  637. for (int i = call.Arguments.Count - 1; i >= firstArgumentIndex; i--)
  638. {
  639. if (!DetermineCaptureAndDeclarationScope(info, i - firstArgumentIndex, call.Arguments[i]))
  640. break;
  641. }
  642. if (firstArgumentIndex > 0)
  643. {
  644. DetermineCaptureAndDeclarationScope(info, -1, call.Arguments[0]);
  645. }
  646. break;
  647. case LdFtn _:
  648. // &LocalFunction is only possible, if the local function is declared static,
  649. // this means that the local function can be declared in the top-level scope.
  650. // leave info.DeclarationScope null/unassigned.
  651. break;
  652. default:
  653. throw new NotSupportedException();
  654. }
  655. }
  656. bool DetermineCaptureAndDeclarationScope(LocalFunctionInfo info, int parameterIndex, ILInstruction arg)
  657. {
  658. ILFunction function = info.Definition;
  659. ILVariable closureVar;
  660. if (parameterIndex >= 0)
  661. {
  662. if (!(parameterIndex < function.Method.Parameters.Count
  663. && IsClosureParameter(function.Method.Parameters[parameterIndex], resolveContext)))
  664. {
  665. return false;
  666. }
  667. }
  668. if (!(arg.MatchLdLoc(out closureVar) || arg.MatchLdLoca(out closureVar)))
  669. {
  670. closureVar = ResolveAncestorScopeReference(arg);
  671. if (closureVar == null)
  672. return false;
  673. }
  674. if (closureVar.Kind == VariableKind.NamedArgument)
  675. return false;
  676. var initializer = GetClosureInitializer(closureVar);
  677. if (initializer == null)
  678. return false;
  679. // determine the capture scope of closureVar and the declaration scope of the function
  680. var additionalScope = BlockContainer.FindClosestContainer(initializer);
  681. if (closureVar.CaptureScope == null)
  682. closureVar.CaptureScope = additionalScope;
  683. else
  684. {
  685. BlockContainer combinedScope = FindCommonAncestorInstruction<BlockContainer>(closureVar.CaptureScope, additionalScope);
  686. Debug.Assert(combinedScope != null);
  687. closureVar.CaptureScope = combinedScope;
  688. }
  689. if (closureVar.Kind == VariableKind.Local)
  690. {
  691. closureVar.Kind = VariableKind.DisplayClassLocal;
  692. }
  693. if (function.DeclarationScope == null)
  694. function.DeclarationScope = closureVar.CaptureScope;
  695. else if (!IsInNestedLocalFunction(function.DeclarationScope, closureVar.CaptureScope.Ancestors.OfType<ILFunction>().First()))
  696. function.DeclarationScope = FindCommonAncestorInstruction<BlockContainer>(function.DeclarationScope, closureVar.CaptureScope);
  697. return true;
  698. ILInstruction GetClosureInitializer(ILVariable variable)
  699. {
  700. var type = variable.Type.UnwrapByRef().GetDefinition();
  701. if (type == null)
  702. return null;
  703. if (variable.Kind == VariableKind.Parameter)
  704. return null;
  705. if (type.Kind == TypeKind.Struct)
  706. return GetStatement(variable.AddressInstructions.OrderBy(i => i.StartILOffset).First());
  707. else
  708. return (StLoc)variable.StoreInstructions[0];
  709. }
  710. }
  711. bool IsInNestedLocalFunction(BlockContainer declarationScope, ILFunction function)
  712. {
  713. return TreeTraversal.PreOrder(function, f => f.LocalFunctions).Any(f => declarationScope.IsDescendantOf(f.Body));
  714. }
  715. internal static bool IsLocalFunctionReference(NewObj inst, ILTransformContext context)
  716. {
  717. if (inst == null || inst.Arguments.Count != 2 || inst.Method.DeclaringType.Kind != TypeKind.Delegate)
  718. return false;
  719. var opCode = inst.Arguments[1].OpCode;
  720. return opCode == OpCode.LdFtn
  721. && IsLocalFunctionMethod(((IInstructionWithMethodOperand)inst.Arguments[1]).Method, context);
  722. }
  723. public static bool IsLocalFunctionMethod(IMethod method, ILTransformContext context)
  724. {
  725. if (method.MetadataToken.IsNil)
  726. return false;
  727. return IsLocalFunctionMethod(method.ParentModule.MetadataFile, (MethodDefinitionHandle)method.MetadataToken, context);
  728. }
  729. public static bool IsLocalFunctionMethod(MetadataFile module, MethodDefinitionHandle methodHandle, ILTransformContext context = null)
  730. {
  731. if (context != null && context.PEFile != module)
  732. return false;
  733. var metadata = module.Metadata;
  734. var method = metadata.GetMethodDefinition(methodHandle);
  735. var declaringType = method.GetDeclaringType();
  736. if ((method.Attributes & MethodAttributes.Assembly) == 0 || !(method.IsCompilerGenerated(metadata) || declaringType.IsCompilerGenerated(metadata)))
  737. return false;
  738. if (!ParseLocalFunctionName(metadata.GetString(method.Name), out _, out _))
  739. return false;
  740. return true;
  741. }
  742. public static bool LocalFunctionNeedsAccessibilityChange(MetadataFile module, MethodDefinitionHandle methodHandle)
  743. {
  744. if (!IsLocalFunctionMethod(module, methodHandle))
  745. return false;
  746. var metadata = module.Metadata;
  747. var method = metadata.GetMethodDefinition(methodHandle);
  748. FindRefStructParameters visitor = new FindRefStructParameters();
  749. method.DecodeSignature(visitor, default);
  750. foreach (var h in visitor.RefStructTypes)
  751. {
  752. var td = metadata.GetTypeDefinition(h);
  753. if (td.IsCompilerGenerated(metadata) && td.IsValueType(metadata))
  754. return true;
  755. }
  756. return false;
  757. }
  758. public static bool IsLocalFunctionDisplayClass(MetadataFile module, TypeDefinitionHandle typeHandle, ILTransformContext context = null)
  759. {
  760. if (context != null && context.PEFile != module)
  761. return false;
  762. var metadata = module.Metadata;
  763. var type = metadata.GetTypeDefinition(typeHandle);
  764. if ((type.Attributes & TypeAttributes.VisibilityMask) != TypeAttributes.NestedPrivate)
  765. return false;
  766. if (!type.HasGeneratedName(metadata))
  767. return false;
  768. var declaringTypeHandle = type.GetDeclaringType();
  769. var declaringType = metadata.GetTypeDefinition(declaringTypeHandle);
  770. foreach (var method in declaringType.GetMethods())
  771. {
  772. if (!IsLocalFunctionMethod(module, method, context))
  773. continue;
  774. var md = metadata.GetMethodDefinition(method);
  775. if (md.DecodeSignature(new FindTypeDecoder(typeHandle, module), default).ParameterTypes.Any())
  776. return true;
  777. }
  778. return false;
  779. }
  780. /// <summary>
  781. /// Newer Roslyn versions use the format "&lt;callerName&gt;g__functionName|x_y"
  782. /// Older versions use "&lt;callerName&gt;g__functionNamex_y"
  783. /// </summary>
  784. static readonly Regex functionNameRegex = new Regex(@"^<(.*)>g__([^\|]*)\|{0,1}\d+(_\d+)?$", RegexOptions.Compiled);
  785. internal static bool ParseLocalFunctionName(string name, out string callerName, out string functionName)
  786. {
  787. callerName = null;
  788. functionName = null;
  789. if (string.IsNullOrWhiteSpace(name))
  790. return false;
  791. var match = functionNameRegex.Match(name);
  792. callerName = match.Groups[1].Value;
  793. functionName = match.Groups[2].Value;
  794. return match.Success;
  795. }
  796. class FindRefStructParameters : ISignatureTypeProvider<TypeDefinitionHandle, Unit>
  797. {
  798. public readonly List<TypeDefinitionHandle> RefStructTypes = new List<TypeDefinitionHandle>();
  799. public TypeDefinitionHandle GetArrayType(TypeDefinitionHandle elementType, ArrayShape shape) => default;
  800. public TypeDefinitionHandle GetFunctionPointerType(MethodSignature<TypeDefinitionHandle> signature) => default;
  801. public TypeDefinitionHandle GetGenericInstantiation(TypeDefinitionHandle genericType, ImmutableArray<TypeDefinitionHandle> typeArguments) => default;
  802. public TypeDefinitionHandle GetGenericMethodParameter(Unit genericContext, int index) => default;
  803. public TypeDefinitionHandle GetGenericTypeParameter(Unit genericContext, int index) => default;
  804. public TypeDefinitionHandle GetModifiedType(TypeDefinitionHandle modifier, TypeDefinitionHandle unmodifiedType, bool isRequired) => default;
  805. public TypeDefinitionHandle GetPinnedType(TypeDefinitionHandle elementType) => default;
  806. public TypeDefinitionHandle GetPointerType(TypeDefinitionHandle elementType) => default;
  807. public TypeDefinitionHandle GetPrimitiveType(PrimitiveTypeCode typeCode) => default;
  808. public TypeDefinitionHandle GetSZArrayType(TypeDefinitionHandle elementType) => default;
  809. public TypeDefinitionHandle GetByReferenceType(TypeDefinitionHandle elementType)
  810. {
  811. if (!elementType.IsNil)
  812. RefStructTypes.Add(elementType);
  813. return elementType;
  814. }
  815. public TypeDefinitionHandle GetTypeFromSpecification(MetadataReader reader, Unit genericContext, TypeSpecificationHandle handle, byte rawTypeKind) => default;
  816. public TypeDefinitionHandle GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind) => handle;
  817. public TypeDefinitionHandle GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind) => default;
  818. }
  819. }
  820. }