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.

331 lines
10 KiB

6 years ago
6 years ago
6 years ago
  1. // Copyright (c) 2011-2016 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.Collections.Generic;
  19. using System.Linq;
  20. using System.Reflection.Metadata;
  21. using ICSharpCode.Decompiler.CSharp;
  22. using ICSharpCode.Decompiler.TypeSystem;
  23. namespace ICSharpCode.Decompiler.IL.Transforms
  24. {
  25. /// <summary>
  26. /// Transforms anonymous methods and lambdas by creating nested ILFunctions.
  27. /// </summary>
  28. public class DelegateConstruction : IILTransform
  29. {
  30. ILTransformContext context;
  31. ITypeResolveContext decompilationContext;
  32. readonly Stack<MethodDefinitionHandle> activeMethods = new Stack<MethodDefinitionHandle>();
  33. void IILTransform.Run(ILFunction function, ILTransformContext context)
  34. {
  35. if (!context.Settings.AnonymousMethods)
  36. return;
  37. var prevContext = this.context;
  38. var prevDecompilationContext = this.decompilationContext;
  39. try
  40. {
  41. activeMethods.Push((MethodDefinitionHandle)function.Method.MetadataToken);
  42. this.context = context;
  43. this.decompilationContext = new SimpleTypeResolveContext(function.Method);
  44. var cancellationToken = context.CancellationToken;
  45. foreach (var inst in function.Descendants)
  46. {
  47. cancellationToken.ThrowIfCancellationRequested();
  48. if (!MatchDelegateConstruction(inst, out var targetMethod, out var target,
  49. out var delegateType, allowTransformed: false))
  50. continue;
  51. context.StepStartGroup($"TransformDelegateConstruction {inst.StartILOffset}", inst);
  52. ILFunction f = TransformDelegateConstruction(inst, targetMethod, target, delegateType);
  53. if (f != null && target is IInstructionWithVariableOperand instWithVar)
  54. {
  55. var v = instWithVar.Variable;
  56. if (v.Kind == VariableKind.Local)
  57. {
  58. v.Kind = VariableKind.DisplayClassLocal;
  59. }
  60. if (v.IsSingleDefinition
  61. && v.StoreInstructions.SingleOrDefault() is StLoc store
  62. && store.Value is NewObj)
  63. {
  64. v.CaptureScope = BlockContainer.FindClosestContainer(store);
  65. }
  66. }
  67. context.StepEndGroup();
  68. }
  69. }
  70. finally
  71. {
  72. this.context = prevContext;
  73. this.decompilationContext = prevDecompilationContext;
  74. activeMethods.Pop();
  75. }
  76. }
  77. internal static bool MatchDelegateConstruction(ILInstruction inst, out IMethod targetMethod,
  78. out ILInstruction target, out IType delegateType, bool allowTransformed = false)
  79. {
  80. targetMethod = null;
  81. target = null;
  82. delegateType = null;
  83. switch (inst)
  84. {
  85. case NewObj call:
  86. if (call.Arguments.Count != 2)
  87. return false;
  88. target = call.Arguments[0];
  89. var opCode = call.Arguments[1].OpCode;
  90. delegateType = call.Method.DeclaringType;
  91. if (!(opCode == OpCode.LdFtn || opCode == OpCode.LdVirtFtn
  92. || (allowTransformed && opCode == OpCode.ILFunction)))
  93. return false;
  94. targetMethod = ((IInstructionWithMethodOperand)call.Arguments[1]).Method;
  95. break;
  96. case LdVirtDelegate ldVirtDelegate:
  97. target = ldVirtDelegate.Argument;
  98. targetMethod = ldVirtDelegate.Method;
  99. delegateType = ldVirtDelegate.Type;
  100. break;
  101. default:
  102. return false;
  103. }
  104. return delegateType.Kind == TypeKind.Delegate || delegateType.Kind == TypeKind.Unknown;
  105. }
  106. static bool IsAnonymousMethod(ITypeDefinition decompiledTypeDefinition, IMethod method)
  107. {
  108. if (method == null)
  109. return false;
  110. if (!(method.HasGeneratedName()
  111. || method.Name.Contains("$")
  112. || method.IsCompilerGeneratedOrIsInCompilerGeneratedClass()
  113. || TransformDisplayClassUsage.IsPotentialClosure(
  114. decompiledTypeDefinition, method.DeclaringTypeDefinition)
  115. || ContainsAnonymousType(method)))
  116. {
  117. return false;
  118. }
  119. return true;
  120. }
  121. static bool ContainsAnonymousType(IMethod method)
  122. {
  123. if (method.ReturnType.ContainsAnonymousType())
  124. return true;
  125. foreach (var p in method.Parameters)
  126. {
  127. if (p.Type.ContainsAnonymousType())
  128. return true;
  129. }
  130. return false;
  131. }
  132. static GenericContext? GenericContextFromTypeArguments(TypeParameterSubstitution subst)
  133. {
  134. var classTypeParameters = new List<ITypeParameter>();
  135. var methodTypeParameters = new List<ITypeParameter>();
  136. if (subst.ClassTypeArguments != null)
  137. {
  138. foreach (var t in subst.ClassTypeArguments)
  139. {
  140. if (t is ITypeParameter tp)
  141. classTypeParameters.Add(tp);
  142. else
  143. return null;
  144. }
  145. }
  146. if (subst.MethodTypeArguments != null)
  147. {
  148. foreach (var t in subst.MethodTypeArguments)
  149. {
  150. if (t is ITypeParameter tp)
  151. methodTypeParameters.Add(tp);
  152. else
  153. return null;
  154. }
  155. }
  156. return new GenericContext(classTypeParameters, methodTypeParameters);
  157. }
  158. ILFunction TransformDelegateConstruction(
  159. ILInstruction value, IMethod targetMethod,
  160. ILInstruction target, IType delegateType)
  161. {
  162. if (!IsAnonymousMethod(decompilationContext.CurrentTypeDefinition, targetMethod))
  163. return null;
  164. if (targetMethod.MetadataToken.IsNil)
  165. return null;
  166. if (LocalFunctionDecompiler.IsLocalFunctionMethod(targetMethod, context))
  167. return null;
  168. if (!ValidateDelegateTarget(target))
  169. return null;
  170. var handle = (MethodDefinitionHandle)targetMethod.MetadataToken;
  171. if (activeMethods.Contains(handle))
  172. {
  173. this.context.Function.Warnings.Add(" Found self-referencing delegate construction. Abort transformation to avoid stack overflow.");
  174. return null;
  175. }
  176. var methodDefinition = context.PEFile.Metadata.GetMethodDefinition((MethodDefinitionHandle)targetMethod.MetadataToken);
  177. if (!methodDefinition.HasBody())
  178. return null;
  179. var genericContext = GenericContextFromTypeArguments(targetMethod.Substitution);
  180. if (genericContext == null)
  181. return null;
  182. var ilReader = context.CreateILReader();
  183. var body = context.PEFile.GetMethodBody(methodDefinition.RelativeVirtualAddress);
  184. var function = ilReader.ReadIL((MethodDefinitionHandle)targetMethod.MetadataToken, body, genericContext.Value, ILFunctionKind.Delegate, context.CancellationToken);
  185. function.DelegateType = delegateType;
  186. // Embed the lambda into the parent function's ILAst, so that "Show steps" can show
  187. // how the lambda body is being transformed.
  188. value.ReplaceWith(function);
  189. function.CheckInvariant(ILPhase.Normal);
  190. var contextPrefix = targetMethod.Name;
  191. foreach (ILVariable v in function.Variables.Where(v => v.Kind != VariableKind.Parameter))
  192. {
  193. v.Name = contextPrefix + v.Name;
  194. }
  195. var nestedContext = new ILTransformContext(context, function);
  196. function.RunTransforms(CSharpDecompiler.GetILTransforms().TakeWhile(t => !(t is DelegateConstruction)).Concat(GetTransforms()), nestedContext);
  197. nestedContext.Step("DelegateConstruction (ReplaceDelegateTargetVisitor)", function);
  198. function.AcceptVisitor(new ReplaceDelegateTargetVisitor(target, function.Variables.SingleOrDefault(VariableKindExtensions.IsThis)));
  199. // handle nested lambdas
  200. nestedContext.StepStartGroup("DelegateConstruction (nested lambdas)", function);
  201. ((IILTransform)this).Run(function, nestedContext);
  202. nestedContext.StepEndGroup();
  203. function.AddILRange(target);
  204. function.AddILRange(value);
  205. if (value is Call call)
  206. function.AddILRange(call.Arguments[1]);
  207. return function;
  208. }
  209. private static bool ValidateDelegateTarget(ILInstruction inst)
  210. {
  211. switch (inst)
  212. {
  213. case LdNull _:
  214. return true;
  215. case LdLoc ldloc:
  216. return ldloc.Variable.IsSingleDefinition;
  217. case LdObj ldobj:
  218. // TODO : should make sure that the display-class 'this' is unused,
  219. // if the delegate target is ldobj(ldsflda field).
  220. if (ldobj.Target is LdsFlda)
  221. return true;
  222. // TODO : ldfld chains must be validated more thoroughly, i.e., we should make sure
  223. // that the value of the field is never changed.
  224. ILInstruction target = ldobj;
  225. while (target is LdObj || target is LdFlda)
  226. {
  227. if (target is LdObj o)
  228. {
  229. target = o.Target;
  230. continue;
  231. }
  232. if (target is LdFlda f)
  233. {
  234. target = f.Target;
  235. continue;
  236. }
  237. }
  238. return target is LdLoc;
  239. default:
  240. return false;
  241. }
  242. }
  243. private IEnumerable<IILTransform> GetTransforms()
  244. {
  245. yield return new CombineExitsTransform();
  246. }
  247. /// <summary>
  248. /// Replaces loads of 'this' with the target expression.
  249. /// Async delegates use: ldobj(ldloca this).
  250. /// </summary>
  251. internal class ReplaceDelegateTargetVisitor : ILVisitor
  252. {
  253. readonly ILVariable thisVariable;
  254. readonly ILInstruction target;
  255. public ReplaceDelegateTargetVisitor(ILInstruction target, ILVariable thisVariable)
  256. {
  257. this.target = target;
  258. this.thisVariable = thisVariable;
  259. }
  260. protected override void Default(ILInstruction inst)
  261. {
  262. foreach (var child in inst.Children)
  263. {
  264. child.AcceptVisitor(this);
  265. }
  266. }
  267. protected internal override void VisitILFunction(ILFunction function)
  268. {
  269. if (function == thisVariable?.Function)
  270. {
  271. ILVariable v = null;
  272. switch (target)
  273. {
  274. case LdLoc l:
  275. v = l.Variable;
  276. break;
  277. case LdObj lo:
  278. ILInstruction inner = lo.Target;
  279. while (inner is LdFlda ldf)
  280. {
  281. inner = ldf.Target;
  282. }
  283. if (inner is LdLoc l2)
  284. v = l2.Variable;
  285. break;
  286. }
  287. if (v != null)
  288. function.CapturedVariables.Add(v);
  289. }
  290. base.VisitILFunction(function);
  291. }
  292. protected internal override void VisitLdLoc(LdLoc inst)
  293. {
  294. if (inst.Variable == thisVariable)
  295. {
  296. inst.ReplaceWith(target.Clone());
  297. return;
  298. }
  299. base.VisitLdLoc(inst);
  300. }
  301. protected internal override void VisitLdObj(LdObj inst)
  302. {
  303. if (inst.Target.MatchLdLoca(thisVariable))
  304. {
  305. inst.ReplaceWith(target.Clone());
  306. return;
  307. }
  308. base.VisitLdObj(inst);
  309. }
  310. }
  311. }
  312. }