// 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 Mono.Cecil; namespace Decompiler.Transforms { /// /// Converts "new Action(obj, ldftn(func))" into "new Action(obj.func)". /// For anonymous methods, creates an AnonymousMethodExpression. /// public class DelegateConstruction : ContextTrackingVisitor { internal sealed class Annotation { /// /// ldftn or ldvirtftn? /// public readonly bool IsVirtual; public Annotation(bool isVirtual) { this.IsVirtual = isVirtual; } } 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 var typeArguments = methodIdent.TypeArguments.ToArray(); methodIdent.TypeArguments = null; MemberReferenceExpression mre = new MemberReferenceExpression { Target = obj, MemberName = methodIdent.Identifier, TypeArguments = typeArguments }; mre.AddAnnotation(method); objectCreateExpression.Arguments = new [] { mre }; return null; } } } return base.VisitObjectCreateExpression(objectCreateExpression, data); } 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 (method == null || !method.Name.StartsWith("<", StringComparison.Ordinal)) return false; if (!(method.IsCompilerGenerated() || IsPotentialClosure(method.DeclaringType))) 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(); if (method.Parameters.All(p => string.IsNullOrEmpty(p.Name))) { ame.HasParameterList = false; } else { ame.HasParameterList = true; ame.Parameters = AstBuilder.MakeParameters(method.Parameters); } ame.Body = body; // 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()); } objectCreateExpression.ReplaceWith(ame); return true; } bool IsPotentialClosure(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(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; AstNode next = stmt.NextSibling; stmt.Remove(); for (cur = next; cur != null; cur = next) { next = cur.NextSibling; // Delete any following statements as long as they assign simple variables to the display class: // Test for the pattern: // "variableName.MemberName = right;" ExpressionStatement es = cur as ExpressionStatement; if (es == null) break; AssignmentExpression ae = es.Expression as AssignmentExpression; if (ae == null || ae.Operator != AssignmentOperatorType.Assign) break; MemberReferenceExpression left = ae.Left as MemberReferenceExpression; if (left == null || !IsParameter(ae.Right)) break; if (!(left.Target is IdentifierExpression) || (left.Target as IdentifierExpression).Identifier != variable.Name) break; dict[left.MemberName] = ae.Right; es.Remove(); } // Now create variables for all fields of the display class (except for those that we already handled) foreach (FieldDefinition field in type.Fields) { if (dict.ContainsKey(field.Name)) continue; VariableDeclarationStatement newVarDecl = new VariableDeclarationStatement(); newVarDecl.Type = AstBuilder.ConvertType(field.FieldType, field); newVarDecl.Variables = new [] { new VariableInitializer(field.Name) }; blockStatement.InsertChildBefore(cur, newVarDecl, BlockStatement.StatementRole); dict[field.Name] = 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; Expression replacement; if (dict.TryGetValue(mre.MemberName, out replacement)) { mre.ReplaceWith(replacement.Clone()); } } } } return null; } bool IsParameter(Expression expr) { if (expr is ThisReferenceExpression) return true; IdentifierExpression ident = expr as IdentifierExpression; return ident != null && ident.Annotation() != null; } } }