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.

478 lines
20 KiB

  1. // Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
  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.Threading;
  23. using ICSharpCode.Decompiler;
  24. using ICSharpCode.Decompiler.ILAst;
  25. using ICSharpCode.NRefactory.CSharp;
  26. using ICSharpCode.NRefactory.PatternMatching;
  27. using Mono.Cecil;
  28. namespace ICSharpCode.Decompiler.Ast.Transforms
  29. {
  30. /// <summary>
  31. /// Converts "new Action(obj, ldftn(func))" into "new Action(obj.func)".
  32. /// For anonymous methods, creates an AnonymousMethodExpression.
  33. /// Also gets rid of any "Display Classes" left over after inlining an anonymous method.
  34. /// </summary>
  35. public class DelegateConstruction : ContextTrackingVisitor<object>
  36. {
  37. internal sealed class Annotation
  38. {
  39. /// <summary>
  40. /// ldftn or ldvirtftn?
  41. /// </summary>
  42. public readonly bool IsVirtual;
  43. public Annotation(bool isVirtual)
  44. {
  45. this.IsVirtual = isVirtual;
  46. }
  47. }
  48. internal sealed class CapturedVariableAnnotation
  49. {
  50. }
  51. List<string> currentlyUsedVariableNames = new List<string>();
  52. public DelegateConstruction(DecompilerContext context) : base(context)
  53. {
  54. }
  55. public override object VisitObjectCreateExpression(ObjectCreateExpression objectCreateExpression, object data)
  56. {
  57. if (objectCreateExpression.Arguments.Count == 2) {
  58. Expression obj = objectCreateExpression.Arguments.First();
  59. Expression func = objectCreateExpression.Arguments.Last();
  60. Annotation annotation = func.Annotation<Annotation>();
  61. if (annotation != null) {
  62. IdentifierExpression methodIdent = (IdentifierExpression)((InvocationExpression)func).Arguments.Single();
  63. MethodReference method = methodIdent.Annotation<MethodReference>();
  64. if (method != null) {
  65. if (HandleAnonymousMethod(objectCreateExpression, obj, method))
  66. return null;
  67. // Perform the transformation to "new Action(obj.func)".
  68. obj.Remove();
  69. methodIdent.Remove();
  70. if (!annotation.IsVirtual && obj is ThisReferenceExpression) {
  71. // maybe it's getting the pointer of a base method?
  72. if (method.DeclaringType.GetElementType() != context.CurrentType) {
  73. obj = new BaseReferenceExpression();
  74. }
  75. }
  76. if (!annotation.IsVirtual && obj is NullReferenceExpression && !method.HasThis) {
  77. // We're loading a static method.
  78. // However it is possible to load extension methods with an instance, so we compare the number of arguments:
  79. bool isExtensionMethod = false;
  80. TypeReference delegateType = objectCreateExpression.Type.Annotation<TypeReference>();
  81. if (delegateType != null) {
  82. TypeDefinition delegateTypeDef = delegateType.Resolve();
  83. if (delegateTypeDef != null) {
  84. MethodDefinition invokeMethod = delegateTypeDef.Methods.FirstOrDefault(m => m.Name == "Invoke");
  85. if (invokeMethod != null) {
  86. isExtensionMethod = (invokeMethod.Parameters.Count + 1 == method.Parameters.Count);
  87. }
  88. }
  89. }
  90. if (!isExtensionMethod) {
  91. obj = new TypeReferenceExpression { Type = AstBuilder.ConvertType(method.DeclaringType) };
  92. }
  93. }
  94. // now transform the identifier into a member reference
  95. MemberReferenceExpression mre = new MemberReferenceExpression();
  96. mre.Target = obj;
  97. mre.MemberName = methodIdent.Identifier;
  98. methodIdent.TypeArguments.MoveTo(mre.TypeArguments);
  99. mre.AddAnnotation(method);
  100. objectCreateExpression.Arguments.Clear();
  101. objectCreateExpression.Arguments.Add(mre);
  102. return null;
  103. }
  104. }
  105. }
  106. return base.VisitObjectCreateExpression(objectCreateExpression, data);
  107. }
  108. internal static bool IsAnonymousMethod(DecompilerContext context, MethodDefinition method)
  109. {
  110. if (method == null || !(method.Name.StartsWith("<", StringComparison.Ordinal) || method.Name.Contains("$")))
  111. return false;
  112. if (!(method.IsCompilerGenerated() || IsPotentialClosure(context, method.DeclaringType)))
  113. return false;
  114. return true;
  115. }
  116. bool HandleAnonymousMethod(ObjectCreateExpression objectCreateExpression, Expression target, MethodReference methodRef)
  117. {
  118. if (!context.Settings.AnonymousMethods)
  119. return false; // anonymous method decompilation is disabled
  120. if (target != null && !(target is IdentifierExpression || target is ThisReferenceExpression || target is NullReferenceExpression))
  121. return false; // don't copy arbitrary expressions, deal with identifiers only
  122. // Anonymous methods are defined in the same assembly
  123. MethodDefinition method = methodRef.ResolveWithinSameModule();
  124. if (!IsAnonymousMethod(context, method))
  125. return false;
  126. // Create AnonymousMethodExpression and prepare parameters
  127. AnonymousMethodExpression ame = new AnonymousMethodExpression();
  128. ame.CopyAnnotationsFrom(objectCreateExpression); // copy ILRanges etc.
  129. ame.RemoveAnnotations<MethodReference>(); // remove reference to delegate ctor
  130. ame.AddAnnotation(method); // add reference to anonymous method
  131. ame.Parameters.AddRange(AstBuilder.MakeParameters(method, isLambda: true));
  132. ame.HasParameterList = true;
  133. // rename variables so that they don't conflict with the parameters:
  134. foreach (ParameterDeclaration pd in ame.Parameters) {
  135. EnsureVariableNameIsAvailable(objectCreateExpression, pd.Name);
  136. }
  137. // Decompile the anonymous method:
  138. DecompilerContext subContext = context.Clone();
  139. subContext.CurrentMethod = method;
  140. subContext.ReservedVariableNames.AddRange(currentlyUsedVariableNames);
  141. BlockStatement body = AstMethodBodyBuilder.CreateMethodBody(method, subContext, ame.Parameters);
  142. TransformationPipeline.RunTransformationsUntil(body, v => v is DelegateConstruction, subContext);
  143. body.AcceptVisitor(this, null);
  144. bool isLambda = false;
  145. if (ame.Parameters.All(p => p.ParameterModifier == ParameterModifier.None)) {
  146. isLambda = (body.Statements.Count == 1 && body.Statements.Single() is ReturnStatement);
  147. }
  148. // Remove the parameter list from an AnonymousMethodExpression if the original method had no names,
  149. // and the parameters are not used in the method body
  150. if (!isLambda && method.Parameters.All(p => string.IsNullOrEmpty(p.Name))) {
  151. var parameterReferencingIdentifiers =
  152. from ident in body.Descendants.OfType<IdentifierExpression>()
  153. let v = ident.Annotation<ILVariable>()
  154. where v != null && v.IsParameter && method.Parameters.Contains(v.OriginalParameter)
  155. select ident;
  156. if (!parameterReferencingIdentifiers.Any()) {
  157. ame.Parameters.Clear();
  158. ame.HasParameterList = false;
  159. }
  160. }
  161. // Replace all occurrences of 'this' in the method body with the delegate's target:
  162. foreach (AstNode node in body.Descendants) {
  163. if (node is ThisReferenceExpression)
  164. node.ReplaceWith(target.Clone());
  165. }
  166. if (isLambda) {
  167. LambdaExpression lambda = new LambdaExpression();
  168. lambda.CopyAnnotationsFrom(ame);
  169. ame.Parameters.MoveTo(lambda.Parameters);
  170. Expression returnExpr = ((ReturnStatement)body.Statements.Single()).Expression;
  171. returnExpr.Remove();
  172. lambda.Body = returnExpr;
  173. objectCreateExpression.ReplaceWith(lambda);
  174. } else {
  175. ame.Body = body;
  176. objectCreateExpression.ReplaceWith(ame);
  177. }
  178. return true;
  179. }
  180. internal static bool IsPotentialClosure(DecompilerContext context, TypeDefinition potentialDisplayClass)
  181. {
  182. if (potentialDisplayClass == null || !potentialDisplayClass.IsCompilerGeneratedOrIsInCompilerGeneratedClass())
  183. return false;
  184. // check that methodContainingType is within containingType
  185. while (potentialDisplayClass != context.CurrentType) {
  186. potentialDisplayClass = potentialDisplayClass.DeclaringType;
  187. if (potentialDisplayClass == null)
  188. return false;
  189. }
  190. return true;
  191. }
  192. #region Track current variables
  193. public override object VisitMethodDeclaration(MethodDeclaration methodDeclaration, object data)
  194. {
  195. Debug.Assert(currentlyUsedVariableNames.Count == 0);
  196. try {
  197. currentlyUsedVariableNames.AddRange(methodDeclaration.Parameters.Select(p => p.Name));
  198. return base.VisitMethodDeclaration(methodDeclaration, data);
  199. } finally {
  200. currentlyUsedVariableNames.Clear();
  201. }
  202. }
  203. public override object VisitOperatorDeclaration(OperatorDeclaration operatorDeclaration, object data)
  204. {
  205. Debug.Assert(currentlyUsedVariableNames.Count == 0);
  206. try {
  207. currentlyUsedVariableNames.AddRange(operatorDeclaration.Parameters.Select(p => p.Name));
  208. return base.VisitOperatorDeclaration(operatorDeclaration, data);
  209. } finally {
  210. currentlyUsedVariableNames.Clear();
  211. }
  212. }
  213. public override object VisitConstructorDeclaration(ConstructorDeclaration constructorDeclaration, object data)
  214. {
  215. Debug.Assert(currentlyUsedVariableNames.Count == 0);
  216. try {
  217. currentlyUsedVariableNames.AddRange(constructorDeclaration.Parameters.Select(p => p.Name));
  218. return base.VisitConstructorDeclaration(constructorDeclaration, data);
  219. } finally {
  220. currentlyUsedVariableNames.Clear();
  221. }
  222. }
  223. public override object VisitIndexerDeclaration(IndexerDeclaration indexerDeclaration, object data)
  224. {
  225. Debug.Assert(currentlyUsedVariableNames.Count == 0);
  226. try {
  227. currentlyUsedVariableNames.AddRange(indexerDeclaration.Parameters.Select(p => p.Name));
  228. return base.VisitIndexerDeclaration(indexerDeclaration, data);
  229. } finally {
  230. currentlyUsedVariableNames.Clear();
  231. }
  232. }
  233. public override object VisitAccessor(Accessor accessor, object data)
  234. {
  235. try {
  236. currentlyUsedVariableNames.Add("value");
  237. return base.VisitAccessor(accessor, data);
  238. } finally {
  239. currentlyUsedVariableNames.RemoveAt(currentlyUsedVariableNames.Count - 1);
  240. }
  241. }
  242. public override object VisitVariableDeclarationStatement(VariableDeclarationStatement variableDeclarationStatement, object data)
  243. {
  244. foreach (VariableInitializer v in variableDeclarationStatement.Variables)
  245. currentlyUsedVariableNames.Add(v.Name);
  246. return base.VisitVariableDeclarationStatement(variableDeclarationStatement, data);
  247. }
  248. public override object VisitFixedStatement(FixedStatement fixedStatement, object data)
  249. {
  250. foreach (VariableInitializer v in fixedStatement.Variables)
  251. currentlyUsedVariableNames.Add(v.Name);
  252. return base.VisitFixedStatement(fixedStatement, data);
  253. }
  254. #endregion
  255. static readonly ExpressionStatement displayClassAssignmentPattern =
  256. new ExpressionStatement(new AssignmentExpression(
  257. new NamedNode("variable", new IdentifierExpression()),
  258. new ObjectCreateExpression { Type = new AnyNode("type") }
  259. ));
  260. public override object VisitBlockStatement(BlockStatement blockStatement, object data)
  261. {
  262. int numberOfVariablesOutsideBlock = currentlyUsedVariableNames.Count;
  263. base.VisitBlockStatement(blockStatement, data);
  264. foreach (ExpressionStatement stmt in blockStatement.Statements.OfType<ExpressionStatement>().ToArray()) {
  265. Match displayClassAssignmentMatch = displayClassAssignmentPattern.Match(stmt);
  266. if (!displayClassAssignmentMatch.Success)
  267. continue;
  268. ILVariable variable = displayClassAssignmentMatch.Get<AstNode>("variable").Single().Annotation<ILVariable>();
  269. if (variable == null)
  270. continue;
  271. TypeDefinition type = variable.Type.ResolveWithinSameModule();
  272. if (!IsPotentialClosure(context, type))
  273. continue;
  274. if (displayClassAssignmentMatch.Get<AstType>("type").Single().Annotation<TypeReference>().ResolveWithinSameModule() != type)
  275. continue;
  276. // Looks like we found a display class creation. Now let's verify that the variable is used only for field accesses:
  277. bool ok = true;
  278. foreach (var identExpr in blockStatement.Descendants.OfType<IdentifierExpression>()) {
  279. if (identExpr.Identifier == variable.Name && identExpr != displayClassAssignmentMatch.Get("variable").Single()) {
  280. if (!(identExpr.Parent is MemberReferenceExpression && identExpr.Parent.Annotation<FieldReference>() != null))
  281. ok = false;
  282. }
  283. }
  284. if (!ok)
  285. continue;
  286. Dictionary<FieldReference, AstNode> dict = new Dictionary<FieldReference, AstNode>();
  287. // Delete the variable declaration statement:
  288. VariableDeclarationStatement displayClassVarDecl = PatternStatementTransform.FindVariableDeclaration(stmt, variable.Name);
  289. if (displayClassVarDecl != null)
  290. displayClassVarDecl.Remove();
  291. // Delete the assignment statement:
  292. AstNode cur = stmt.NextSibling;
  293. stmt.Remove();
  294. // Delete any following statements as long as they assign parameters to the display class
  295. BlockStatement rootBlock = blockStatement.Ancestors.OfType<BlockStatement>().LastOrDefault() ?? blockStatement;
  296. List<ILVariable> parameterOccurrances = rootBlock.Descendants.OfType<IdentifierExpression>()
  297. .Select(n => n.Annotation<ILVariable>()).Where(p => p != null && p.IsParameter).ToList();
  298. AstNode next;
  299. for (; cur != null; cur = next) {
  300. next = cur.NextSibling;
  301. // Test for the pattern:
  302. // "variableName.MemberName = right;"
  303. ExpressionStatement closureFieldAssignmentPattern = new ExpressionStatement(
  304. new AssignmentExpression(
  305. new NamedNode("left", new MemberReferenceExpression { Target = new IdentifierExpression(variable.Name) }),
  306. new AnyNode("right")
  307. )
  308. );
  309. Match m = closureFieldAssignmentPattern.Match(cur);
  310. if (m.Success) {
  311. FieldDefinition fieldDef = m.Get<MemberReferenceExpression>("left").Single().Annotation<FieldReference>().ResolveWithinSameModule();
  312. AstNode right = m.Get<AstNode>("right").Single();
  313. bool isParameter = false;
  314. bool isDisplayClassParentPointerAssignment = false;
  315. if (right is ThisReferenceExpression) {
  316. isParameter = true;
  317. } else if (right is IdentifierExpression) {
  318. // handle parameters only if the whole method contains no other occurrence except for 'right'
  319. ILVariable v = right.Annotation<ILVariable>();
  320. isParameter = v.IsParameter && parameterOccurrances.Count(c => c == v) == 1;
  321. if (!isParameter && IsPotentialClosure(context, v.Type.ResolveWithinSameModule())) {
  322. // parent display class within the same method
  323. // (closure2.localsX = closure1;)
  324. isDisplayClassParentPointerAssignment = true;
  325. }
  326. } else if (right is MemberReferenceExpression) {
  327. // copy of parent display class reference from an outer lambda
  328. // closure2.localsX = this.localsY
  329. MemberReferenceExpression mre = m.Get<MemberReferenceExpression>("right").Single();
  330. do {
  331. // descend into the targets of the mre as long as the field types are closures
  332. FieldDefinition fieldDef2 = mre.Annotation<FieldReference>().ResolveWithinSameModule();
  333. if (fieldDef2 == null || !IsPotentialClosure(context, fieldDef2.FieldType.ResolveWithinSameModule())) {
  334. break;
  335. }
  336. // if we finally get to a this reference, it's copying a display class parent pointer
  337. if (mre.Target is ThisReferenceExpression) {
  338. isDisplayClassParentPointerAssignment = true;
  339. }
  340. mre = mre.Target as MemberReferenceExpression;
  341. } while (mre != null);
  342. }
  343. if (isParameter || isDisplayClassParentPointerAssignment) {
  344. dict[fieldDef] = right;
  345. cur.Remove();
  346. } else {
  347. break;
  348. }
  349. } else {
  350. break;
  351. }
  352. }
  353. // Now create variables for all fields of the display class (except for those that we already handled as parameters)
  354. List<Tuple<AstType, ILVariable>> variablesToDeclare = new List<Tuple<AstType, ILVariable>>();
  355. foreach (FieldDefinition field in type.Fields) {
  356. if (field.IsStatic)
  357. continue; // skip static fields
  358. if (dict.ContainsKey(field)) // skip field if it already was handled as parameter
  359. continue;
  360. string capturedVariableName = field.Name;
  361. if (capturedVariableName.StartsWith("$VB$Local_", StringComparison.Ordinal) && capturedVariableName.Length > 10)
  362. capturedVariableName = capturedVariableName.Substring(10);
  363. EnsureVariableNameIsAvailable(blockStatement, capturedVariableName);
  364. currentlyUsedVariableNames.Add(capturedVariableName);
  365. ILVariable ilVar = new ILVariable
  366. {
  367. IsGenerated = true,
  368. Name = capturedVariableName,
  369. Type = field.FieldType,
  370. };
  371. variablesToDeclare.Add(Tuple.Create(AstBuilder.ConvertType(field.FieldType, field), ilVar));
  372. dict[field] = new IdentifierExpression(capturedVariableName).WithAnnotation(ilVar);
  373. }
  374. // Now figure out where the closure was accessed and use the simpler replacement expression there:
  375. foreach (var identExpr in blockStatement.Descendants.OfType<IdentifierExpression>()) {
  376. if (identExpr.Identifier == variable.Name) {
  377. MemberReferenceExpression mre = (MemberReferenceExpression)identExpr.Parent;
  378. AstNode replacement;
  379. if (dict.TryGetValue(mre.Annotation<FieldReference>().ResolveWithinSameModule(), out replacement)) {
  380. mre.ReplaceWith(replacement.Clone());
  381. }
  382. }
  383. }
  384. // Now insert the variable declarations (we can do this after the replacements only so that the scope detection works):
  385. Statement insertionPoint = blockStatement.Statements.FirstOrDefault();
  386. foreach (var tuple in variablesToDeclare) {
  387. var newVarDecl = new VariableDeclarationStatement(tuple.Item1, tuple.Item2.Name);
  388. newVarDecl.Variables.Single().AddAnnotation(new CapturedVariableAnnotation());
  389. newVarDecl.Variables.Single().AddAnnotation(tuple.Item2);
  390. blockStatement.Statements.InsertBefore(insertionPoint, newVarDecl);
  391. }
  392. }
  393. currentlyUsedVariableNames.RemoveRange(numberOfVariablesOutsideBlock, currentlyUsedVariableNames.Count - numberOfVariablesOutsideBlock);
  394. return null;
  395. }
  396. void EnsureVariableNameIsAvailable(AstNode currentNode, string name)
  397. {
  398. int pos = currentlyUsedVariableNames.IndexOf(name);
  399. if (pos < 0) {
  400. // name is still available
  401. return;
  402. }
  403. // Naming conflict. Let's rename the existing variable so that the field keeps the name from metadata.
  404. NameVariables nv = new NameVariables();
  405. // Add currently used variable and parameter names
  406. foreach (string nameInUse in currentlyUsedVariableNames)
  407. nv.AddExistingName(nameInUse);
  408. // variables declared in child nodes of this block
  409. foreach (VariableInitializer vi in currentNode.Descendants.OfType<VariableInitializer>())
  410. nv.AddExistingName(vi.Name);
  411. // parameters in child lambdas
  412. foreach (ParameterDeclaration pd in currentNode.Descendants.OfType<ParameterDeclaration>())
  413. nv.AddExistingName(pd.Name);
  414. string newName = nv.GetAlternativeName(name);
  415. currentlyUsedVariableNames[pos] = newName;
  416. // find top-most block
  417. AstNode topMostBlock = currentNode.Ancestors.OfType<BlockStatement>().LastOrDefault() ?? currentNode;
  418. // rename identifiers
  419. foreach (IdentifierExpression ident in topMostBlock.Descendants.OfType<IdentifierExpression>()) {
  420. if (ident.Identifier == name) {
  421. ident.Identifier = newName;
  422. ILVariable v = ident.Annotation<ILVariable>();
  423. if (v != null)
  424. v.Name = newName;
  425. }
  426. }
  427. // rename variable declarations
  428. foreach (VariableInitializer vi in topMostBlock.Descendants.OfType<VariableInitializer>()) {
  429. if (vi.Name == name) {
  430. vi.Name = newName;
  431. ILVariable v = vi.Annotation<ILVariable>();
  432. if (v != null)
  433. v.Name = newName;
  434. }
  435. }
  436. }
  437. }
  438. }