summaryrefslogtreecommitdiff
path: root/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs
diff options
context:
space:
mode:
Diffstat (limited to 'ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs')
-rw-r--r--ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs502
1 files changed, 502 insertions, 0 deletions
diff --git a/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs b/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs
new file mode 100644
index 00000000..04b2293d
--- /dev/null
+++ b/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs
@@ -0,0 +1,502 @@
+// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using ICSharpCode.Decompiler;
+using ICSharpCode.Decompiler.ILAst;
+using ICSharpCode.NRefactory.CSharp;
+using ICSharpCode.NRefactory.PatternMatching;
+using Mono.Cecil;
+
+namespace ICSharpCode.Decompiler.Ast.Transforms
+{
+ /// <summary>
+ /// 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.
+ /// </summary>
+ public class DelegateConstruction : ContextTrackingVisitor<object>
+ {
+ internal sealed class Annotation
+ {
+ /// <summary>
+ /// ldftn or ldvirtftn?
+ /// </summary>
+ public readonly bool IsVirtual;
+
+ public Annotation(bool isVirtual)
+ {
+ IsVirtual = isVirtual;
+ }
+ }
+
+ internal sealed class CapturedVariableAnnotation
+ {
+ }
+
+ List<string> currentlyUsedVariableNames = new List<string>();
+
+ 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<Annotation>();
+ if (annotation != null) {
+ IdentifierExpression methodIdent = (IdentifierExpression)((InvocationExpression)func).Arguments.Single();
+ MethodReference method = methodIdent.Annotation<MethodReference>();
+ 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.GetElementType() != 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<TypeReference>();
+ 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.HasGeneratedName() || method.Name.Contains("$")))
+ return false;
+ if (!(method.IsCompilerGenerated() || IsPotentialClosure(context, method.DeclaringType)))
+ return false;
+ return true;
+ }
+
+ bool HandleAnonymousMethod(ObjectCreateExpression objectCreateExpression, Expression target, MethodReference methodRef)
+ {
+ if (!context.Settings.AnonymousMethods)
+ return false; // anonymous method decompilation is disabled
+ if (target != null && !(target is IdentifierExpression || target is ThisReferenceExpression || target is NullReferenceExpression))
+ return false; // don't copy arbitrary expressions, deal with identifiers only
+
+ // Anonymous methods are defined in the same assembly
+ MethodDefinition method = methodRef.ResolveWithinSameModule();
+ if (!IsAnonymousMethod(context, method))
+ return false;
+
+ // Create AnonymousMethodExpression and prepare parameters
+ AnonymousMethodExpression ame = new AnonymousMethodExpression();
+ ame.CopyAnnotationsFrom(objectCreateExpression); // copy ILRanges etc.
+ ame.RemoveAnnotations<MethodReference>(); // remove reference to delegate ctor
+ ame.AddAnnotation(method); // add reference to anonymous method
+ ame.Parameters.AddRange(AstBuilder.MakeParameters(method, isLambda: true));
+ ame.HasParameterList = true;
+
+ // rename variables so that they don't conflict with the parameters:
+ foreach (ParameterDeclaration pd in ame.Parameters) {
+ EnsureVariableNameIsAvailable(objectCreateExpression, pd.Name);
+ }
+
+ // Decompile the anonymous method:
+
+ DecompilerContext subContext = context.Clone();
+ subContext.CurrentMethod = method;
+ subContext.CurrentMethodIsAsync = false;
+ subContext.ReservedVariableNames.AddRange(currentlyUsedVariableNames);
+ BlockStatement body = AstMethodBodyBuilder.CreateMethodBody(method, subContext, ame.Parameters);
+ TransformationPipeline.RunTransformationsUntil(body, v => v is DelegateConstruction, subContext);
+ body.AcceptVisitor(this, null);
+
+
+ bool isLambda = false;
+ if (ame.Parameters.All(p => p.ParameterModifier == ParameterModifier.None)) {
+ isLambda = (body.Statements.Count == 1 && body.Statements.Single() is ReturnStatement);
+ }
+ // Remove the parameter list from an AnonymousMethodExpression if the original method had no names,
+ // and the parameters are not used in the method body
+ if (!isLambda && method.Parameters.All(p => string.IsNullOrEmpty(p.Name))) {
+ var parameterReferencingIdentifiers =
+ from ident in body.Descendants.OfType<IdentifierExpression>()
+ let v = ident.Annotation<ILVariable>()
+ where v != null && v.IsParameter && method.Parameters.Contains(v.OriginalParameter)
+ select ident;
+ if (!parameterReferencingIdentifiers.Any()) {
+ ame.Parameters.Clear();
+ ame.HasParameterList = false;
+ }
+ }
+
+ // 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());
+ }
+ Expression replacement;
+ if (isLambda) {
+ LambdaExpression lambda = new LambdaExpression();
+ lambda.CopyAnnotationsFrom(ame);
+ ame.Parameters.MoveTo(lambda.Parameters);
+ Expression returnExpr = ((ReturnStatement)body.Statements.Single()).Expression;
+ returnExpr.Remove();
+ lambda.Body = returnExpr;
+ replacement = lambda;
+ } else {
+ ame.Body = body;
+ replacement = ame;
+ }
+ var expectedType = objectCreateExpression.Annotation<TypeInformation>().ExpectedType.Resolve();
+ if (expectedType != null && !expectedType.IsDelegate()) {
+ var simplifiedDelegateCreation = (ObjectCreateExpression)objectCreateExpression.Clone();
+ simplifiedDelegateCreation.Arguments.Clear();
+ simplifiedDelegateCreation.Arguments.Add(replacement);
+ replacement = simplifiedDelegateCreation;
+ }
+ objectCreateExpression.ReplaceWith(replacement);
+ return true;
+ }
+
+ internal static bool IsPotentialClosure(DecompilerContext context, TypeDefinition potentialDisplayClass)
+ {
+ if (potentialDisplayClass == null || !potentialDisplayClass.IsCompilerGeneratedOrIsInCompilerGeneratedClass())
+ 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 VisitInvocationExpression(InvocationExpression invocationExpression, object data)
+ {
+ if (context.Settings.ExpressionTrees && ExpressionTreeConverter.CouldBeExpressionTree(invocationExpression)) {
+ Expression converted = ExpressionTreeConverter.TryConvert(context, invocationExpression);
+ if (converted != null) {
+ invocationExpression.ReplaceWith(converted);
+ return converted.AcceptVisitor(this, data);
+ }
+ }
+ return base.VisitInvocationExpression(invocationExpression, data);
+ }
+
+ #region Track current variables
+ public override object VisitMethodDeclaration(MethodDeclaration methodDeclaration, object data)
+ {
+ Debug.Assert(currentlyUsedVariableNames.Count == 0);
+ try {
+ currentlyUsedVariableNames.AddRange(methodDeclaration.Parameters.Select(p => p.Name));
+ return base.VisitMethodDeclaration(methodDeclaration, data);
+ } finally {
+ currentlyUsedVariableNames.Clear();
+ }
+ }
+
+ public override object VisitOperatorDeclaration(OperatorDeclaration operatorDeclaration, object data)
+ {
+ Debug.Assert(currentlyUsedVariableNames.Count == 0);
+ try {
+ currentlyUsedVariableNames.AddRange(operatorDeclaration.Parameters.Select(p => p.Name));
+ return base.VisitOperatorDeclaration(operatorDeclaration, data);
+ } finally {
+ currentlyUsedVariableNames.Clear();
+ }
+ }
+
+ public override object VisitConstructorDeclaration(ConstructorDeclaration constructorDeclaration, object data)
+ {
+ Debug.Assert(currentlyUsedVariableNames.Count == 0);
+ try {
+ currentlyUsedVariableNames.AddRange(constructorDeclaration.Parameters.Select(p => p.Name));
+ return base.VisitConstructorDeclaration(constructorDeclaration, data);
+ } finally {
+ currentlyUsedVariableNames.Clear();
+ }
+ }
+
+ public override object VisitIndexerDeclaration(IndexerDeclaration indexerDeclaration, object data)
+ {
+ Debug.Assert(currentlyUsedVariableNames.Count == 0);
+ try {
+ currentlyUsedVariableNames.AddRange(indexerDeclaration.Parameters.Select(p => p.Name));
+ return base.VisitIndexerDeclaration(indexerDeclaration, data);
+ } finally {
+ currentlyUsedVariableNames.Clear();
+ }
+ }
+
+ public override object VisitAccessor(Accessor accessor, object data)
+ {
+ try {
+ currentlyUsedVariableNames.Add("value");
+ return base.VisitAccessor(accessor, data);
+ } finally {
+ currentlyUsedVariableNames.RemoveAt(currentlyUsedVariableNames.Count - 1);
+ }
+ }
+
+ public override object VisitVariableDeclarationStatement(VariableDeclarationStatement variableDeclarationStatement, object data)
+ {
+ foreach (VariableInitializer v in variableDeclarationStatement.Variables)
+ currentlyUsedVariableNames.Add(v.Name);
+ return base.VisitVariableDeclarationStatement(variableDeclarationStatement, data);
+ }
+
+ public override object VisitFixedStatement(FixedStatement fixedStatement, object data)
+ {
+ foreach (VariableInitializer v in fixedStatement.Variables)
+ currentlyUsedVariableNames.Add(v.Name);
+ return base.VisitFixedStatement(fixedStatement, data);
+ }
+ #endregion
+
+ static readonly ExpressionStatement displayClassAssignmentPattern =
+ new ExpressionStatement(new AssignmentExpression(
+ new NamedNode("variable", new IdentifierExpression(Pattern.AnyString)),
+ new ObjectCreateExpression { Type = new AnyNode("type") }
+ ));
+
+ public override object VisitBlockStatement(BlockStatement blockStatement, object data)
+ {
+ int numberOfVariablesOutsideBlock = currentlyUsedVariableNames.Count;
+ base.VisitBlockStatement(blockStatement, data);
+ foreach (ExpressionStatement stmt in blockStatement.Statements.OfType<ExpressionStatement>().ToArray()) {
+ Match displayClassAssignmentMatch = displayClassAssignmentPattern.Match(stmt);
+ if (!displayClassAssignmentMatch.Success)
+ continue;
+
+ ILVariable variable = displayClassAssignmentMatch.Get<AstNode>("variable").Single().Annotation<ILVariable>();
+ if (variable == null)
+ continue;
+ TypeDefinition type = variable.Type.ResolveWithinSameModule();
+ if (!IsPotentialClosure(context, type))
+ continue;
+ if (displayClassAssignmentMatch.Get<AstType>("type").Single().Annotation<TypeReference>().ResolveWithinSameModule() != type)
+ 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<IdentifierExpression>()) {
+ if (identExpr.Identifier == variable.Name && identExpr != displayClassAssignmentMatch.Get("variable").Single()) {
+ if (!(identExpr.Parent is MemberReferenceExpression && identExpr.Parent.Annotation<FieldReference>() != null))
+ ok = false;
+ }
+ }
+ if (!ok)
+ continue;
+ Dictionary<FieldReference, AstNode> dict = new Dictionary<FieldReference, AstNode>();
+
+ // Delete the variable declaration statement:
+ VariableDeclarationStatement displayClassVarDecl = PatternStatementTransform.FindVariableDeclaration(stmt, variable.Name);
+ if (displayClassVarDecl != null)
+ displayClassVarDecl.Remove();
+
+ // Delete the assignment statement:
+ AstNode cur = stmt.NextSibling;
+ stmt.Remove();
+
+ // Delete any following statements as long as they assign parameters to the display class
+ BlockStatement rootBlock = blockStatement.Ancestors.OfType<BlockStatement>().LastOrDefault() ?? blockStatement;
+ List<ILVariable> parameterOccurrances = rootBlock.Descendants.OfType<IdentifierExpression>()
+ .Select(n => n.Annotation<ILVariable>()).Where(p => p != null && p.IsParameter).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),
+ MemberName = Pattern.AnyString
+ }),
+ new AnyNode("right")
+ )
+ );
+ Match m = closureFieldAssignmentPattern.Match(cur);
+ if (m.Success) {
+ FieldDefinition fieldDef = m.Get<MemberReferenceExpression>("left").Single().Annotation<FieldReference>().ResolveWithinSameModule();
+ AstNode right = m.Get<AstNode>("right").Single();
+ bool isParameter = false;
+ bool isDisplayClassParentPointerAssignment = false;
+ if (right is ThisReferenceExpression) {
+ isParameter = true;
+ } else if (right is IdentifierExpression) {
+ // handle parameters only if the whole method contains no other occurrence except for 'right'
+ ILVariable v = right.Annotation<ILVariable>();
+ isParameter = v.IsParameter && parameterOccurrances.Count(c => c == v) == 1;
+ if (!isParameter && IsPotentialClosure(context, v.Type.ResolveWithinSameModule())) {
+ // parent display class within the same method
+ // (closure2.localsX = closure1;)
+ isDisplayClassParentPointerAssignment = true;
+ }
+ } else if (right is MemberReferenceExpression) {
+ // copy of parent display class reference from an outer lambda
+ // closure2.localsX = this.localsY
+ MemberReferenceExpression mre = m.Get<MemberReferenceExpression>("right").Single();
+ do {
+ // descend into the targets of the mre as long as the field types are closures
+ FieldDefinition fieldDef2 = mre.Annotation<FieldReference>().ResolveWithinSameModule();
+ if (fieldDef2 == null || !IsPotentialClosure(context, fieldDef2.FieldType.ResolveWithinSameModule())) {
+ break;
+ }
+ // if we finally get to a this reference, it's copying a display class parent pointer
+ if (mre.Target is ThisReferenceExpression) {
+ isDisplayClassParentPointerAssignment = true;
+ }
+ mre = mre.Target as MemberReferenceExpression;
+ } while (mre != null);
+ }
+ if (isParameter || isDisplayClassParentPointerAssignment) {
+ dict[fieldDef] = 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<Tuple<AstType, ILVariable>> variablesToDeclare = new List<Tuple<AstType, ILVariable>>();
+ foreach (FieldDefinition field in type.Fields) {
+ if (field.IsStatic)
+ continue; // skip static fields
+ if (dict.ContainsKey(field)) // skip field if it already was handled as parameter
+ continue;
+ string capturedVariableName = field.Name;
+ if (capturedVariableName.StartsWith("$VB$Local_", StringComparison.Ordinal) && capturedVariableName.Length > 10)
+ capturedVariableName = capturedVariableName.Substring(10);
+ EnsureVariableNameIsAvailable(blockStatement, capturedVariableName);
+ currentlyUsedVariableNames.Add(capturedVariableName);
+ ILVariable ilVar = new ILVariable
+ {
+ IsGenerated = true,
+ Name = capturedVariableName,
+ Type = field.FieldType,
+ };
+ variablesToDeclare.Add(Tuple.Create(AstBuilder.ConvertType(field.FieldType, field), ilVar));
+ dict[field] = new IdentifierExpression(capturedVariableName).WithAnnotation(ilVar);
+ }
+
+ // Now figure out where the closure was accessed and use the simpler replacement expression there:
+ foreach (var identExpr in blockStatement.Descendants.OfType<IdentifierExpression>()) {
+ if (identExpr.Identifier == variable.Name) {
+ MemberReferenceExpression mre = (MemberReferenceExpression)identExpr.Parent;
+ AstNode replacement;
+ if (dict.TryGetValue(mre.Annotation<FieldReference>().ResolveWithinSameModule(), 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):
+ Statement insertionPoint = blockStatement.Statements.FirstOrDefault();
+ foreach (var tuple in variablesToDeclare) {
+ var newVarDecl = new VariableDeclarationStatement(tuple.Item1, tuple.Item2.Name);
+ newVarDecl.Variables.Single().AddAnnotation(new CapturedVariableAnnotation());
+ newVarDecl.Variables.Single().AddAnnotation(tuple.Item2);
+ blockStatement.Statements.InsertBefore(insertionPoint, newVarDecl);
+ }
+ }
+ currentlyUsedVariableNames.RemoveRange(numberOfVariablesOutsideBlock, currentlyUsedVariableNames.Count - numberOfVariablesOutsideBlock);
+ return null;
+ }
+
+ void EnsureVariableNameIsAvailable(AstNode currentNode, string name)
+ {
+ int pos = currentlyUsedVariableNames.IndexOf(name);
+ if (pos < 0) {
+ // name is still available
+ return;
+ }
+ // Naming conflict. Let's rename the existing variable so that the field keeps the name from metadata.
+ NameVariables nv = new NameVariables();
+ // Add currently used variable and parameter names
+ foreach (string nameInUse in currentlyUsedVariableNames)
+ nv.AddExistingName(nameInUse);
+ // variables declared in child nodes of this block
+ foreach (VariableInitializer vi in currentNode.Descendants.OfType<VariableInitializer>())
+ nv.AddExistingName(vi.Name);
+ // parameters in child lambdas
+ foreach (ParameterDeclaration pd in currentNode.Descendants.OfType<ParameterDeclaration>())
+ nv.AddExistingName(pd.Name);
+
+ string newName = nv.GetAlternativeName(name);
+ currentlyUsedVariableNames[pos] = newName;
+
+ // find top-most block
+ AstNode topMostBlock = currentNode.Ancestors.OfType<BlockStatement>().LastOrDefault() ?? currentNode;
+
+ // rename identifiers
+ foreach (IdentifierExpression ident in topMostBlock.Descendants.OfType<IdentifierExpression>()) {
+ if (ident.Identifier == name) {
+ ident.Identifier = newName;
+ ILVariable v = ident.Annotation<ILVariable>();
+ if (v != null)
+ v.Name = newName;
+ }
+ }
+ // rename variable declarations
+ foreach (VariableInitializer vi in topMostBlock.Descendants.OfType<VariableInitializer>()) {
+ if (vi.Name == name) {
+ vi.Name = newName;
+ ILVariable v = vi.Annotation<ILVariable>();
+ if (v != null)
+ v.Name = newName;
+ }
+ }
+ }
+ }
+}