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.

333 lines
12 KiB

  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;
  19. using System.Collections.Generic;
  20. using System.Linq;
  21. using ICSharpCode.Decompiler.CSharp;
  22. using ICSharpCode.Decompiler.TypeSystem;
  23. namespace ICSharpCode.Decompiler.IL.Transforms
  24. {
  25. public class DelegateConstruction : IILTransform
  26. {
  27. ILTransformContext context;
  28. ITypeResolveContext decompilationContext;
  29. void IILTransform.Run(ILFunction function, ILTransformContext context)
  30. {
  31. if (!context.Settings.AnonymousMethods)
  32. return;
  33. this.context = context;
  34. this.decompilationContext = new SimpleTypeResolveContext(context.TypeSystem.Resolve(function.Method));
  35. var orphanedVariableInits = new List<ILInstruction>();
  36. var targetsToReplace = new List<IInstructionWithVariableOperand>();
  37. foreach (var block in function.Descendants.OfType<Block>()) {
  38. for (int i = block.Instructions.Count - 1; i >= 0; i--) {
  39. context.CancellationToken.ThrowIfCancellationRequested();
  40. foreach (var call in block.Instructions[i].Descendants.OfType<NewObj>()) {
  41. ILInstruction target;
  42. ILFunction f = TransformDelegateConstruction(call, out target);
  43. if (f != null) {
  44. call.Arguments[0].ReplaceWith(new Nop());
  45. call.Arguments[1].ReplaceWith(f);
  46. }
  47. if (target is IInstructionWithVariableOperand && !target.MatchLdThis())
  48. targetsToReplace.Add((IInstructionWithVariableOperand)target);
  49. }
  50. ILVariable targetVariable;
  51. ILInstruction value;
  52. if (block.Instructions[i].MatchStLoc(out targetVariable, out value)) {
  53. var newObj = value as NewObj;
  54. // TODO : it is probably not a good idea to remove *all* display-classes
  55. // is there a way to minimize the false-positives?
  56. if (newObj != null && IsInSimpleDisplayClass(newObj.Method)) {
  57. targetVariable.CaptureScope = FindBlockContainer(block);
  58. targetsToReplace.Add((IInstructionWithVariableOperand)block.Instructions[i]);
  59. }
  60. }
  61. }
  62. }
  63. foreach (var target in targetsToReplace.OrderByDescending(t => ((ILInstruction)t).ILRange.Start)) {
  64. function.AcceptVisitor(new TransformDisplayClassUsages(function, target, target.Variable.CaptureScope, orphanedVariableInits));
  65. }
  66. foreach (var store in orphanedVariableInits) {
  67. if (store.Parent is Block containingBlock)
  68. containingBlock.Instructions.Remove(store);
  69. }
  70. }
  71. private BlockContainer FindBlockContainer(Block block)
  72. {
  73. return BlockContainer.FindClosestContainer(block) ?? throw new NotSupportedException();
  74. }
  75. static bool IsInSimpleDisplayClass(IMethod method)
  76. {
  77. if (!method.IsCompilerGeneratedOrIsInCompilerGeneratedClass())
  78. return false;
  79. return IsSimpleDisplayClass(method.DeclaringType);
  80. }
  81. internal static bool IsSimpleDisplayClass(IType type)
  82. {
  83. if (!type.HasGeneratedName() || (!type.Name.Contains("DisplayClass") && !type.Name.Contains("AnonStorey")))
  84. return false;
  85. if (type.DirectBaseTypes.Any(t => !t.IsKnownType(KnownTypeCode.Object)))
  86. return false;
  87. return true;
  88. }
  89. #region TransformDelegateConstruction
  90. internal static bool IsDelegateConstruction(NewObj inst, bool allowTransformed = false)
  91. {
  92. if (inst == null || inst.Arguments.Count != 2 || inst.Method.DeclaringType.Kind != TypeKind.Delegate)
  93. return false;
  94. var opCode = inst.Arguments[1].OpCode;
  95. return opCode == OpCode.LdFtn || opCode == OpCode.LdVirtFtn || (allowTransformed && opCode == OpCode.ILFunction);
  96. }
  97. internal static bool IsPotentialClosure(ILTransformContext context, NewObj inst)
  98. {
  99. var decompilationContext = new SimpleTypeResolveContext(context.TypeSystem.Resolve(context.Function.Method));
  100. return IsPotentialClosure(decompilationContext.CurrentTypeDefinition, inst.Method.DeclaringTypeDefinition);
  101. }
  102. static bool IsAnonymousMethod(ITypeDefinition decompiledTypeDefinition, IMethod method)
  103. {
  104. if (method == null || !(method.HasGeneratedName() || method.Name.Contains("$")))
  105. return false;
  106. if (!(method.IsCompilerGeneratedOrIsInCompilerGeneratedClass() || IsPotentialClosure(decompiledTypeDefinition, method.DeclaringTypeDefinition)))
  107. return false;
  108. return true;
  109. }
  110. static bool IsPotentialClosure(ITypeDefinition decompiledTypeDefinition, ITypeDefinition potentialDisplayClass)
  111. {
  112. if (potentialDisplayClass == null || !potentialDisplayClass.IsCompilerGeneratedOrIsInCompilerGeneratedClass())
  113. return false;
  114. while (potentialDisplayClass != decompiledTypeDefinition) {
  115. potentialDisplayClass = potentialDisplayClass.DeclaringTypeDefinition;
  116. if (potentialDisplayClass == null)
  117. return false;
  118. }
  119. return true;
  120. }
  121. ILFunction TransformDelegateConstruction(NewObj value, out ILInstruction target)
  122. {
  123. target = null;
  124. if (!IsDelegateConstruction(value))
  125. return null;
  126. var targetMethod = ((IInstructionWithMethodOperand)value.Arguments[1]).Method;
  127. if (IsAnonymousMethod(decompilationContext.CurrentTypeDefinition, targetMethod)) {
  128. target = value.Arguments[0];
  129. var methodDefinition = (Mono.Cecil.MethodDefinition)context.TypeSystem.GetCecil(targetMethod);
  130. var localTypeSystem = context.TypeSystem.GetSpecializingTypeSystem(new SimpleTypeResolveContext(targetMethod));
  131. var ilReader = new ILReader(localTypeSystem);
  132. ilReader.UseDebugSymbols = context.Settings.UseDebugSymbols;
  133. var function = ilReader.ReadIL(methodDefinition.Body, context.CancellationToken);
  134. function.CheckInvariant(ILPhase.Normal);
  135. var contextPrefix = targetMethod.Name;
  136. foreach (ILVariable v in function.Variables.Where(v => v.Kind != VariableKind.Parameter)) {
  137. v.Name = contextPrefix + v.Name;
  138. }
  139. var nestedContext = new ILTransformContext(function, localTypeSystem, context.Settings) {
  140. CancellationToken = context.CancellationToken
  141. };
  142. function.RunTransforms(CSharpDecompiler.GetILTransforms().TakeWhile(t => !(t is DelegateConstruction)), nestedContext);
  143. function.AcceptVisitor(new ReplaceDelegateTargetVisitor(target, function.Variables.SingleOrDefault(v => v.Index == -1 && v.Kind == VariableKind.Parameter)));
  144. // handle nested lambdas
  145. ((IILTransform)new DelegateConstruction()).Run(function, nestedContext);
  146. return function;
  147. }
  148. return null;
  149. }
  150. /// <summary>
  151. /// Replaces loads of 'this' with the target expression.
  152. /// Async delegates use: ldobj(ldloca this).
  153. /// </summary>
  154. class ReplaceDelegateTargetVisitor : ILVisitor
  155. {
  156. readonly ILVariable thisVariable;
  157. readonly ILInstruction target;
  158. public ReplaceDelegateTargetVisitor(ILInstruction target, ILVariable thisVariable)
  159. {
  160. this.target = target;
  161. this.thisVariable = thisVariable;
  162. }
  163. protected override void Default(ILInstruction inst)
  164. {
  165. foreach (var child in inst.Children) {
  166. child.AcceptVisitor(this);
  167. }
  168. }
  169. protected internal override void VisitLdLoc(LdLoc inst)
  170. {
  171. if (inst.Variable == thisVariable) {
  172. inst.ReplaceWith(target.Clone());
  173. return;
  174. }
  175. base.VisitLdLoc(inst);
  176. }
  177. protected internal override void VisitLdObj(LdObj inst)
  178. {
  179. if (inst.Target.MatchLdLoca(thisVariable)) {
  180. inst.ReplaceWith(target.Clone());
  181. return;
  182. }
  183. base.VisitLdObj(inst);
  184. }
  185. }
  186. /// <summary>
  187. /// 1. Stores to display class fields are replaced with stores to local variables (in some
  188. /// cases existing variables are used; otherwise fresh variables are added to the
  189. /// ILFunction-container) and all usages of those fields are replaced with the local variable.
  190. /// (see initValues)
  191. /// 2. Usages of the display class container (or any copy) are removed.
  192. /// </summary>
  193. class TransformDisplayClassUsages : ILVisitor
  194. {
  195. ILFunction currentFunction;
  196. BlockContainer captureScope;
  197. readonly IInstructionWithVariableOperand targetLoad;
  198. readonly List<ILVariable> targetAndCopies = new List<ILVariable>();
  199. readonly List<ILInstruction> orphanedVariableInits;
  200. readonly Dictionary<IField, DisplayClassVariable> initValues = new Dictionary<IField, DisplayClassVariable>();
  201. struct DisplayClassVariable
  202. {
  203. public ILVariable variable;
  204. public ILInstruction value;
  205. }
  206. public TransformDisplayClassUsages(ILFunction function, IInstructionWithVariableOperand targetLoad, BlockContainer captureScope, List<ILInstruction> orphanedVariableInits)
  207. {
  208. this.currentFunction = function;
  209. this.targetLoad = targetLoad;
  210. this.captureScope = captureScope;
  211. this.orphanedVariableInits = orphanedVariableInits;
  212. this.targetAndCopies.Add(targetLoad.Variable);
  213. }
  214. protected override void Default(ILInstruction inst)
  215. {
  216. foreach (var child in inst.Children) {
  217. child.AcceptVisitor(this);
  218. }
  219. }
  220. protected internal override void VisitStLoc(StLoc inst)
  221. {
  222. base.VisitStLoc(inst);
  223. if (inst.Variable == targetLoad.Variable)
  224. orphanedVariableInits.Add(inst);
  225. if (MatchesTargetOrCopyLoad(inst.Value)) {
  226. targetAndCopies.Add(inst.Variable);
  227. orphanedVariableInits.Add(inst);
  228. }
  229. }
  230. bool MatchesTargetOrCopyLoad(ILInstruction inst)
  231. {
  232. return targetAndCopies.Any(v => inst.MatchLdLoc(v));
  233. }
  234. protected internal override void VisitStObj(StObj inst)
  235. {
  236. base.VisitStObj(inst);
  237. ILInstruction target;
  238. IField field;
  239. if (!inst.Target.MatchLdFlda(out target, out field) || !MatchesTargetOrCopyLoad(target))
  240. return;
  241. field = (IField)field.MemberDefinition;
  242. DisplayClassVariable info;
  243. ILInstruction value;
  244. if (initValues.TryGetValue(field, out info)) {
  245. inst.ReplaceWith(new StLoc(info.variable, inst.Value));
  246. } else {
  247. if (inst.Value.MatchLdLoc(out var v) && v.Kind == VariableKind.Parameter && currentFunction == v.Function) {
  248. // special case for parameters: remove copies of parameter values.
  249. orphanedVariableInits.Add(inst);
  250. value = inst.Value;
  251. } else {
  252. v = currentFunction.RegisterVariable(VariableKind.Local, field.Type, field.Name);
  253. v.CaptureScope = captureScope;
  254. inst.ReplaceWith(new StLoc(v, inst.Value));
  255. value = new LdLoc(v);
  256. }
  257. initValues.Add(field, new DisplayClassVariable { value = value, variable = v });
  258. }
  259. }
  260. protected internal override void VisitLdObj(LdObj inst)
  261. {
  262. base.VisitLdObj(inst);
  263. ILInstruction target;
  264. IField field;
  265. if (!inst.Target.MatchLdFlda(out target, out field) || !MatchesTargetOrCopyLoad(target))
  266. return;
  267. DisplayClassVariable info;
  268. if (!initValues.TryGetValue((IField)field.MemberDefinition, out info))
  269. return;
  270. inst.ReplaceWith(info.value.Clone());
  271. }
  272. protected internal override void VisitLdFlda(LdFlda inst)
  273. {
  274. base.VisitLdFlda(inst);
  275. if (inst.Parent is LdObj || inst.Parent is StObj)
  276. return;
  277. if (!MatchesTargetOrCopyLoad(inst.Target))
  278. return;
  279. var field = (IField)inst.Field.MemberDefinition;
  280. DisplayClassVariable info;
  281. if (!initValues.TryGetValue(field, out info)) {
  282. var v = currentFunction.RegisterVariable(VariableKind.Local, field.Type, field.Name);
  283. v.CaptureScope = captureScope;
  284. inst.ReplaceWith(new LdLoca(v));
  285. var value = new LdLoc(v);
  286. initValues.Add(field, new DisplayClassVariable { value = value, variable = v });
  287. } else if (info.value is LdLoc l) {
  288. inst.ReplaceWith(new LdLoca(l.Variable));
  289. } else {
  290. throw new NotImplementedException();
  291. }
  292. }
  293. protected internal override void VisitCompoundAssignmentInstruction(CompoundAssignmentInstruction inst)
  294. {
  295. base.VisitCompoundAssignmentInstruction(inst);
  296. if (inst.Target.MatchLdLoc(out var v)) {
  297. inst.ReplaceWith(new StLoc(v, new BinaryNumericInstruction(inst.Operator, inst.Target, inst.Value, inst.CheckForOverflow, inst.Sign)));
  298. }
  299. }
  300. }
  301. #endregion
  302. }
  303. }