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.

886 lines
30 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
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.Diagnostics;
  21. using System.Linq;
  22. using System.Reflection.Metadata;
  23. using ICSharpCode.Decompiler.Disassembler;
  24. using ICSharpCode.Decompiler.IL.ControlFlow;
  25. using ICSharpCode.Decompiler.Metadata;
  26. using ICSharpCode.Decompiler.TypeSystem;
  27. using ICSharpCode.Decompiler.Util;
  28. namespace ICSharpCode.Decompiler.IL.Transforms
  29. {
  30. /// <summary>
  31. /// Transforms closure fields to local variables.
  32. ///
  33. /// This is a post-processing step of <see cref="DelegateConstruction"/>, <see cref="LocalFunctionDecompiler"/>
  34. /// and <see cref="TransformExpressionTrees"/>.
  35. ///
  36. /// In general we can perform SROA (scalar replacement of aggregates) on any variable that
  37. /// satisfies the following conditions:
  38. /// 1) It is initialized by an empty/default constructor call.
  39. /// 2) The variable is never passed to another method.
  40. /// 3) The variable is never the target of an invocation.
  41. ///
  42. /// Note that 2) and 3) apply because declarations and uses of lambdas and local functions
  43. /// are already transformed by the time this transform is applied.
  44. /// </summary>
  45. public class TransformDisplayClassUsage : ILVisitor, IILTransform
  46. {
  47. class VariableToDeclare
  48. {
  49. private readonly DisplayClass container;
  50. private readonly IField field;
  51. private ILVariable declaredVariable;
  52. public string Name => field.Name;
  53. public bool CanPropagate { get; private set; }
  54. public bool UsesInitialValue { get; set; }
  55. public HashSet<ILInstruction> Initializers { get; } = new HashSet<ILInstruction>();
  56. public VariableToDeclare(DisplayClass container, IField field, ILVariable declaredVariable = null)
  57. {
  58. this.container = container;
  59. this.field = field;
  60. this.declaredVariable = declaredVariable;
  61. Debug.Assert(declaredVariable == null || declaredVariable.StateMachineField == field);
  62. }
  63. public void Propagate(ILVariable variable)
  64. {
  65. this.declaredVariable = variable;
  66. this.CanPropagate = variable != null;
  67. }
  68. public ILVariable GetOrDeclare()
  69. {
  70. if (declaredVariable != null)
  71. return declaredVariable;
  72. declaredVariable = container.Variable.Function.RegisterVariable(VariableKind.Local, field.Type, field.Name);
  73. declaredVariable.InitialValueIsInitialized = true;
  74. declaredVariable.UsesInitialValue = UsesInitialValue;
  75. declaredVariable.CaptureScope = container.CaptureScope;
  76. return declaredVariable;
  77. }
  78. }
  79. [DebuggerDisplay("[DisplayClass {Variable} : {Type}]")]
  80. class DisplayClass
  81. {
  82. public readonly ILVariable Variable;
  83. public readonly ITypeDefinition Type;
  84. public readonly Dictionary<IField, VariableToDeclare> VariablesToDeclare;
  85. public BlockContainer CaptureScope;
  86. public ILInstruction Initializer;
  87. public DisplayClass(ILVariable variable, ITypeDefinition type)
  88. {
  89. Variable = variable;
  90. Type = type;
  91. VariablesToDeclare = new Dictionary<IField, VariableToDeclare>();
  92. }
  93. }
  94. ILTransformContext context;
  95. ITypeResolveContext decompilationContext;
  96. readonly Dictionary<ILVariable, DisplayClass> displayClasses = new Dictionary<ILVariable, DisplayClass>();
  97. readonly Dictionary<ILVariable, ILVariable> displayClassCopyMap = new Dictionary<ILVariable, ILVariable>();
  98. void IILTransform.Run(ILFunction function, ILTransformContext context)
  99. {
  100. if (this.context != null)
  101. throw new InvalidOperationException("Reentrancy in " + nameof(TransformDisplayClassUsage));
  102. try
  103. {
  104. this.context = context;
  105. this.decompilationContext = new SimpleTypeResolveContext(context.Function.Method);
  106. AnalyzeFunction(function);
  107. Transform(function);
  108. }
  109. finally
  110. {
  111. ClearState();
  112. }
  113. }
  114. void ClearState()
  115. {
  116. displayClasses.Clear();
  117. displayClassCopyMap.Clear();
  118. this.decompilationContext = null;
  119. this.context = null;
  120. }
  121. void AnalyzeFunction(ILFunction function)
  122. {
  123. void VisitFunction(ILFunction f)
  124. {
  125. foreach (var v in f.Variables.ToArray())
  126. {
  127. var result = AnalyzeVariable(v);
  128. if (result == null || displayClasses.ContainsKey(result.Variable))
  129. continue;
  130. context.Step($"Detected display-class {result.Variable}", result.Initializer ?? f.Body);
  131. displayClasses.Add(result.Variable, result);
  132. }
  133. }
  134. void VisitChildren(ILInstruction inst)
  135. {
  136. foreach (var child in inst.Children)
  137. {
  138. Visit(child);
  139. }
  140. }
  141. void Visit(ILInstruction inst)
  142. {
  143. switch (inst)
  144. {
  145. case ILFunction f:
  146. VisitFunction(f);
  147. VisitChildren(inst);
  148. break;
  149. default:
  150. VisitChildren(inst);
  151. break;
  152. }
  153. }
  154. Visit(function);
  155. foreach (var (v, displayClass) in displayClasses.ToArray())
  156. {
  157. if (!ValidateDisplayClassUses(v, displayClass))
  158. displayClasses.Remove(v);
  159. }
  160. foreach (var displayClass in displayClasses.Values)
  161. {
  162. // handle uninitialized fields
  163. foreach (var f in displayClass.Type.Fields)
  164. {
  165. if (displayClass.VariablesToDeclare.ContainsKey(f))
  166. continue;
  167. var variable = AddVariable(displayClass, null, f);
  168. variable.UsesInitialValue = true;
  169. displayClass.VariablesToDeclare[(IField)f.MemberDefinition] = variable;
  170. }
  171. foreach (var v in displayClass.VariablesToDeclare.Values)
  172. {
  173. if (v.CanPropagate)
  174. {
  175. var variableToPropagate = v.GetOrDeclare();
  176. if (variableToPropagate.Kind != VariableKind.Parameter && !displayClasses.ContainsKey(variableToPropagate))
  177. v.Propagate(null);
  178. }
  179. }
  180. }
  181. }
  182. bool ValidateDisplayClassUses(ILVariable v, DisplayClass displayClass)
  183. {
  184. foreach (var ldloc in v.LoadInstructions)
  185. {
  186. if (!ValidateUse(displayClass, ldloc))
  187. return false;
  188. }
  189. foreach (var ldloca in v.AddressInstructions)
  190. {
  191. if (!ValidateUse(displayClass, ldloca))
  192. return false;
  193. }
  194. return true;
  195. bool ValidateUse(DisplayClass container, ILInstruction use)
  196. {
  197. IField field;
  198. switch (use.Parent)
  199. {
  200. case LdFlda ldflda when ldflda.MatchLdFlda(out var target, out field) && target == use:
  201. var keyField = (IField)field.MemberDefinition;
  202. if (!container.VariablesToDeclare.TryGetValue(keyField, out VariableToDeclare variable) || variable == null)
  203. {
  204. variable = AddVariable(container, null, field);
  205. }
  206. container.VariablesToDeclare[keyField] = variable;
  207. return true;
  208. case StObj stobj when stobj.MatchStObj(out var target, out ILInstruction value, out _) && value == use:
  209. if (target.MatchLdFlda(out var load, out field) && load.MatchLdLocRef(out var otherVariable) && displayClasses.TryGetValue(otherVariable, out var otherDisplayClass))
  210. {
  211. if (otherDisplayClass.VariablesToDeclare.TryGetValue((IField)field.MemberDefinition, out var declaredVar))
  212. return declaredVar.CanPropagate;
  213. }
  214. return false;
  215. case StLoc stloc when stloc.Variable.IsSingleDefinition && stloc.Value == use:
  216. displayClassCopyMap[stloc.Variable] = v;
  217. return ValidateDisplayClassUses(stloc.Variable, displayClass);
  218. default:
  219. return false;
  220. }
  221. }
  222. }
  223. private DisplayClass AnalyzeVariable(ILVariable v)
  224. {
  225. switch (v.Kind)
  226. {
  227. case VariableKind.Parameter:
  228. if (context.Settings.YieldReturn && v.Function.StateMachineCompiledWithMono && v.IsThis())
  229. return HandleMonoStateMachine(v.Function, v);
  230. return null;
  231. case VariableKind.StackSlot:
  232. case VariableKind.Local:
  233. case VariableKind.DisplayClassLocal:
  234. return DetectDisplayClass(v);
  235. case VariableKind.InitializerTarget:
  236. return DetectDisplayClassInitializer(v);
  237. default:
  238. return null;
  239. }
  240. }
  241. DisplayClass DetectDisplayClass(ILVariable v)
  242. {
  243. ITypeDefinition definition;
  244. if (v.Kind != VariableKind.StackSlot)
  245. {
  246. definition = v.Type.GetDefinition();
  247. }
  248. else if (v.StoreInstructions.Count > 0 && v.StoreInstructions[0] is StLoc stloc)
  249. {
  250. definition = stloc.Value.InferType(context.TypeSystem).GetDefinition();
  251. }
  252. else
  253. {
  254. definition = null;
  255. }
  256. if (!ValidateDisplayClassDefinition(definition))
  257. return null;
  258. DisplayClass result;
  259. switch (definition.Kind)
  260. {
  261. case TypeKind.Class:
  262. if (!v.IsSingleDefinition)
  263. return null;
  264. if (!(v.StoreInstructions.SingleOrDefault() is StLoc stloc))
  265. return null;
  266. if (stloc.Value is NewObj newObj && ValidateConstructor(context, newObj.Method))
  267. {
  268. result = new DisplayClass(v, definition) {
  269. CaptureScope = v.CaptureScope,
  270. Initializer = stloc
  271. };
  272. }
  273. else
  274. {
  275. return null;
  276. }
  277. HandleInitBlock(stloc.Parent as Block, stloc.ChildIndex + 1, result, result.Variable);
  278. break;
  279. case TypeKind.Struct:
  280. if (v.StoreInstructions.Count != 0)
  281. return null;
  282. Debug.Assert(v.StoreInstructions.Count == 0);
  283. result = new DisplayClass(v, definition) { CaptureScope = v.CaptureScope };
  284. HandleInitBlock(FindDisplayStructInitBlock(v), 0, result, result.Variable);
  285. break;
  286. default:
  287. return null;
  288. }
  289. if (IsMonoNestedCaptureScope(definition))
  290. {
  291. result.CaptureScope = null;
  292. }
  293. return result;
  294. }
  295. void HandleInitBlock(Block initBlock, int startIndex, DisplayClass result, ILVariable targetVariable)
  296. {
  297. if (initBlock == null)
  298. return;
  299. for (int i = startIndex; i < initBlock.Instructions.Count; i++)
  300. {
  301. var init = initBlock.Instructions[i];
  302. if (!init.MatchStFld(out var target, out var field, out _))
  303. break;
  304. if (!target.MatchLdLocRef(targetVariable))
  305. break;
  306. if (result.VariablesToDeclare.ContainsKey((IField)field.MemberDefinition))
  307. break;
  308. var variable = AddVariable(result, (StObj)init, field);
  309. result.VariablesToDeclare[(IField)field.MemberDefinition] = variable;
  310. }
  311. }
  312. private Block FindDisplayStructInitBlock(ILVariable v)
  313. {
  314. var root = v.Function.Body;
  315. return Visit(root)?.Ancestors.OfType<Block>().FirstOrDefault();
  316. // Try to find a common ancestor of all uses of the variable v.
  317. ILInstruction Visit(ILInstruction inst)
  318. {
  319. switch (inst)
  320. {
  321. case LdLoc l when l.Variable == v:
  322. return l;
  323. case StLoc s when s.Variable == v:
  324. return s;
  325. case LdLoca la when la.Variable == v:
  326. return la;
  327. default:
  328. return VisitChildren(inst);
  329. }
  330. }
  331. ILInstruction VisitChildren(ILInstruction inst)
  332. {
  333. // Visit all children of the instruction
  334. ILInstruction result = null;
  335. foreach (var child in inst.Children)
  336. {
  337. var newResult = Visit(child);
  338. // As soon as there is a second use of v in this sub-tree,
  339. // we can skip all other children and just return this node.
  340. if (result == null)
  341. {
  342. result = newResult;
  343. }
  344. else if (newResult != null)
  345. {
  346. return inst;
  347. }
  348. }
  349. // returns null, if v is not used in this sub-tree;
  350. // returns a descendant of inst, if it is the only use of v in this sub-tree.
  351. return result;
  352. }
  353. }
  354. DisplayClass DetectDisplayClassInitializer(ILVariable v)
  355. {
  356. if (v.StoreInstructions.Count != 1 || !(v.StoreInstructions[0] is StLoc store && store.Parent is Block initializerBlock && initializerBlock.Kind == BlockKind.ObjectInitializer))
  357. return null;
  358. if (!(store.Value is NewObj newObj))
  359. return null;
  360. var definition = newObj.Method.DeclaringType.GetDefinition();
  361. if (!ValidateDisplayClassDefinition(definition))
  362. return null;
  363. if (!ValidateConstructor(context, newObj.Method))
  364. return null;
  365. if (!initializerBlock.Parent.MatchStLoc(out var referenceVariable))
  366. return null;
  367. if (!referenceVariable.IsSingleDefinition)
  368. return null;
  369. if (!(referenceVariable.StoreInstructions.SingleOrDefault() is StLoc))
  370. return null;
  371. var result = new DisplayClass(referenceVariable, definition) {
  372. CaptureScope = referenceVariable.CaptureScope,
  373. Initializer = initializerBlock.Parent
  374. };
  375. HandleInitBlock(initializerBlock, 1, result, v);
  376. return result;
  377. }
  378. private bool ValidateDisplayClassDefinition(ITypeDefinition definition)
  379. {
  380. if (definition == null)
  381. return false;
  382. if (definition.ParentModule.MetadataFile != context.PEFile)
  383. return false;
  384. // We do not want to accidentially transform state-machines and thus destroy them.
  385. var token = (TypeDefinitionHandle)definition.MetadataToken;
  386. var metadata = definition.ParentModule.MetadataFile.Metadata;
  387. if (YieldReturnDecompiler.IsCompilerGeneratorEnumerator(token, metadata))
  388. return false;
  389. if (AsyncAwaitDecompiler.IsCompilerGeneratedStateMachine(token, metadata))
  390. return false;
  391. if (!context.Settings.AggressiveScalarReplacementOfAggregates)
  392. {
  393. if (definition.DeclaringTypeDefinition == null)
  394. return false;
  395. if (!IsPotentialClosure(context, definition))
  396. return false;
  397. }
  398. return true;
  399. }
  400. internal static bool ValidateConstructor(ILTransformContext context, IMethod method)
  401. {
  402. try
  403. {
  404. if (method.Parameters.Count != 0)
  405. return false;
  406. var handle = (MethodDefinitionHandle)method.MetadataToken;
  407. var module = (MetadataModule)method.ParentModule;
  408. var file = module.MetadataFile;
  409. if (handle.IsNil || file != context.PEFile)
  410. return false;
  411. var def = file.Metadata.GetMethodDefinition(handle);
  412. if (def.RelativeVirtualAddress == 0)
  413. return false;
  414. var body = file.GetMethodBody(def.RelativeVirtualAddress);
  415. // some compilers produce ctors with unused local variables
  416. // see https://github.com/icsharpcode/ILSpy/issues/2174
  417. //if (!body.LocalSignature.IsNil)
  418. // return false;
  419. if (body.ExceptionRegions.Length != 0)
  420. return false;
  421. var reader = body.GetILReader();
  422. if (reader.Length < 7)
  423. return false;
  424. // IL_0000: ldarg.0
  425. // IL_0001: call instance void [mscorlib]System.Object::.ctor()
  426. // IL_0006: ret
  427. var opCode = DecodeOpCodeSkipNop(ref reader);
  428. switch (opCode)
  429. {
  430. case ILOpCode.Ldarg:
  431. case ILOpCode.Ldarg_s:
  432. if (reader.DecodeIndex(opCode) != 0)
  433. return false;
  434. break;
  435. case ILOpCode.Ldarg_0:
  436. // OK
  437. break;
  438. default:
  439. return false;
  440. }
  441. if (DecodeOpCodeSkipNop(ref reader) != ILOpCode.Call)
  442. return false;
  443. var baseCtorHandle = MetadataTokenHelpers.EntityHandleOrNil(reader.ReadInt32());
  444. if (baseCtorHandle.IsNil)
  445. return false;
  446. var objectCtor = module.ResolveMethod(baseCtorHandle, new TypeSystem.GenericContext());
  447. if (!objectCtor.DeclaringType.IsKnownType(KnownTypeCode.Object))
  448. return false;
  449. if (!objectCtor.IsConstructor || objectCtor.Parameters.Count != 0)
  450. return false;
  451. return DecodeOpCodeSkipNop(ref reader) == ILOpCode.Ret;
  452. }
  453. catch (BadImageFormatException)
  454. {
  455. return false;
  456. }
  457. }
  458. static ILOpCode DecodeOpCodeSkipNop(ref BlobReader reader)
  459. {
  460. ILOpCode code;
  461. do
  462. {
  463. code = reader.DecodeOpCode();
  464. } while (code == ILOpCode.Nop);
  465. return code;
  466. }
  467. VariableToDeclare AddVariable(DisplayClass result, StObj statement, IField field)
  468. {
  469. VariableToDeclare variable = new VariableToDeclare(result, field);
  470. if (statement != null)
  471. {
  472. variable.Propagate(ResolveVariableToPropagate(statement.Value, field.Type));
  473. variable.Initializers.Add(statement);
  474. }
  475. variable.UsesInitialValue =
  476. result.Type.IsReferenceType != false || result.Variable.UsesInitialValue;
  477. return variable;
  478. }
  479. /// <summary>
  480. /// Resolves references to variables that can be propagated.
  481. /// If a value does not match either LdLoc or a LdObj LdLdFlda* LdLoc chain, null is returned.
  482. /// The if any of the variables/fields in the chain cannot be propagated, null is returned.
  483. /// </summary>
  484. ILVariable ResolveVariableToPropagate(ILInstruction value, IType expectedType = null)
  485. {
  486. ILVariable v;
  487. switch (value)
  488. {
  489. case LdLoc load:
  490. v = load.Variable;
  491. if (v.Kind == VariableKind.Parameter)
  492. {
  493. if (v.LoadCount != 1 && !v.IsThis())
  494. {
  495. // If the variable is a parameter and it is used elsewhere, we cannot propagate it.
  496. // "dc.field = v; dc.field.mutate(); use(v);" cannot turn to "v.mutate(); use(v)"
  497. return null;
  498. }
  499. }
  500. else
  501. {
  502. // Non-parameter propagation will later be checked, and will only be allowed for display classes
  503. if (v.Type.IsReferenceType != true)
  504. {
  505. // don't allow propagation for display structs (as used with local functions)
  506. return null;
  507. }
  508. }
  509. if (!v.IsSingleDefinition)
  510. {
  511. // "dc.field = v; v = 42; use(dc.field)" cannot turn to "v = 42; use(v);"
  512. return null;
  513. }
  514. if (!(expectedType == null || v.Kind == VariableKind.StackSlot || NormalizeTypeVisitor.IgnoreNullability.EquivalentTypes(v.Type, expectedType)))
  515. return null;
  516. return v;
  517. case LdObj ldfld:
  518. DisplayClass currentDisplayClass = null;
  519. foreach (var item in ldfld.Target.Descendants)
  520. {
  521. if (IsDisplayClassLoad(item, out v))
  522. {
  523. if (!displayClasses.TryGetValue(v, out currentDisplayClass))
  524. return null;
  525. }
  526. if (currentDisplayClass == null)
  527. return null;
  528. if (item is LdFlda ldf && currentDisplayClass.VariablesToDeclare.TryGetValue((IField)ldf.Field.MemberDefinition, out var vd))
  529. {
  530. if (!vd.CanPropagate)
  531. return null;
  532. if (!displayClasses.TryGetValue(vd.GetOrDeclare(), out currentDisplayClass))
  533. return null;
  534. }
  535. }
  536. return currentDisplayClass.Variable;
  537. default:
  538. return null;
  539. }
  540. }
  541. private void Transform(ILFunction function)
  542. {
  543. VisitILFunction(function);
  544. context.Step($"ResetHasInitialValueFlag", function);
  545. foreach (var f in function.Descendants.OfType<ILFunction>())
  546. {
  547. RemoveDeadVariableInit.ResetUsesInitialValueFlag(f, context);
  548. f.CapturedVariables.RemoveWhere(v => v.IsDead);
  549. }
  550. }
  551. internal static bool IsClosure(ILTransformContext context, ILVariable variable, out ITypeDefinition closureType, out ILInstruction initializer)
  552. {
  553. closureType = null;
  554. initializer = null;
  555. if (variable.IsSingleDefinition && variable.StoreInstructions.SingleOrDefault() is StLoc inst)
  556. {
  557. initializer = inst;
  558. if (IsClosureInit(context, inst, out closureType))
  559. {
  560. return true;
  561. }
  562. }
  563. closureType = variable.Type.GetDefinition();
  564. if (context.Settings.LocalFunctions && closureType?.Kind == TypeKind.Struct
  565. && variable.UsesInitialValue && IsPotentialClosure(context, closureType))
  566. {
  567. initializer = LocalFunctionDecompiler.GetStatement(variable.AddressInstructions.OrderBy(i => i.StartILOffset).First());
  568. return true;
  569. }
  570. return false;
  571. }
  572. static bool IsClosureInit(ILTransformContext context, StLoc inst, out ITypeDefinition closureType)
  573. {
  574. if (inst.Value is NewObj newObj)
  575. {
  576. closureType = newObj.Method.DeclaringTypeDefinition;
  577. return closureType != null && IsPotentialClosure(context, newObj);
  578. }
  579. closureType = null;
  580. return false;
  581. }
  582. bool IsMonoNestedCaptureScope(ITypeDefinition closureType)
  583. {
  584. if (!closureType.Name.Contains("AnonStorey"))
  585. return false;
  586. var decompilationContext = new SimpleTypeResolveContext(context.Function.Method);
  587. return closureType.Fields.Any(f => IsPotentialClosure(decompilationContext.CurrentTypeDefinition, f.ReturnType.GetDefinition()));
  588. }
  589. /// <summary>
  590. /// mcs likes to optimize closures in yield state machines away by moving the captured variables' fields into the state machine type,
  591. /// We construct a <see cref="DisplayClass"/> that spans the whole method body.
  592. /// </summary>
  593. DisplayClass HandleMonoStateMachine(ILFunction function, ILVariable thisVariable)
  594. {
  595. if (!(function.StateMachineCompiledWithMono && thisVariable.IsThis()))
  596. return null;
  597. // Special case for Mono-compiled yield state machines
  598. ITypeDefinition closureType = thisVariable.Type.GetDefinition();
  599. if (!(closureType != decompilationContext.CurrentTypeDefinition
  600. && IsPotentialClosure(decompilationContext.CurrentTypeDefinition, closureType, allowTypeImplementingInterfaces: true)))
  601. return null;
  602. var displayClass = new DisplayClass(thisVariable, thisVariable.Type.GetDefinition());
  603. displayClass.CaptureScope = (BlockContainer)function.Body;
  604. foreach (var stateMachineVariable in function.Variables)
  605. {
  606. if (stateMachineVariable.StateMachineField == null || displayClass.VariablesToDeclare.ContainsKey(stateMachineVariable.StateMachineField))
  607. continue;
  608. VariableToDeclare variableToDeclare = new VariableToDeclare(displayClass, stateMachineVariable.StateMachineField, stateMachineVariable);
  609. displayClass.VariablesToDeclare.Add(stateMachineVariable.StateMachineField, variableToDeclare);
  610. }
  611. if (!function.Method.IsStatic && FindThisField(out var thisField))
  612. {
  613. var thisVar = function.Variables
  614. .FirstOrDefault(t => t.IsThis() && t.Type.GetDefinition() == decompilationContext.CurrentTypeDefinition);
  615. if (thisVar == null)
  616. {
  617. thisVar = new ILVariable(VariableKind.Parameter, decompilationContext.CurrentTypeDefinition, -1) {
  618. Name = "this", StateMachineField = thisField
  619. };
  620. function.Variables.Add(thisVar);
  621. }
  622. else if (thisVar.StateMachineField != null && displayClass.VariablesToDeclare.ContainsKey(thisVar.StateMachineField))
  623. {
  624. // "this" was already added previously, no need to add it twice.
  625. return displayClass;
  626. }
  627. VariableToDeclare variableToDeclare = new VariableToDeclare(displayClass, thisField, thisVar);
  628. displayClass.VariablesToDeclare.Add(thisField, variableToDeclare);
  629. }
  630. return displayClass;
  631. bool FindThisField(out IField foundField)
  632. {
  633. foundField = null;
  634. foreach (var field in closureType.GetFields(f2 => !f2.IsStatic && !displayClass.VariablesToDeclare.ContainsKey(f2) && f2.Type.GetDefinition() == decompilationContext.CurrentTypeDefinition))
  635. {
  636. thisField = field;
  637. return true;
  638. }
  639. return false;
  640. }
  641. }
  642. internal static bool IsPotentialClosure(ILTransformContext context, NewObj inst)
  643. {
  644. var decompilationContext = new SimpleTypeResolveContext(context.Function.Ancestors.OfType<ILFunction>().Last().Method);
  645. return IsPotentialClosure(decompilationContext.CurrentTypeDefinition, inst.Method.DeclaringTypeDefinition);
  646. }
  647. internal static bool IsPotentialClosure(ILTransformContext context, ITypeDefinition potentialDisplayClass)
  648. {
  649. var decompilationContext = new SimpleTypeResolveContext(context.Function.Ancestors.OfType<ILFunction>().Last().Method);
  650. return IsPotentialClosure(decompilationContext.CurrentTypeDefinition, potentialDisplayClass);
  651. }
  652. internal static bool IsPotentialClosure(ITypeDefinition decompiledTypeDefinition, ITypeDefinition potentialDisplayClass, bool allowTypeImplementingInterfaces = false)
  653. {
  654. if (potentialDisplayClass == null || !potentialDisplayClass.IsCompilerGeneratedOrIsInCompilerGeneratedClass())
  655. return false;
  656. switch (potentialDisplayClass.Kind)
  657. {
  658. case TypeKind.Struct:
  659. break;
  660. case TypeKind.Class:
  661. if (!allowTypeImplementingInterfaces)
  662. {
  663. if (!potentialDisplayClass.DirectBaseTypes.All(t => t.IsKnownType(KnownTypeCode.Object)))
  664. return false;
  665. }
  666. break;
  667. default:
  668. return false;
  669. }
  670. while (potentialDisplayClass != decompiledTypeDefinition)
  671. {
  672. potentialDisplayClass = potentialDisplayClass.DeclaringTypeDefinition;
  673. if (potentialDisplayClass == null)
  674. return false;
  675. }
  676. return true;
  677. }
  678. readonly Stack<ILFunction> currentFunctions = new Stack<ILFunction>();
  679. protected internal override void VisitILFunction(ILFunction function)
  680. {
  681. context.StepStartGroup("Visit " + function.Name);
  682. try
  683. {
  684. this.currentFunctions.Push(function);
  685. base.VisitILFunction(function);
  686. }
  687. finally
  688. {
  689. this.currentFunctions.Pop();
  690. context.StepEndGroup(keepIfEmpty: true);
  691. }
  692. }
  693. protected override void Default(ILInstruction inst)
  694. {
  695. ILInstruction next;
  696. for (var child = inst.Children.FirstOrDefault(); child != null; child = next)
  697. {
  698. next = child.GetNextSibling();
  699. child.AcceptVisitor(this);
  700. }
  701. }
  702. protected internal override void VisitStLoc(StLoc inst)
  703. {
  704. DisplayClass displayClass;
  705. if (inst.Parent is Block parentBlock && inst.Variable.IsSingleDefinition)
  706. {
  707. if ((inst.Variable.Kind == VariableKind.Local || inst.Variable.Kind == VariableKind.StackSlot) && inst.Variable.LoadCount == 0)
  708. {
  709. // traverse pre-order, so that we do not have to deal with more special cases afterwards
  710. base.VisitStLoc(inst);
  711. if (inst.Value is StLoc || inst.Value is CompoundAssignmentInstruction)
  712. {
  713. context.Step($"Remove unused variable assignment {inst.Variable.Name}", inst);
  714. inst.ReplaceWith(inst.Value);
  715. }
  716. return;
  717. }
  718. if (displayClasses.TryGetValue(inst.Variable, out displayClass) && displayClass.Initializer == inst)
  719. {
  720. // inline contents of object initializer block
  721. if (inst.Value is Block initBlock && initBlock.Kind == BlockKind.ObjectInitializer)
  722. {
  723. context.Step($"Remove initializer of {inst.Variable.Name}", inst);
  724. for (int i = 1; i < initBlock.Instructions.Count; i++)
  725. {
  726. var stobj = (StObj)initBlock.Instructions[i];
  727. var variable = displayClass.VariablesToDeclare[(IField)((LdFlda)stobj.Target).Field.MemberDefinition];
  728. parentBlock.Instructions.Insert(inst.ChildIndex + i, new StLoc(variable.GetOrDeclare(), stobj.Value).WithILRange(stobj));
  729. }
  730. }
  731. context.Step($"Remove initializer of {inst.Variable.Name}", inst);
  732. parentBlock.Instructions.Remove(inst);
  733. return;
  734. }
  735. if (inst.Value is LdLoc || inst.Value is LdObj)
  736. {
  737. // in some cases (e.g. if inlining fails), there can be a reference to a display class in a stack slot,
  738. // in that case it is necessary to resolve the reference and iff it can be propagated, replace all loads
  739. // of the single-definition.
  740. if (!displayClassCopyMap.TryGetValue(inst.Variable, out var referencedDisplayClass))
  741. {
  742. referencedDisplayClass = ResolveVariableToPropagate(inst.Value);
  743. }
  744. if (referencedDisplayClass != null && displayClasses.TryGetValue(referencedDisplayClass, out _))
  745. {
  746. context.Step($"Propagate reference to {referencedDisplayClass.Name} in {inst.Variable}", inst);
  747. foreach (var ld in inst.Variable.LoadInstructions.ToArray())
  748. {
  749. ld.ReplaceWith(new LdLoc(referencedDisplayClass).WithILRange(ld));
  750. }
  751. parentBlock.Instructions.Remove(inst);
  752. return;
  753. }
  754. }
  755. }
  756. base.VisitStLoc(inst);
  757. }
  758. protected internal override void VisitStObj(StObj inst)
  759. {
  760. if (IsDisplayClassFieldAccess(inst.Target, out var v, out var displayClass, out var field))
  761. {
  762. VariableToDeclare vd = displayClass.VariablesToDeclare[(IField)field.MemberDefinition];
  763. if (vd.CanPropagate && vd.Initializers.Contains(inst))
  764. {
  765. if (inst.Parent is Block containingBlock)
  766. {
  767. context.Step($"Remove initializer of {v.Name}.{vd.Name} due to propagation", inst);
  768. containingBlock.Instructions.Remove(inst);
  769. return;
  770. }
  771. }
  772. if (inst.Value is LdLoc ldLoc && ldLoc.Variable is { IsSingleDefinition: true, CaptureScope: null }
  773. && ldLoc.Variable.StoreInstructions.FirstOrDefault() is StLoc stloc
  774. && stloc.Parent is Block block)
  775. {
  776. ILInlining.InlineOneIfPossible(block, stloc.ChildIndex, InliningOptions.None, context);
  777. }
  778. }
  779. base.VisitStObj(inst);
  780. EarlyExpressionTransforms.StObjToStLoc(inst, context);
  781. }
  782. protected internal override void VisitLdObj(LdObj inst)
  783. {
  784. base.VisitLdObj(inst);
  785. EarlyExpressionTransforms.LdObjToLdLoc(inst, context);
  786. }
  787. private bool IsDisplayClassLoad(ILInstruction target, out ILVariable variable)
  788. {
  789. // We cannot use MatchLdLocRef here because local functions use ref parameters
  790. if (!target.MatchLdLoc(out variable) && !target.MatchLdLoca(out variable))
  791. return false;
  792. if (displayClassCopyMap.TryGetValue(variable, out ILVariable other))
  793. variable = other;
  794. return true;
  795. }
  796. private bool IsDisplayClassFieldAccess(ILInstruction inst,
  797. out ILVariable displayClassVar, out DisplayClass displayClass, out IField field)
  798. {
  799. displayClass = null;
  800. displayClassVar = null;
  801. field = null;
  802. if (inst is not LdFlda ldflda)
  803. return false;
  804. field = ldflda.Field;
  805. return IsDisplayClassLoad(ldflda.Target, out displayClassVar)
  806. && displayClasses.TryGetValue(displayClassVar, out displayClass);
  807. }
  808. protected internal override void VisitLdFlda(LdFlda inst)
  809. {
  810. base.VisitLdFlda(inst);
  811. // Get display class info
  812. if (!IsDisplayClassFieldAccess(inst, out _, out DisplayClass displayClass, out IField field))
  813. return;
  814. var keyField = (IField)field.MemberDefinition;
  815. var v = displayClass.VariablesToDeclare[keyField];
  816. context.Step($"Replace {field.Name} with captured variable {v.Name}", inst);
  817. ILVariable variable = v.GetOrDeclare();
  818. inst.ReplaceWith(new LdLoca(variable).WithILRange(inst));
  819. // add captured variable to all descendant functions from the declaring function to this use-site function
  820. foreach (var f in currentFunctions)
  821. {
  822. if (f == variable.Function)
  823. break;
  824. f.CapturedVariables.Add(variable);
  825. }
  826. }
  827. }
  828. }