// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) // This code is distributed under MIT X11 license (for details please see \doc\license.txt) using System; using System.Collections.Generic; using System.Linq; using System.Threading; using ICSharpCode.Decompiler; using ICSharpCode.NRefactory.CSharp; using ICSharpCode.NRefactory.CSharp.PatternMatching; using Mono.Cecil; namespace ICSharpCode.Decompiler.Ast.Transforms { /// /// Converts "new Action(obj, ldftn(func))" into "new Action(obj.func)". /// For anonymous methods, creates an AnonymousMethodExpression. /// Also gets rid of any "Display Classes" left over after inlining an anonymous method. /// public class DelegateConstruction : ContextTrackingVisitor { internal sealed class Annotation { /// /// ldftn or ldvirtftn? /// public readonly bool IsVirtual; public Annotation(bool isVirtual) { this.IsVirtual = isVirtual; } } internal sealed class CapturedVariableAnnotation { } public DelegateConstruction(DecompilerContext context) : base(context) { } public override object VisitObjectCreateExpression(ObjectCreateExpression objectCreateExpression, object data) { if (objectCreateExpression.Arguments.Count() == 2) { Expression obj = objectCreateExpression.Arguments.First(); Expression func = objectCreateExpression.Arguments.Last(); Annotation annotation = func.Annotation(); if (annotation != null) { IdentifierExpression methodIdent = (IdentifierExpression)((InvocationExpression)func).Arguments.Single(); MethodReference method = methodIdent.Annotation(); if (method != null) { if (HandleAnonymousMethod(objectCreateExpression, obj, method)) return null; // Perform the transformation to "new Action(obj.func)". obj.Remove(); methodIdent.Remove(); if (!annotation.IsVirtual && obj is ThisReferenceExpression) { // maybe it's getting the pointer of a base method? if (method.DeclaringType != context.CurrentType) { obj = new BaseReferenceExpression(); } } if (!annotation.IsVirtual && obj is NullReferenceExpression && !method.HasThis) { // We're loading a static method. // However it is possible to load extension methods with an instance, so we compare the number of arguments: bool isExtensionMethod = false; TypeReference delegateType = objectCreateExpression.Type.Annotation(); if (delegateType != null) { TypeDefinition delegateTypeDef = delegateType.Resolve(); if (delegateTypeDef != null) { MethodDefinition invokeMethod = delegateTypeDef.Methods.FirstOrDefault(m => m.Name == "Invoke"); if (invokeMethod != null) { isExtensionMethod = (invokeMethod.Parameters.Count + 1 == method.Parameters.Count); } } } if (!isExtensionMethod) { obj = new TypeReferenceExpression { Type = AstBuilder.ConvertType(method.DeclaringType) }; } } // now transform the identifier into a member reference MemberReferenceExpression mre = new MemberReferenceExpression(); mre.Target = obj; mre.MemberName = methodIdent.Identifier; methodIdent.TypeArguments.MoveTo(mre.TypeArguments); mre.AddAnnotation(method); objectCreateExpression.Arguments.Clear(); objectCreateExpression.Arguments.Add(mre); return null; } } } return base.VisitObjectCreateExpression(objectCreateExpression, data); } internal static bool IsAnonymousMethod(DecompilerContext context, MethodDefinition method) { if (method == null || !method.Name.StartsWith("<", StringComparison.Ordinal)) return false; if (!(method.IsCompilerGenerated() || IsPotentialClosure(context, method.DeclaringType))) return false; return true; } bool HandleAnonymousMethod(ObjectCreateExpression objectCreateExpression, Expression target, MethodReference methodRef) { // Anonymous methods are defined in the same assembly, so there's no need to Resolve(). MethodDefinition method = methodRef as MethodDefinition; if (!IsAnonymousMethod(context, method)) return false; // Decompile the anonymous method: DecompilerContext subContext = context.Clone(); subContext.CurrentMethod = method; BlockStatement body = AstMethodBodyBuilder.CreateMethodBody(method, subContext); TransformationPipeline.RunTransformationsUntil(body, v => v is DelegateConstruction, subContext); body.AcceptVisitor(this, null); AnonymousMethodExpression ame = new AnonymousMethodExpression(); bool isLambda = false; if (method.Parameters.All(p => string.IsNullOrEmpty(p.Name))) { ame.HasParameterList = false; } else { ame.HasParameterList = true; ame.Parameters.AddRange(AstBuilder.MakeParameters(method.Parameters)); if (ame.Parameters.All(p => p.ParameterModifier == ParameterModifier.None)) { isLambda = (body.Statements.Count == 1 && body.Statements.Single() is ReturnStatement); } } // Replace all occurrences of 'this' in the method body with the delegate's target: foreach (AstNode node in body.Descendants) { if (node is ThisReferenceExpression) node.ReplaceWith(target.Clone()); } if (isLambda) { LambdaExpression lambda = new LambdaExpression(); ame.Parameters.MoveTo(lambda.Parameters); Expression returnExpr = ((ReturnStatement)body.Statements.Single()).Expression; returnExpr.Remove(); lambda.Body = returnExpr; objectCreateExpression.ReplaceWith(lambda); } else { ame.Body = body; objectCreateExpression.ReplaceWith(ame); } return true; } static bool IsPotentialClosure(DecompilerContext context, TypeDefinition potentialDisplayClass) { if (potentialDisplayClass == null || !potentialDisplayClass.IsCompilerGenerated()) return false; // check that methodContainingType is within containingType while (potentialDisplayClass != context.CurrentType) { potentialDisplayClass = potentialDisplayClass.DeclaringType; if (potentialDisplayClass == null) return false; } return true; } public override object VisitBlockStatement(BlockStatement blockStatement, object data) { base.VisitBlockStatement(blockStatement, data); foreach (VariableDeclarationStatement stmt in blockStatement.Statements.OfType()) { if (stmt.Variables.Count() != 1) continue; var variable = stmt.Variables.Single(); TypeDefinition type = stmt.Type.Annotation(); if (!IsPotentialClosure(context, type)) continue; ObjectCreateExpression oce = variable.Initializer as ObjectCreateExpression; if (oce == null || oce.Type.Annotation() != type || oce.Arguments.Any() || !oce.Initializer.IsNull) continue; // Looks like we found a display class creation. Now let's verify that the variable is used only for field accesses: bool ok = true; foreach (var identExpr in blockStatement.Descendants.OfType()) { if (identExpr.Identifier == variable.Name) { if (!(identExpr.Parent is MemberReferenceExpression && identExpr.Parent.Annotation() != null)) ok = false; } } if (!ok) continue; Dictionary dict = new Dictionary(); // Delete the variable declaration statement: AstNode cur = stmt.NextSibling; stmt.Remove(); if (blockStatement.Parent.NodeType == NodeType.Member || blockStatement.Parent is Accessor) { // Delete any following statements as long as they assign parameters to the display class // Do parameter handling only for closures created in the top scope (direct child of method/accessor) List parameterOccurrances = blockStatement.Descendants.OfType() .Select(n => n.Annotation()).Where(p => p != null).ToList(); AstNode next; for (; cur != null; cur = next) { next = cur.NextSibling; // Test for the pattern: // "variableName.MemberName = right;" ExpressionStatement closureFieldAssignmentPattern = new ExpressionStatement( new AssignmentExpression( new NamedNode("left", new MemberReferenceExpression { Target = new IdentifierExpression(variable.Name) }), new AnyNode("right") ) ); Match m = closureFieldAssignmentPattern.Match(cur); if (m != null) { AstNode right = m.Get("right").Single(); bool isParameter = false; if (right is ThisReferenceExpression) { isParameter = true; } else if (right is IdentifierExpression) { // handle parameters only if the whole method contains no other occurrance except for 'right' ParameterReference param = right.Annotation(); isParameter = parameterOccurrances.Count(c => c == param) == 1; } if (isParameter) { dict[m.Get("left").Single().Annotation()] = right; cur.Remove(); } else { break; } } else { break; } } } // Now create variables for all fields of the display class (except for those that we already handled as parameters) List> variablesToDeclare = new List>(); foreach (FieldDefinition field in type.Fields) { if (dict.ContainsKey(field)) continue; variablesToDeclare.Add(Tuple.Create(AstBuilder.ConvertType(field.FieldType, field), field.Name)); dict[field] = new IdentifierExpression(field.Name); } // Now figure out where the closure was accessed and use the simpler replacement expression there: foreach (var identExpr in blockStatement.Descendants.OfType()) { if (identExpr.Identifier == variable.Name) { MemberReferenceExpression mre = (MemberReferenceExpression)identExpr.Parent; AstNode replacement; if (dict.TryGetValue(mre.Annotation(), out replacement)) { mre.ReplaceWith(replacement.Clone()); } } } // Now insert the variable declarations (we can do this after the replacements only so that the scope detection works): foreach (var tuple in variablesToDeclare) { var newVarDecl = DeclareVariableInSmallestScope.DeclareVariable(blockStatement, tuple.Item1, tuple.Item2, allowPassIntoLoops: false); if (newVarDecl != null) newVarDecl.Variables.Single().AddAnnotation(new CapturedVariableAnnotation()); } } return null; } } }