From bd77b8301f6dacc7a028a9516b66fb3afd7ae801 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Mon, 24 Jun 2019 17:51:13 +0200 Subject: [PATCH 01/23] Basic implementation of local functions. --- .../CSharp/CSharpDecompiler.cs | 3 +- .../OutputVisitor/CSharpOutputVisitor.cs | 19 ++++ .../CSharp/StatementBuilder.cs | 11 ++ .../CSharp/Syntax/DepthFirstAstVisitor.cs | 21 +++- .../CSharp/Syntax/IAstVisitor.cs | 3 + .../LocalFunctionDeclarationStatement.cs | 94 ++++++++++++++++ .../CSharp/Transforms/DeclareVariables.cs | 2 +- ICSharpCode.Decompiler/DecompilerSettings.cs | 2 +- .../ICSharpCode.Decompiler.csproj | 1 + .../IL/ControlFlow/YieldReturnDecompiler.cs | 2 +- ICSharpCode.Decompiler/IL/ILReader.cs | 4 +- .../IL/Instructions/Block.cs | 15 +++ .../IL/Instructions/ILFunction.cs | 64 ++++++++++- .../IL/Transforms/DelegateConstruction.cs | 8 +- .../IL/Transforms/LocalFunctionDecompiler.cs | 103 +++++++++++++++++- .../IL/Transforms/ProxyCallReplacer.cs | 2 +- .../IL/Transforms/TransformExpressionTrees.cs | 7 +- .../Rewrite/ConnectionIdRewritePass.cs | 2 +- ILSpy/Languages/ILAstLanguage.cs | 2 +- 19 files changed, 343 insertions(+), 22 deletions(-) create mode 100644 ICSharpCode.Decompiler/CSharp/Syntax/Statements/LocalFunctionDeclarationStatement.cs diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 5863da05e..891763b90 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -156,6 +156,7 @@ namespace ICSharpCode.Decompiler.CSharp }, new ProxyCallReplacer(), new DelegateConstruction(), + new LocalFunctionDecompiler(), new HighLevelLoopTransform(), new ReduceNestingTransform(), new IntroduceDynamicTypeOnLocals(), @@ -395,7 +396,7 @@ namespace ICSharpCode.Decompiler.CSharp return new DecompilerTypeSystem(file, resolver); } - TypeSystemAstBuilder CreateAstBuilder(ITypeResolveContext decompilationContext) + internal static TypeSystemAstBuilder CreateAstBuilder(ITypeResolveContext decompilationContext) { var typeSystemAstBuilder = new TypeSystemAstBuilder(); typeSystemAstBuilder.ShowAttributes = true; diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs index d3bef7f73..038c44455 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -1883,6 +1883,25 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor Semicolon(); EndNode(variableDeclarationStatement); } + + public virtual void VisitLocalFunctionDeclarationStatement(LocalFunctionDeclarationStatement localFunctionDeclarationStatement) + { + StartNode(localFunctionDeclarationStatement); + + WriteModifiers(localFunctionDeclarationStatement.ModifierTokens); + localFunctionDeclarationStatement.ReturnType.AcceptVisitor(this); + Space(); + WriteIdentifier(localFunctionDeclarationStatement.NameToken); + WriteTypeParameters(localFunctionDeclarationStatement.TypeParameters); + Space(policy.SpaceBeforeMethodDeclarationParentheses); + WriteCommaSeparatedListInParenthesis(localFunctionDeclarationStatement.Parameters, policy.SpaceWithinMethodDeclarationParentheses); + foreach (Constraint constraint in localFunctionDeclarationStatement.Constraints) { + constraint.AcceptVisitor(this); + } + WriteMethodBody(localFunctionDeclarationStatement.Body, policy.MethodBraceStyle); + + EndNode(localFunctionDeclarationStatement); + } public virtual void VisitWhileStatement(WhileStatement whileStatement) { diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs index a799c9cf4..70f58b7eb 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -1015,5 +1015,16 @@ namespace ICSharpCode.Decompiler.CSharp stmt.InsertChildAfter(null, new Comment(" IL cpblk instruction"), Roles.Comment); return stmt; } + + protected internal override Statement VisitILFunction(ILFunction function) + { + var stmt = new LocalFunctionDeclarationStatement(); + var tsab = CSharpDecompiler.CreateAstBuilder(null); + stmt.Name = function.Method.Name; + stmt.Parameters.AddRange(function.Method.Parameters.Select(tsab.ConvertParameter)); + stmt.ReturnType = tsab.ConvertType(function.Method.ReturnType); + stmt.Body = ConvertAsBlock(function.Body); + return stmt; + } } } diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs b/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs index 44add8be6..3b3a0b216 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs @@ -390,7 +390,12 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax { VisitChildren (variableDeclarationStatement); } - + + public virtual void VisitLocalFunctionDeclarationStatement(LocalFunctionDeclarationStatement localFunctionDeclarationStatement) + { + VisitChildren(localFunctionDeclarationStatement); + } + public virtual void VisitWhileStatement (WhileStatement whileStatement) { VisitChildren (whileStatement); @@ -1037,7 +1042,12 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax { return VisitChildren (variableDeclarationStatement); } - + + public virtual T VisitLocalFunctionDeclarationStatement(LocalFunctionDeclarationStatement localFunctionDeclarationStatement) + { + return VisitChildren(localFunctionDeclarationStatement); + } + public virtual T VisitWhileStatement (WhileStatement whileStatement) { return VisitChildren (whileStatement); @@ -1684,7 +1694,12 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax { return VisitChildren (variableDeclarationStatement, data); } - + + public virtual S VisitLocalFunctionDeclarationStatement(LocalFunctionDeclarationStatement localFunctionDeclarationStatement, T data) + { + return VisitChildren(localFunctionDeclarationStatement, data); + } + public virtual S VisitWhileStatement (WhileStatement whileStatement, T data) { return VisitChildren (whileStatement, data); diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs b/ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs index 2d5bd8760..9ed151865 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs @@ -110,6 +110,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax void VisitUnsafeStatement(UnsafeStatement unsafeStatement); void VisitUsingStatement(UsingStatement usingStatement); void VisitVariableDeclarationStatement(VariableDeclarationStatement variableDeclarationStatement); + void VisitLocalFunctionDeclarationStatement(LocalFunctionDeclarationStatement localFunctionDeclarationStatement); void VisitWhileStatement(WhileStatement whileStatement); void VisitYieldBreakStatement(YieldBreakStatement yieldBreakStatement); void VisitYieldReturnStatement(YieldReturnStatement yieldReturnStatement); @@ -251,6 +252,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax S VisitUnsafeStatement(UnsafeStatement unsafeStatement); S VisitUsingStatement(UsingStatement usingStatement); S VisitVariableDeclarationStatement(VariableDeclarationStatement variableDeclarationStatement); + S VisitLocalFunctionDeclarationStatement(LocalFunctionDeclarationStatement localFunctionDeclarationStatement); S VisitWhileStatement(WhileStatement whileStatement); S VisitYieldBreakStatement(YieldBreakStatement yieldBreakStatement); S VisitYieldReturnStatement(YieldReturnStatement yieldReturnStatement); @@ -392,6 +394,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax S VisitUnsafeStatement(UnsafeStatement unsafeStatement, T data); S VisitUsingStatement(UsingStatement usingStatement, T data); S VisitVariableDeclarationStatement(VariableDeclarationStatement variableDeclarationStatement, T data); + S VisitLocalFunctionDeclarationStatement(LocalFunctionDeclarationStatement localFunctionDeclarationStatement, T data); S VisitWhileStatement(WhileStatement whileStatement, T data); S VisitYieldBreakStatement(YieldBreakStatement yieldBreakStatement, T data); S VisitYieldReturnStatement(YieldReturnStatement yieldReturnStatement, T data); diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Statements/LocalFunctionDeclarationStatement.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Statements/LocalFunctionDeclarationStatement.cs new file mode 100644 index 000000000..581b53bfa --- /dev/null +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Statements/LocalFunctionDeclarationStatement.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Text; +using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching; + +namespace ICSharpCode.Decompiler.CSharp.Syntax +{ + public class LocalFunctionDeclarationStatement : Statement + { + public AstNodeCollection TypeParameters { + get { return GetChildrenByRole(Roles.TypeParameter); } + } + + public CSharpTokenNode LParToken { + get { return GetChildByRole(Roles.LPar); } + } + + public AstNodeCollection Parameters { + get { return GetChildrenByRole(Roles.Parameter); } + } + + public CSharpTokenNode RParToken { + get { return GetChildByRole(Roles.RPar); } + } + + public AstNodeCollection Constraints { + get { return GetChildrenByRole(Roles.Constraint); } + } + + public BlockStatement Body { + get { return GetChildByRole(Roles.Body); } + set { SetChildByRole(Roles.Body, value); } + } + + public Modifiers Modifiers { + get { return EntityDeclaration.GetModifiers(this); } + set { EntityDeclaration.SetModifiers(this, value); } + } + + public bool HasModifier(Modifiers mod) + { + return (Modifiers & mod) == mod; + } + + public IEnumerable ModifierTokens { + get { return GetChildrenByRole(EntityDeclaration.ModifierRole); } + } + + public virtual string Name { + get { + return GetChildByRole(Roles.Identifier).Name; + } + set { + SetChildByRole(Roles.Identifier, Identifier.Create(value, TextLocation.Empty)); + } + } + + public virtual Identifier NameToken { + get { return GetChildByRole(Roles.Identifier); } + set { SetChildByRole(Roles.Identifier, value); } + } + + public virtual AstType ReturnType { + get { return GetChildByRole(Roles.Type); } + set { SetChildByRole(Roles.Type, value); } + } + + public override void AcceptVisitor(IAstVisitor visitor) + { + visitor.VisitLocalFunctionDeclarationStatement(this); + } + + public override T AcceptVisitor(IAstVisitor visitor) + { + return visitor.VisitLocalFunctionDeclarationStatement(this); + } + + public override S AcceptVisitor(IAstVisitor visitor, T data) + { + return visitor.VisitLocalFunctionDeclarationStatement(this, data); + } + + protected internal override bool DoMatch(AstNode other, Match match) + { + LocalFunctionDeclarationStatement o = other as LocalFunctionDeclarationStatement; + return o != null && MatchString(this.Name, o.Name) + && (this.Modifiers == Modifiers.Any || this.Modifiers == o.Modifiers) + && this.ReturnType.DoMatch(o.ReturnType, match) + && this.TypeParameters.DoMatch(o.TypeParameters, match) + && this.Parameters.DoMatch(o.Parameters, match) && this.Constraints.DoMatch(o.Constraints, match) + && this.Body.DoMatch(o.Body, match); + } + } +} diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs index 4b2051cc9..f9cb5633e 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs @@ -260,7 +260,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } else { newPoint = new InsertionPoint { level = nodeLevel, nextNode = identExpr }; if (variable.HasInitialValue) { - // Uninitialized variables are logically initialized at the beginning of the functin + // Uninitialized variables are logically initialized at the beginning of the function // Because it's possible that the variable has a loop-carried dependency, // declare it outside of any loops. while (startIndex >= 0) { diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index 163d58346..f6e7a94dd 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -942,7 +942,7 @@ namespace ICSharpCode.Decompiler } } - bool localFunctions = false; + bool localFunctions = true; /// /// Gets/Sets whether C# 7.0 local functions should be used. diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 8ab2fbd69..5a8d439e2 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -161,6 +161,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs index fc6f6dcc0..c4db8d89a 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs @@ -390,7 +390,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow methodTypeParameters: null); var body = context.TypeSystem.MainModule.PEFile.Reader.GetMethodBody(methodDef.RelativeVirtualAddress); var il = context.CreateILReader() - .ReadIL(method, body, genericContext, context.CancellationToken); + .ReadIL(method, body, genericContext, context.Function.Kind, context.CancellationToken); il.RunTransforms(CSharpDecompiler.EarlyILTransforms(true), new ILTransformContext(il, context.TypeSystem, context.DebugInfo, context.Settings) { CancellationToken = context.CancellationToken, diff --git a/ICSharpCode.Decompiler/IL/ILReader.cs b/ICSharpCode.Decompiler/IL/ILReader.cs index 4d98af2fd..2c3c8b654 100644 --- a/ICSharpCode.Decompiler/IL/ILReader.cs +++ b/ICSharpCode.Decompiler/IL/ILReader.cs @@ -480,14 +480,14 @@ namespace ICSharpCode.Decompiler.IL /// /// Decodes the specified method body and returns an ILFunction. /// - public ILFunction ReadIL(MethodDefinitionHandle method, MethodBodyBlock body, GenericContext genericContext = default, CancellationToken cancellationToken = default) + public ILFunction ReadIL(MethodDefinitionHandle method, MethodBodyBlock body, GenericContext genericContext = default, ILFunctionKind kind = ILFunctionKind.TopLevelFunction, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); Init(method, body, genericContext); ReadInstructions(cancellationToken); var blockBuilder = new BlockBuilder(body, variableByExceptionHandler); blockBuilder.CreateBlocks(mainContainer, instructionBuilder, isBranchTarget, cancellationToken); - var function = new ILFunction(this.method, body.GetCodeSize(), this.genericContext, mainContainer); + var function = new ILFunction(this.method, body.GetCodeSize(), this.genericContext, mainContainer, kind); CollectionExtensions.AddRange(function.Variables, parameterVariables); CollectionExtensions.AddRange(function.Variables, localVariables); CollectionExtensions.AddRange(function.Variables, stackVariables); diff --git a/ICSharpCode.Decompiler/IL/Instructions/Block.cs b/ICSharpCode.Decompiler/IL/Instructions/Block.cs index ab04f550e..157c37b79 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/Block.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/Block.cs @@ -280,6 +280,21 @@ namespace ICSharpCode.Decompiler.IL return inst; } + /// + /// Gets the closest parent Block. + /// Returns null, if the instruction is not a descendant of a Block. + /// + public static Block FindClosestBlock(ILInstruction inst) + { + var curr = inst; + while (curr != null) { + if (curr is Block) + return (Block)curr; + curr = curr.Parent; + } + return null; + } + public bool MatchInlineAssignBlock(out CallInstruction call, out ILInstruction value) { call = null; diff --git a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs index f77106260..cb4acd62c 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs @@ -99,13 +99,22 @@ namespace ICSharpCode.Decompiler.IL /// public IType DelegateType; - public bool IsExpressionTree => DelegateType != null && DelegateType.FullName == "System.Linq.Expressions.Expression" && DelegateType.TypeParameterCount == 1; + ILFunctionKind kind; + + public ILFunctionKind Kind { + get => kind; + set { + if (kind == ILFunctionKind.TopLevelFunction || kind == ILFunctionKind.LocalFunction) + throw new InvalidOperationException("ILFunction.Kind of a top-level or local function may not be changed."); + kind = value; + } + } public readonly IType ReturnType; public readonly IReadOnlyList Parameters; - public ILFunction(IMethod method, int codeSize, GenericContext genericContext, ILInstruction body) : base(OpCode.ILFunction) + public ILFunction(IMethod method, int codeSize, GenericContext genericContext, ILInstruction body, ILFunctionKind kind = ILFunctionKind.TopLevelFunction) : base(OpCode.ILFunction) { this.Method = method; this.CodeSize = codeSize; @@ -114,6 +123,7 @@ namespace ICSharpCode.Decompiler.IL this.ReturnType = Method?.ReturnType; this.Parameters = Method?.Parameters; this.Variables = new ILVariableCollection(this); + this.kind = kind; } public ILFunction(IType returnType, IReadOnlyList parameters, GenericContext genericContext, ILInstruction body) : base(OpCode.ILFunction) @@ -123,10 +133,31 @@ namespace ICSharpCode.Decompiler.IL this.ReturnType = returnType; this.Parameters = parameters; this.Variables = new ILVariableCollection(this); + this.kind = ILFunctionKind.ExpressionTree; } internal override void CheckInvariant(ILPhase phase) { + switch (kind) { + case ILFunctionKind.TopLevelFunction: + Debug.Assert(Parent == null); + Debug.Assert(DelegateType == null); + break; + case ILFunctionKind.Delegate: + Debug.Assert(Parent != null && !(Parent is Block)); + Debug.Assert(DelegateType != null); + Debug.Assert(!(DelegateType?.FullName == "System.Linq.Expressions.Expression" && DelegateType.TypeParameterCount == 1)); + break; + case ILFunctionKind.ExpressionTree: + Debug.Assert(Parent != null && !(Parent is Block)); + Debug.Assert(DelegateType != null); + Debug.Assert(DelegateType?.FullName == "System.Linq.Expressions.Expression" && DelegateType.TypeParameterCount == 1); + break; + case ILFunctionKind.LocalFunction: + Debug.Assert(Parent is Block); + Debug.Assert(DelegateType == null); + break; + } for (int i = 0; i < Variables.Count; i++) { Debug.Assert(Variables[i].Function == this); Debug.Assert(Variables[i].IndexInFunction == i); @@ -148,8 +179,13 @@ namespace ICSharpCode.Decompiler.IL output.Write(' '); Method.WriteTo(output); } - if (IsExpressionTree) { - output.Write(".ET"); + switch (kind) { + case ILFunctionKind.ExpressionTree: + output.Write(".ET"); + break; + case ILFunctionKind.LocalFunction: + output.Write(".local"); + break; } if (DelegateType != null) { output.Write("["); @@ -290,4 +326,24 @@ namespace ICSharpCode.Decompiler.IL Debug.Assert(ok); } } + + public enum ILFunctionKind + { + /// + /// ILFunction is a "top-level" function, i.e., method, accessor, constructor, destructor or operator. + /// + TopLevelFunction, + /// + /// ILFunction is a delegate or lambda expression. + /// + Delegate, + /// + /// ILFunction is an expression tree lambda. + /// + ExpressionTree, + /// + /// ILFunction is a C# 7.0 local function. + /// + LocalFunction + } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs b/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs index aa4148c33..c39caba78 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs @@ -175,11 +175,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms var targetMethod = ((IInstructionWithMethodOperand)value.Arguments[1]).Method; if (!IsAnonymousMethod(decompilationContext.CurrentTypeDefinition, targetMethod)) return null; - if (LocalFunctionDecompiler.IsLocalFunctionMethod(targetMethod.ParentModule.PEFile, (MethodDefinitionHandle)targetMethod.MetadataToken)) - return null; - target = value.Arguments[0]; if (targetMethod.MetadataToken.IsNil) return null; + if (LocalFunctionDecompiler.IsLocalFunctionMethod(targetMethod)) + return null; + target = value.Arguments[0]; var methodDefinition = context.PEFile.Metadata.GetMethodDefinition((MethodDefinitionHandle)targetMethod.MetadataToken); if (!methodDefinition.HasBody()) return null; @@ -188,7 +188,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return null; var ilReader = context.CreateILReader(); var body = context.PEFile.Reader.GetMethodBody(methodDefinition.RelativeVirtualAddress); - var function = ilReader.ReadIL((MethodDefinitionHandle)targetMethod.MetadataToken, body, genericContext.Value, context.CancellationToken); + var function = ilReader.ReadIL((MethodDefinitionHandle)targetMethod.MetadataToken, body, genericContext.Value, ILFunctionKind.Delegate, context.CancellationToken); function.DelegateType = value.Method.DeclaringType; function.CheckInvariant(ILPhase.Normal); // Embed the lambda into the parent function's ILAst, so that "Show steps" can show diff --git a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs index b54adc273..cd38b463c 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs @@ -1,20 +1,121 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using System.Reflection; using System.Reflection.Metadata; using System.Text; using System.Text.RegularExpressions; +using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.Metadata; +using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.Util; namespace ICSharpCode.Decompiler.IL.Transforms { class LocalFunctionDecompiler : IILTransform { + ILTransformContext context; + ITypeResolveContext decompilationContext; + public void Run(ILFunction function, ILTransformContext context) { - throw new NotImplementedException(); + if (!context.Settings.LocalFunctions) + return; + this.context = context; + this.decompilationContext = new SimpleTypeResolveContext(function.Method); + var localFunctions = new Dictionary>(); + var cancellationToken = context.CancellationToken; + // Find use-sites + foreach (var inst in function.Descendants) { + cancellationToken.ThrowIfCancellationRequested(); + if (inst is Call call && IsLocalFunctionMethod(call.Method)) { + context.StepStartGroup($"LocalFunctionDecompiler {call.StartILOffset}", call); + if (!localFunctions.TryGetValue(call.Method, out var info)) { + info = new List() { call }; + localFunctions.Add(call.Method, info); + } else { + info.Add(call); + } + context.StepEndGroup(); + } + } + + foreach (var (method, useSites) in localFunctions) { + var insertionPoint = FindInsertionPoint(useSites); + if (TransformLocalFunction(method, (Block)insertionPoint.Parent, insertionPoint.ChildIndex + 1) == null) + continue; + } + } + + static ILInstruction FindInsertionPoint(List useSites) + { + ILInstruction insertionPoint = null; + foreach (var call in useSites) { + if (insertionPoint == null) { + insertionPoint = GetStatement(call); + continue; + } + + var ancestor = FindCommonAncestorInstruction(insertionPoint, GetStatement(call)); + + if (ancestor == null) + return null; + + insertionPoint = ancestor; + } + + switch (insertionPoint) { + case BlockContainer bc: + return insertionPoint; + case Block b: + return insertionPoint; + default: + return insertionPoint; + } + } + + static ILInstruction FindCommonAncestorInstruction(ILInstruction a, ILInstruction b) + { + var ancestorsOfB = new HashSet(b.Ancestors); + return a.Ancestors.FirstOrDefault(ancestorsOfB.Contains); + } + + static ILInstruction GetStatement(ILInstruction inst) + { + while (inst.Parent != null) { + if (inst.Parent is Block) + return inst; + inst = inst.Parent; + } + return inst; + } + + private ILFunction TransformLocalFunction(IMethod targetMethod, Block parent, int insertionPoint) + { + var methodDefinition = context.PEFile.Metadata.GetMethodDefinition((MethodDefinitionHandle)targetMethod.MetadataToken); + if (!methodDefinition.HasBody()) + return null; + var genericContext = DelegateConstruction.GenericContextFromTypeArguments(targetMethod.Substitution); + if (genericContext == null) + return null; + var ilReader = context.CreateILReader(); + var body = context.PEFile.Reader.GetMethodBody(methodDefinition.RelativeVirtualAddress); + var function = ilReader.ReadIL((MethodDefinitionHandle)targetMethod.MetadataToken, body, genericContext.Value, ILFunctionKind.LocalFunction, context.CancellationToken); + // Embed the local function into the parent function's ILAst, so that "Show steps" can show + // how the local function body is being transformed. + parent.Instructions.Insert(insertionPoint, function); + function.CheckInvariant(ILPhase.Normal); + + var nestedContext = new ILTransformContext(context, function); + function.RunTransforms(CSharpDecompiler.GetILTransforms().TakeWhile(t => !(t is LocalFunctionDecompiler)), nestedContext); + + return function; + } + + public static bool IsLocalFunctionMethod(IMethod method) + { + return IsLocalFunctionMethod(method.ParentModule.PEFile, (MethodDefinitionHandle)method.MetadataToken); } public static bool IsLocalFunctionMethod(PEFile module, MethodDefinitionHandle methodHandle) diff --git a/ICSharpCode.Decompiler/IL/Transforms/ProxyCallReplacer.cs b/ICSharpCode.Decompiler/IL/Transforms/ProxyCallReplacer.cs index 6b5383150..d9279f1bb 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ProxyCallReplacer.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ProxyCallReplacer.cs @@ -36,7 +36,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms // partially copied from CSharpDecompiler var ilReader = context.CreateILReader(); var body = context.PEFile.Reader.GetMethodBody(methodDef.RelativeVirtualAddress); - var proxyFunction = ilReader.ReadIL(handle, body, genericContext.Value, context.CancellationToken); + var proxyFunction = ilReader.ReadIL(handle, body, genericContext.Value, ILFunctionKind.TopLevelFunction, context.CancellationToken); var transformContext = new ILTransformContext(context, proxyFunction); proxyFunction.RunTransforms(CSharp.CSharpDecompiler.EarlyILTransforms(), transformContext); if (!(proxyFunction.Body is BlockContainer blockContainer)) diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs index 89c92bb69..8713dc87c 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs @@ -197,6 +197,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms void SetExpressionTreeFlag(ILFunction lambda, CallInstruction call) { + lambda.Kind = IsExpressionTree(call.Method.ReturnType) ? ILFunctionKind.ExpressionTree : ILFunctionKind.Delegate; lambda.DelegateType = call.Method.ReturnType; } @@ -341,7 +342,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms } return (null, SpecialType.UnknownType); case ILFunction function: - if (function.IsExpressionTree) { + if (function.Kind == ILFunctionKind.ExpressionTree) { function.DelegateType = UnwrapExpressionTree(function.DelegateType); } return (function, function.DelegateType); @@ -372,6 +373,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms } } + bool IsExpressionTree(IType delegateType) => delegateType is ParameterizedType pt + && pt.FullName == "System.Linq.Expressions.Expression" + && pt.TypeArguments.Count == 1; + IType UnwrapExpressionTree(IType delegateType) { if (delegateType is ParameterizedType pt && pt.FullName == "System.Linq.Expressions.Expression" && pt.TypeArguments.Count == 1) { diff --git a/ILSpy.BamlDecompiler/Rewrite/ConnectionIdRewritePass.cs b/ILSpy.BamlDecompiler/Rewrite/ConnectionIdRewritePass.cs index 82ab77302..d4875c0af 100644 --- a/ILSpy.BamlDecompiler/Rewrite/ConnectionIdRewritePass.cs +++ b/ILSpy.BamlDecompiler/Rewrite/ConnectionIdRewritePass.cs @@ -94,7 +94,7 @@ namespace ILSpy.BamlDecompiler.Rewrite // decompile method and optimize the switch var ilReader = new ILReader(ctx.TypeSystem.MainModule); - var function = ilReader.ReadIL((MethodDefinitionHandle)method.MetadataToken, body, genericContext, ctx.CancellationToken); + var function = ilReader.ReadIL((MethodDefinitionHandle)method.MetadataToken, body, genericContext, ILFunctionKind.TopLevelFunction, ctx.CancellationToken); var context = new ILTransformContext(function, ctx.TypeSystem, null) { CancellationToken = ctx.CancellationToken diff --git a/ILSpy/Languages/ILAstLanguage.cs b/ILSpy/Languages/ILAstLanguage.cs index fa278e490..3d0fbdc8a 100644 --- a/ILSpy/Languages/ILAstLanguage.cs +++ b/ILSpy/Languages/ILAstLanguage.cs @@ -119,7 +119,7 @@ namespace ICSharpCode.ILSpy var reader = new ILReader(typeSystem.MainModule); reader.UseDebugSymbols = options.DecompilerSettings.UseDebugSymbols; var methodBody = module.Reader.GetMethodBody(methodDef.RelativeVirtualAddress); - ILFunction il = reader.ReadIL((SRM.MethodDefinitionHandle)method.MetadataToken, methodBody, cancellationToken: options.CancellationToken); + ILFunction il = reader.ReadIL((SRM.MethodDefinitionHandle)method.MetadataToken, methodBody, kind: ILFunctionKind.TopLevelFunction, cancellationToken: options.CancellationToken); var namespaces = new HashSet(); var decompiler = new CSharpDecompiler(typeSystem, options.DecompilerSettings) { CancellationToken = options.CancellationToken }; ILTransformContext context = decompiler.CreateILTransformContext(il); From 8d1522f387e0b6363aacf593b349e9d54cc97bc4 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 13 Jul 2019 20:29:30 +0200 Subject: [PATCH 02/23] Transform display classes used in local functions. --- ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 66 +++++++++---- .../CSharp/ExpressionBuilder.cs | 45 +++++++-- .../Resolver/CSharpInvocationResolveResult.cs | 9 +- .../LocalFunctionReferenceResolveResult.cs | 37 +++++++ .../CSharp/StatementBuilder.cs | 11 ++- .../LocalFunctionDeclarationStatement.cs | 20 +++- .../Transforms/IntroduceExtensionMethods.cs | 2 +- .../ICSharpCode.Decompiler.csproj | 1 + .../IL/Instructions/ILFunction.cs | 2 + .../IL/Transforms/AssignVariableNames.cs | 14 +++ .../IL/Transforms/DelegateConstruction.cs | 2 +- .../IL/Transforms/LocalFunctionDecompiler.cs | 36 ++++++- .../Transforms/TransformDisplayClassUsage.cs | 96 ++++++++++++++----- .../Output/TextTokenWriter.cs | 10 +- .../TypeSystem/DecompilerTypeSystem.cs | 4 + ICSharpCode.Decompiler/TypeSystem/IMethod.cs | 2 +- 16 files changed, 293 insertions(+), 64 deletions(-) create mode 100644 ICSharpCode.Decompiler/CSharp/Resolver/LocalFunctionReferenceResolveResult.cs diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index 61494fceb..7ec5ee19d 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -38,6 +38,7 @@ namespace ICSharpCode.Decompiler.CSharp { public OpCode CallOpCode; public bool NeedsBoxingConversion; + public bool IsLocalFunction; } struct ArgumentList @@ -52,13 +53,23 @@ namespace ICSharpCode.Decompiler.CSharp public bool AddNamesToPrimitiveValues; public bool IsExpandedForm; + public int LocalFunctionParameterCount; public int Length => Arguments.Length; + private int GetActualArgumentCount() + { + if (LocalFunctionParameterCount < 0 && FirstOptionalArgumentIndex < 0) + return Arguments.Length; + if (LocalFunctionParameterCount < 0) + return FirstOptionalArgumentIndex; + if (FirstOptionalArgumentIndex < 0) + return LocalFunctionParameterCount; + return Math.Min(FirstOptionalArgumentIndex, LocalFunctionParameterCount); + } + public IEnumerable GetArgumentResolveResults(int skipCount = 0) { - return FirstOptionalArgumentIndex < 0 - ? Arguments.Skip(skipCount).Select(a => a.ResolveResult) - : Arguments.Skip(skipCount).Take(FirstOptionalArgumentIndex).Select(a => a.ResolveResult); + return Arguments.Skip(skipCount).Take(GetActualArgumentCount()).Select(a => a.ResolveResult); } public IEnumerable GetArgumentExpressions(int skipCount = 0) @@ -77,22 +88,12 @@ namespace ICSharpCode.Decompiler.CSharp } } } + int argumentCount = GetActualArgumentCount(); if (ArgumentNames == null) { - if (FirstOptionalArgumentIndex < 0) - return Arguments.Skip(skipCount).Select(arg => arg.Expression); - return Arguments.Skip(skipCount).Take(FirstOptionalArgumentIndex).Select(arg => arg.Expression); + return Arguments.Skip(skipCount).Take(argumentCount).Select(arg => arg.Expression); } else { Debug.Assert(skipCount == 0); - if (FirstOptionalArgumentIndex < 0) { - return Arguments.Zip(ArgumentNames, - (arg, name) => { - if (name == null) - return arg.Expression; - else - return new NamedArgumentExpression(name, arg); - }); - } - return Arguments.Take(FirstOptionalArgumentIndex).Zip(ArgumentNames.Take(FirstOptionalArgumentIndex), + return Arguments.Take(argumentCount).Zip(ArgumentNames.Take(argumentCount), (arg, name) => { if (name == null) return arg.Expression; @@ -179,11 +180,17 @@ namespace ICSharpCode.Decompiler.CSharp { // Used for Call, CallVirt and NewObj var expectedTargetDetails = new ExpectedTargetDetails { - CallOpCode = callOpCode + CallOpCode = callOpCode, + IsLocalFunction = expressionBuilder.IsLocalFunction(method) }; TranslatedExpression target; if (callOpCode == OpCode.NewObj) { target = default(TranslatedExpression); // no target + } else if (expectedTargetDetails.IsLocalFunction) { + var localFunction = expressionBuilder.ResolveLocalFunction(method); + target = new IdentifierExpression(localFunction.Name) + .WithoutILInstruction() + .WithRR(new LocalFunctionReferenceResolveResult(localFunction.Definition)); } else { target = expressionBuilder.TranslateTarget( callArguments.FirstOrDefault(), @@ -210,6 +217,22 @@ namespace ICSharpCode.Decompiler.CSharp var argumentList = BuildArgumentList(expectedTargetDetails, target.ResolveResult, method, firstParamIndex, callArguments, argumentToParameterMap); + + if (expectedTargetDetails.IsLocalFunction) { + int parameterCount = 0; + foreach (var param in method.Parameters) { + if (param.IsRef && param.Type is ByReferenceType byRef) { + var type = byRef.ElementType.GetDefinition(); + if (type != null && type.IsCompilerGenerated()) + break; + } + parameterCount++; + } + argumentList.LocalFunctionParameterCount = parameterCount; + return new InvocationExpression(target, argumentList.GetArgumentExpressions()) + .WithRR(new CSharpInvocationResolveResult(target.ResolveResult, method, + argumentList.GetArgumentResolveResults().ToList(), isExpandedForm: argumentList.IsExpandedForm, isLocalFunctionInvocation: true)); + } if (method is VarArgInstanceMethod) { argumentList.FirstOptionalArgumentIndex = -1; @@ -566,10 +589,11 @@ namespace ICSharpCode.Decompiler.CSharp } - private ArgumentList BuildArgumentList(ExpectedTargetDetails expectedTargetDetails, ResolveResult target, IMethod method, int firstParamIndex, - IReadOnlyList callArguments, IReadOnlyList argumentToParameterMap) + private ArgumentList BuildArgumentList(ExpectedTargetDetails expectedTargetDetails, ResolveResult target, IMethod method, + int firstParamIndex, IReadOnlyList callArguments, IReadOnlyList argumentToParameterMap) { ArgumentList list = new ArgumentList(); + list.LocalFunctionParameterCount = -1; // Translate arguments to the expected parameter types var arguments = new List(method.Parameters.Count); @@ -748,7 +772,9 @@ namespace ICSharpCode.Decompiler.CSharp if (expressionBuilder.HidesVariableWithName(method.Name)) { requireTarget = true; } else { - if (method.IsStatic) + if (expectedTargetDetails.IsLocalFunction) + requireTarget = false; + else if (method.IsStatic) requireTarget = !expressionBuilder.IsCurrentOrContainingType(method.DeclaringTypeDefinition) || method.Name == ".cctor"; else if (method.Name == ".ctor") requireTarget = true; // always use target for base/this-ctor-call, the constructor initializer pattern depends on this diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 1cfb75ecd..688475cdd 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -68,7 +68,7 @@ namespace ICSharpCode.Decompiler.CSharp class ExpressionBuilder : ILVisitor { readonly IDecompilerTypeSystem typeSystem; - readonly ITypeResolveContext decompilationContext; + internal readonly ITypeResolveContext decompilationContext; internal readonly ILFunction currentFunction; internal readonly ICompilation compilation; internal readonly CSharpResolver resolver; @@ -189,7 +189,38 @@ namespace ICSharpCode.Decompiler.CSharp internal bool HidesVariableWithName(string name) { - return currentFunction.Ancestors.OfType().SelectMany(f => f.Variables).Any(v => v.Name == name); + return currentFunction.Ancestors.OfType().Any(HidesVariableOrNestedFunction); + + bool HidesVariableOrNestedFunction(ILFunction function) + { + foreach (var v in function.Variables) { + if (v.Name == name) + return true; + } + + foreach (var (key, (functionName, definition)) in function.LocalFunctions) { + if (functionName == name) + return true; + } + + return false; + } + } + + internal bool IsLocalFunction(IMethod method) + { + return settings.LocalFunctions && IL.Transforms.LocalFunctionDecompiler.IsLocalFunctionMethod(method); + } + + internal (string Name, ILFunction Definition) ResolveLocalFunction(IMethod method) + { + Debug.Assert(IsLocalFunction(method)); + foreach (var parent in currentFunction.Ancestors.OfType()) { + if (parent.LocalFunctions.TryGetValue(method, out var info)) { + return info; + } + } + return (null, null); } bool RequiresQualifier(IMember member, TranslatedExpression target) @@ -1823,7 +1854,7 @@ namespace ICSharpCode.Decompiler.CSharp return SpecialType.UnknownType; } - IEnumerable MakeParameters(IReadOnlyList parameters, ILFunction function) + internal IEnumerable MakeParameters(IReadOnlyList parameters, ILFunction function) { var variables = function.Variables.Where(v => v.Kind == VariableKind.Parameter).ToDictionary(v => v.Index); int i = 0; @@ -1832,17 +1863,19 @@ namespace ICSharpCode.Decompiler.CSharp if (string.IsNullOrEmpty(pd.Name) && !pd.Type.IsArgList()) { // needs to be consistent with logic in ILReader.CreateILVarable(ParameterDefinition) pd.Name = "P_" + i; + // if this is a local function, we have to skip the parameters for closure references + if (settings.LocalFunctions && function.Kind == ILFunctionKind.LocalFunction && IL.Transforms.LocalFunctionDecompiler.IsClosureParameter(parameter)) + break; } if (settings.AnonymousTypes && parameter.Type.ContainsAnonymousType()) pd.Type = null; - ILVariable v; - if (variables.TryGetValue(i, out v)) + if (variables.TryGetValue(i, out var v)) pd.AddAnnotation(new ILVariableResolveResult(v, parameters[i].Type)); yield return pd; i++; } } - + internal TranslatedExpression TranslateTarget(ILInstruction target, bool nonVirtualInvocation, bool memberStatic, IType memberDeclaringType) { diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpInvocationResolveResult.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpInvocationResolveResult.cs index ee66cb8d9..823887940 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpInvocationResolveResult.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpInvocationResolveResult.cs @@ -46,7 +46,12 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver /// Gets whether a params-Array is being used in its expanded form. /// public readonly bool IsExpandedForm; - + + /// + /// Gets whether this invocation is calling a local function. + /// + public readonly bool IsLocalFunctionInvocation; + readonly IReadOnlyList argumentToParameterMap; /// @@ -70,6 +75,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver bool isExtensionMethodInvocation = false, bool isExpandedForm = false, bool isDelegateInvocation = false, + bool isLocalFunctionInvocation = false, IReadOnlyList argumentToParameterMap = null, IList initializerStatements = null, IType returnTypeOverride = null @@ -80,6 +86,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver this.IsExtensionMethodInvocation = isExtensionMethodInvocation; this.IsExpandedForm = isExpandedForm; this.IsDelegateInvocation = isDelegateInvocation; + this.IsLocalFunctionInvocation = isLocalFunctionInvocation; this.argumentToParameterMap = argumentToParameterMap; } diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/LocalFunctionReferenceResolveResult.cs b/ICSharpCode.Decompiler/CSharp/Resolver/LocalFunctionReferenceResolveResult.cs new file mode 100644 index 000000000..e5253eba4 --- /dev/null +++ b/ICSharpCode.Decompiler/CSharp/Resolver/LocalFunctionReferenceResolveResult.cs @@ -0,0 +1,37 @@ +// Copyright (c) 2019 Siegfried Pammer +// +// 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.Text; +using ICSharpCode.Decompiler.IL; +using ICSharpCode.Decompiler.Semantics; +using ICSharpCode.Decompiler.TypeSystem; + +namespace ICSharpCode.Decompiler.CSharp.Resolver +{ + class LocalFunctionReferenceResolveResult : ResolveResult + { + public readonly ILFunction Function; + + public LocalFunctionReferenceResolveResult(ILFunction function) + : base(SpecialType.NoType) + { + this.Function = function; + } + } +} diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs index 70f58b7eb..759bbe8a9 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -28,6 +28,7 @@ using System; using System.Threading; using ICSharpCode.Decompiler.IL.Transforms; using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching; +using ICSharpCode.Decompiler.CSharp.Resolver; namespace ICSharpCode.Decompiler.CSharp { @@ -1020,10 +1021,14 @@ namespace ICSharpCode.Decompiler.CSharp { var stmt = new LocalFunctionDeclarationStatement(); var tsab = CSharpDecompiler.CreateAstBuilder(null); - stmt.Name = function.Method.Name; - stmt.Parameters.AddRange(function.Method.Parameters.Select(tsab.ConvertParameter)); + var nestedBuilder = new StatementBuilder(typeSystem, exprBuilder.decompilationContext, function, settings, cancellationToken); + var localFunction = exprBuilder.ResolveLocalFunction(function.Method); + Debug.Assert(localFunction.Definition == function); + stmt.Name = localFunction.Name; + stmt.Parameters.AddRange(exprBuilder.MakeParameters(function.Parameters, function)); stmt.ReturnType = tsab.ConvertType(function.Method.ReturnType); - stmt.Body = ConvertAsBlock(function.Body); + stmt.Body = nestedBuilder.ConvertAsBlock(function.Body); + stmt.AddAnnotation(new LocalFunctionReferenceResolveResult(function)); return stmt; } } diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Statements/LocalFunctionDeclarationStatement.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Statements/LocalFunctionDeclarationStatement.cs index 581b53bfa..47ff1641c 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/Statements/LocalFunctionDeclarationStatement.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Statements/LocalFunctionDeclarationStatement.cs @@ -1,6 +1,22 @@ -using System; +// Copyright (c) 2019 Siegfried Pammer +// +// 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.Collections.Generic; -using System.Text; using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching; namespace ICSharpCode.Decompiler.CSharp.Syntax diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceExtensionMethods.cs b/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceExtensionMethods.cs index e15d0d765..54d9b6a58 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceExtensionMethods.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceExtensionMethods.cs @@ -154,7 +154,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms var newResolveResult = new CSharpInvocationResolveResult( irr.TargetResult, irr.Member, irr.Arguments, irr.OverloadResolutionErrors, isExtensionMethodInvocation: true, irr.IsExpandedForm, irr.IsDelegateInvocation, - irr.GetArgumentToParameterMap(), irr.InitializerStatements); + irr.IsLocalFunctionInvocation, irr.GetArgumentToParameterMap(), irr.InitializerStatements); invocationExpression.AddAnnotation(newResolveResult); } } diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index eaf007f65..c0ef7943d 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -60,6 +60,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs index cb4acd62c..e0c61ead1 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs @@ -110,6 +110,8 @@ namespace ICSharpCode.Decompiler.IL } } + public Dictionary LocalFunctions { get; } = new Dictionary(); + public readonly IType ReturnType; public readonly IReadOnlyList Parameters; diff --git a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs index 9cdf8bcb4..5e377d221 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs @@ -162,6 +162,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms break; } } + foreach (var method in function.LocalFunctions.Keys.ToArray()) { + var info = function.LocalFunctions[method]; + if (!LocalFunctionDecompiler.ParseLocalFunctionName(method.Name, out _, out var newName) || !IsValidName(newName)) + newName = null; + function.LocalFunctions[method] = (newName, info.Declaration); + } // Now generate names: var mapping = new Dictionary(ILVariableEqualityComparer.Instance); foreach (var inst in function.Descendants.OfType()) { @@ -174,6 +180,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms v.Name = name; } } + foreach (var method in function.LocalFunctions.Keys.ToArray()) { + var info = function.LocalFunctions[method]; + var newName = info.Name; + if (newName == null) { + newName = GetAlternativeName("f"); + } + function.LocalFunctions[method] = (newName, info.Declaration); + } } /// diff --git a/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs b/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs index e016ef970..5a332fa76 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs @@ -136,10 +136,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms var body = context.PEFile.Reader.GetMethodBody(methodDefinition.RelativeVirtualAddress); var function = ilReader.ReadIL((MethodDefinitionHandle)targetMethod.MetadataToken, body, genericContext.Value, ILFunctionKind.Delegate, context.CancellationToken); function.DelegateType = value.Method.DeclaringType; - function.CheckInvariant(ILPhase.Normal); // Embed the lambda into the parent function's ILAst, so that "Show steps" can show // how the lambda body is being transformed. value.ReplaceWith(function); + function.CheckInvariant(ILPhase.Normal); var contextPrefix = targetMethod.Name; foreach (ILVariable v in function.Variables.Where(v => v.Kind != VariableKind.Parameter)) { diff --git a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs index cd38b463c..9b69af499 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs @@ -1,4 +1,22 @@ -using System; +// Copyright (c) 2019 Siegfried Pammer +// +// 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.Collections.Immutable; using System.Linq; @@ -43,8 +61,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms foreach (var (method, useSites) in localFunctions) { var insertionPoint = FindInsertionPoint(useSites); - if (TransformLocalFunction(method, (Block)insertionPoint.Parent, insertionPoint.ChildIndex + 1) == null) + ILFunction localFunction = TransformLocalFunction(method, (Block)insertionPoint.Parent, insertionPoint.ChildIndex + 1); + if (localFunction == null) continue; + function.LocalFunctions.Add(localFunction.Method, (localFunction.Method.Name, localFunction)); } } @@ -81,7 +101,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms return a.Ancestors.FirstOrDefault(ancestorsOfB.Contains); } - static ILInstruction GetStatement(ILInstruction inst) + internal static bool IsClosureParameter(IParameter parameter) + { + return parameter.IsRef + && ((ByReferenceType)parameter.Type).ElementType.GetDefinition()?.IsCompilerGenerated() == true; + } + + internal static ILInstruction GetStatement(ILInstruction inst) { while (inst.Parent != null) { if (inst.Parent is Block) @@ -161,9 +187,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// Newer Roslyn versions use the format "<callerName>g__functionName|x_y" /// Older versions use "<callerName>g__functionNamex_y" /// - static readonly Regex functionNameRegex = new Regex(@"^<(.*)>g__(.*)\|{0,1}\d+(_\d+)?$", RegexOptions.Compiled); + static readonly Regex functionNameRegex = new Regex(@"^<(.*)>g__([^\|]*)\|{0,1}\d+(_\d+)?$", RegexOptions.Compiled); - static bool ParseLocalFunctionName(string name, out string callerName, out string functionName) + internal static bool ParseLocalFunctionName(string name, out string callerName, out string functionName) { callerName = null; functionName = null; diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs index 317b4da04..9034a8fdd 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs @@ -65,20 +65,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms foreach (var v in f.Variables.ToArray()) { if (HandleMonoStateMachine(function, v, decompilationContext, f)) continue; - if (!(v.IsSingleDefinition && v.StoreInstructions.SingleOrDefault() is StLoc inst)) - continue; - if (IsClosureInit(inst, out ITypeDefinition closureType)) { - // TODO : figure out whether it is a mono compiled closure, without relying on the type name - bool isMono = f.StateMachineCompiledWithMono || closureType.Name.Contains("AnonStorey"); - displayClasses.Add(inst.Variable, new DisplayClass { - IsMono = isMono, - Initializer = inst, - Variable = v, - Definition = closureType, - Variables = new Dictionary(), - CaptureScope = isMono && IsMonoNestedCaptureScope(closureType) ? null : inst.Variable.CaptureScope - }); - instructionsToRemove.Add(inst); + if (IsClosure(v, out ITypeDefinition closureType, out var inst)) { + AddOrUpdateDisplayClass(f, v, closureType, inst); + } + if (v.Kind == VariableKind.Parameter && v.Index > -1 && f.Method.Parameters[v.Index ?? -1] is IParameter p && LocalFunctionDecompiler.IsClosureParameter(p)) { + AddOrUpdateDisplayClass(f, v, ((ByReferenceType)p.Type).ElementType.GetDefinition(), f.Body); } } foreach (var displayClass in displayClasses.Values.OrderByDescending(d => d.Initializer.StartILOffset).ToArray()) { @@ -100,6 +91,56 @@ namespace ICSharpCode.Decompiler.IL.Transforms } } + private void AddOrUpdateDisplayClass(ILFunction f, ILVariable v, ITypeDefinition closureType, ILInstruction inst) + { + var displayClass = displayClasses.Values.FirstOrDefault(c => c.Definition == closureType); + if (displayClass == null) { + // TODO : figure out whether it is a mono compiled closure, without relying on the type name + bool isMono = f.StateMachineCompiledWithMono || closureType.Name.Contains("AnonStorey"); + displayClasses.Add(v, new DisplayClass { + IsMono = isMono, + Initializer = inst, + Variable = v, + Definition = closureType, + Variables = new Dictionary(), + CaptureScope = isMono && IsMonoNestedCaptureScope(closureType) ? null : v.CaptureScope + }); + } else { + displayClass.Variable = v; + displayClass.Initializer = inst; + displayClasses.Add(v, displayClass); + } + } + + bool IsClosure(ILVariable variable, out ITypeDefinition closureType, out ILInstruction initializer) + { + closureType = null; + initializer = null; + if (variable.IsSingleDefinition && variable.StoreInstructions.SingleOrDefault() is StLoc inst) { + initializer = inst; + if (IsClosureInit(inst, out closureType)) { + instructionsToRemove.Add(inst); + return true; + } + } + closureType = variable.Type.GetDefinition(); + if (closureType?.Kind == TypeKind.Struct && variable.HasInitialValue) { + initializer = LocalFunctionDecompiler.GetStatement(variable.AddressInstructions.OrderBy(i => i.StartILOffset).First()); + return IsPotentialClosure(this.context, closureType); + } + return false; + } + + bool IsClosureInit(StLoc inst, out ITypeDefinition closureType) + { + if (inst.Value is NewObj newObj) { + closureType = newObj.Method.DeclaringTypeDefinition; + return closureType != null && IsPotentialClosure(this.context, newObj); + } + closureType = null; + return false; + } + bool IsOuterClosureReference(IField field) { return displayClasses.Values.Any(disp => disp.Definition == field.DeclaringTypeDefinition); @@ -179,6 +220,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms return IsPotentialClosure(decompilationContext.CurrentTypeDefinition, inst.Method.DeclaringTypeDefinition); } + internal static bool IsPotentialClosure(ILTransformContext context, ITypeDefinition potentialDisplayClass) + { + var decompilationContext = new SimpleTypeResolveContext(context.Function.Ancestors.OfType().Last().Method); + return IsPotentialClosure(decompilationContext.CurrentTypeDefinition, potentialDisplayClass); + } + internal static bool IsPotentialClosure(ITypeDefinition decompiledTypeDefinition, ITypeDefinition potentialDisplayClass) { if (potentialDisplayClass == null || !potentialDisplayClass.IsCompilerGeneratedOrIsInCompilerGeneratedClass()) @@ -191,6 +238,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms return true; } + bool IsDisplayClassLoad(ILInstruction target, out ILVariable variable) + { + if (target.MatchLdLoc(out variable) || target.MatchLdLoca(out variable)) + return true; + if (target.MatchAddressOf(out var load) && load.MatchLdLoc(out variable)) + return true; + return false; + } + protected override void Default(ILInstruction inst) { foreach (var child in inst.Children) { @@ -210,13 +266,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms } } - bool IsClosureInit(StLoc inst, out ITypeDefinition closureType) + protected internal override void VisitLdLoc(LdLoc inst) { - closureType = null; - if (!(inst.Value is NewObj newObj)) - return false; - closureType = newObj.Method.DeclaringTypeDefinition; - return closureType != null && IsPotentialClosure(this.context, newObj); + base.VisitLdLoc(inst); } protected internal override void VisitStObj(StObj inst) @@ -229,7 +281,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (!inst.Target.MatchLdFlda(out ILInstruction target, out IField field)) return; // Get display class info - if (!(target is LdLoc displayClassLoad && displayClasses.TryGetValue(displayClassLoad.Variable, out var displayClass))) + if (!IsDisplayClassLoad(target, out var displayClassLoad) || !displayClasses.TryGetValue(displayClassLoad, out var displayClass)) return; field = (IField)field.MemberDefinition; if (displayClass.Variables.TryGetValue(field, out DisplayClassVariable info)) { @@ -264,7 +316,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (!inst.Target.MatchLdFlda(out var target, out IField field)) return; // Get display class info - if (!(target is LdLoc displayClassLoad && displayClasses.TryGetValue(displayClassLoad.Variable, out var displayClass))) + if (!IsDisplayClassLoad(target, out var displayClassLoad) || !displayClasses.TryGetValue(displayClassLoad, out var displayClass)) return; // Get display class variable info if (!displayClass.Variables.TryGetValue((IField)field.MemberDefinition, out DisplayClassVariable info)) diff --git a/ICSharpCode.Decompiler/Output/TextTokenWriter.cs b/ICSharpCode.Decompiler/Output/TextTokenWriter.cs index f19c8cfda..3b572088f 100644 --- a/ICSharpCode.Decompiler/Output/TextTokenWriter.cs +++ b/ICSharpCode.Decompiler/Output/TextTokenWriter.cs @@ -21,6 +21,7 @@ using System.Collections.Generic; using System.Linq; using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.CSharp.OutputVisitor; +using ICSharpCode.Decompiler.CSharp.Resolver; using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.IL; using ICSharpCode.Decompiler.TypeSystem; @@ -169,13 +170,18 @@ namespace ICSharpCode.Decompiler return variable; } - var label = node as LabelStatement; - if (label != null) { + if (node is LabelStatement label) { var method = nodeStack.Select(nd => nd.GetSymbol() as IMethod).FirstOrDefault(mr => mr != null); if (method != null) return method + label.Label; } + if (node is LocalFunctionDeclarationStatement) { + var localFunction = node.GetResolveResult() as LocalFunctionReferenceResolveResult; + if (localFunction != null) + return localFunction.Function.Method; + } + return null; } diff --git a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs index 04ce3ef8f..06f07cdd9 100644 --- a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs +++ b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs @@ -104,6 +104,10 @@ namespace ICSharpCode.Decompiler.TypeSystem /// NullabilityAnnotations = 0x400, /// + /// If this option is active, + /// + LocalFunctions = 0x800, + /// /// Default settings: typical options for the decompiler, with all C# languages features enabled. /// Default = Dynamic | Tuple | ExtensionMethods | DecimalConstants | ReadOnlyStructsAndParameters diff --git a/ICSharpCode.Decompiler/TypeSystem/IMethod.cs b/ICSharpCode.Decompiler/TypeSystem/IMethod.cs index 64c8b3303..17f29aab6 100644 --- a/ICSharpCode.Decompiler/TypeSystem/IMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/IMethod.cs @@ -82,7 +82,7 @@ namespace ICSharpCode.Decompiler.TypeSystem /// /// If this method is reduced from an extension method return the original method, null otherwise. - /// A reduced method doesn't contain the extension method parameter. That means that has one parameter less than it's definition. + /// A reduced method doesn't contain the extension method parameter. That means that it has one parameter less than its definition. /// IMethod ReducedFrom { get; } From 119c679fad319f92be3fe174735a3f49bc88a4a1 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 13 Jul 2019 20:32:00 +0200 Subject: [PATCH 03/23] Add addressof(ldloc V) => ldloca V transform for read access on value types. --- .../IL/Transforms/ExpressionTransforms.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs index 623d69ae8..08c0a965c 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs @@ -389,6 +389,22 @@ namespace ICSharpCode.Decompiler.IL.Transforms { base.VisitLdObj(inst); EarlyExpressionTransforms.LdObjToLdLoc(inst, context); + // ldobj(...(addressof(ldloc V)) where ... can be zero or more ldflda instructions + // => + // ldobj(...(ldloca V)) + var temp = inst.Target; + var range = temp.ILRanges; + while (temp.MatchLdFlda(out var ldfldaTarget, out _)) { + temp = ldfldaTarget; + range = range.Concat(temp.ILRanges); + } + if (temp.MatchAddressOf(out var addressOfTarget) && addressOfTarget.MatchLdLoc(out var v)) { + var replacement = new LdLoca(v).WithILRange(addressOfTarget); + foreach (var r in range) { + replacement = replacement.WithILRange(r); + } + temp.ReplaceWith(replacement); + } } protected internal override void VisitStObj(StObj inst) From 16d6e16da77afbe35fe5df7d7510af3f41e2630f Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 14 Jul 2019 16:22:00 +0200 Subject: [PATCH 04/23] Add support for local function references and recursive local functions. --- ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 122 ++++++++++-------- .../IL/Transforms/DelegateConstruction.cs | 2 +- .../IL/Transforms/LocalFunctionDecompiler.cs | 87 ++++++++++--- .../Transforms/TransformDisplayClassUsage.cs | 8 +- 4 files changed, 141 insertions(+), 78 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index 7ec5ee19d..489a12f1c 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -183,11 +183,14 @@ namespace ICSharpCode.Decompiler.CSharp CallOpCode = callOpCode, IsLocalFunction = expressionBuilder.IsLocalFunction(method) }; + (string Name, ILFunction Definition) localFunction = default; + if (expectedTargetDetails.IsLocalFunction && (localFunction = expressionBuilder.ResolveLocalFunction(method)).Definition == null) { + expectedTargetDetails.IsLocalFunction = false; + } TranslatedExpression target; if (callOpCode == OpCode.NewObj) { target = default(TranslatedExpression); // no target } else if (expectedTargetDetails.IsLocalFunction) { - var localFunction = expressionBuilder.ResolveLocalFunction(method); target = new IdentifierExpression(localFunction.Name) .WithoutILInstruction() .WithRR(new LocalFunctionReferenceResolveResult(localFunction.Definition)); @@ -1236,71 +1239,82 @@ namespace ICSharpCode.Decompiler.CSharp TranslatedExpression target; IType targetType; bool requireTarget; - if (method.IsExtensionMethod && invokeMethod != null && method.Parameters.Count - 1 == invokeMethod.Parameters.Count) { - targetType = method.Parameters[0].Type; - if (targetType.Kind == TypeKind.ByReference && thisArg is Box thisArgBox) { - targetType = ((ByReferenceType)targetType).ElementType; - thisArg = thisArgBox.Argument; - } - target = expressionBuilder.Translate(thisArg, targetType); - requireTarget = true; - } else { - targetType = method.DeclaringType; - if (targetType.IsReferenceType == false && thisArg is Box thisArgBox) { - // Normal struct instance method calls (which TranslateTarget is meant for) expect a 'ref T', - // but delegate construction uses a 'box T'. - if (thisArgBox.Argument is LdObj ldobj) { - thisArg = ldobj.Target; - } else { - thisArg = new AddressOf(thisArgBox.Argument); - } - } - target = expressionBuilder.TranslateTarget(thisArg, - nonVirtualInvocation: func.OpCode == OpCode.LdFtn, - memberStatic: method.IsStatic, - memberDeclaringType: method.DeclaringType); - requireTarget = expressionBuilder.HidesVariableWithName(method.Name) - || (method.IsStatic ? !expressionBuilder.IsCurrentOrContainingType(method.DeclaringTypeDefinition) : !(target.Expression is ThisReferenceExpression)); - } var expectedTargetDetails = new ExpectedTargetDetails { - CallOpCode = inst.OpCode + CallOpCode = inst.OpCode, + IsLocalFunction = expressionBuilder.IsLocalFunction(method) }; - bool needsCast = false; ResolveResult result = null; - var or = new OverloadResolution(resolver.Compilation, method.Parameters.SelectReadOnlyArray(p => new TypeResolveResult(p.Type))); - if (!requireTarget) { - result = resolver.ResolveSimpleName(method.Name, method.TypeArguments, isInvocationTarget: false); - if (result is MethodGroupResolveResult mgrr) { - or.AddMethodLists(mgrr.MethodsGroupedByDeclaringType.ToArray()); - requireTarget = (or.BestCandidateErrors != OverloadResolutionErrors.None || !IsAppropriateCallTarget(expectedTargetDetails, method, or.BestCandidate)); - } else { + string methodName; + if (expectedTargetDetails.IsLocalFunction) { + requireTarget = false; + var localFunction = expressionBuilder.ResolveLocalFunction(method); + result = new LocalFunctionReferenceResolveResult(localFunction.Definition); + target = default; + methodName = localFunction.Name; + } else { + methodName = method.Name; + if (method.IsExtensionMethod && invokeMethod != null && method.Parameters.Count - 1 == invokeMethod.Parameters.Count) { + targetType = method.Parameters[0].Type; + if (targetType.Kind == TypeKind.ByReference && thisArg is Box thisArgBox) { + targetType = ((ByReferenceType)targetType).ElementType; + thisArg = thisArgBox.Argument; + } + target = expressionBuilder.Translate(thisArg, targetType); requireTarget = true; + } else { + targetType = method.DeclaringType; + if (targetType.IsReferenceType == false && thisArg is Box thisArgBox) { + // Normal struct instance method calls (which TranslateTarget is meant for) expect a 'ref T', + // but delegate construction uses a 'box T'. + if (thisArgBox.Argument is LdObj ldobj) { + thisArg = ldobj.Target; + } else { + thisArg = new AddressOf(thisArgBox.Argument); + } + } + target = expressionBuilder.TranslateTarget(thisArg, + nonVirtualInvocation: func.OpCode == OpCode.LdFtn, + memberStatic: method.IsStatic, + memberDeclaringType: method.DeclaringType); + requireTarget = expressionBuilder.HidesVariableWithName(method.Name) + || (method.IsStatic ? !expressionBuilder.IsCurrentOrContainingType(method.DeclaringTypeDefinition) : !(target.Expression is ThisReferenceExpression)); } - } - MemberLookup lookup = null; - if (requireTarget) { - lookup = new MemberLookup(resolver.CurrentTypeDefinition, resolver.CurrentTypeDefinition.ParentModule); - var rr = lookup.Lookup(target.ResolveResult, method.Name, method.TypeArguments, false) ; - needsCast = true; - result = rr; - if (rr is MethodGroupResolveResult mgrr) { - or.AddMethodLists(mgrr.MethodsGroupedByDeclaringType.ToArray()); - needsCast = (or.BestCandidateErrors != OverloadResolutionErrors.None || !IsAppropriateCallTarget(expectedTargetDetails, method, or.BestCandidate)); + var or = new OverloadResolution(resolver.Compilation, method.Parameters.SelectReadOnlyArray(p => new TypeResolveResult(p.Type))); + if (!requireTarget) { + result = resolver.ResolveSimpleName(method.Name, method.TypeArguments, isInvocationTarget: false); + if (result is MethodGroupResolveResult mgrr) { + or.AddMethodLists(mgrr.MethodsGroupedByDeclaringType.ToArray()); + requireTarget = (or.BestCandidateErrors != OverloadResolutionErrors.None || !IsAppropriateCallTarget(expectedTargetDetails, method, or.BestCandidate)); + } else { + requireTarget = true; + } + } + MemberLookup lookup = null; + bool needsCast = false; + if (requireTarget) { + lookup = new MemberLookup(resolver.CurrentTypeDefinition, resolver.CurrentTypeDefinition.ParentModule); + var rr = lookup.Lookup(target.ResolveResult, method.Name, method.TypeArguments, false); + needsCast = true; + result = rr; + if (rr is MethodGroupResolveResult mgrr) { + or.AddMethodLists(mgrr.MethodsGroupedByDeclaringType.ToArray()); + needsCast = (or.BestCandidateErrors != OverloadResolutionErrors.None || !IsAppropriateCallTarget(expectedTargetDetails, method, or.BestCandidate)); + } + } + if (needsCast) { + Debug.Assert(requireTarget); + target = target.ConvertTo(targetType, expressionBuilder); + result = lookup.Lookup(target.ResolveResult, method.Name, method.TypeArguments, false); } - } - if (needsCast) { - Debug.Assert(requireTarget); - target = target.ConvertTo(targetType, expressionBuilder); - result = lookup.Lookup(target.ResolveResult, method.Name, method.TypeArguments, false); } Expression targetExpression; if (requireTarget) { - var mre = new MemberReferenceExpression(target, method.Name); + var mre = new MemberReferenceExpression(target, methodName); mre.TypeArguments.AddRange(method.TypeArguments.Select(expressionBuilder.ConvertType)); mre.WithRR(result); targetExpression = mre; } else { - var ide = new IdentifierExpression(method.Name) + var ide = new IdentifierExpression(methodName) .WithRR(result); targetExpression = ide; } @@ -1308,7 +1322,7 @@ namespace ICSharpCode.Decompiler.CSharp .WithILInstruction(inst) .WithRR(new ConversionResolveResult( inst.Method.DeclaringType, - new MemberResolveResult(target.ResolveResult, method), + target.ResolveResult != null ? new MemberResolveResult(target.ResolveResult, method) : result, Conversion.MethodGroupConversion(method, func.OpCode == OpCode.LdVirtFtn, false))); return oce; } diff --git a/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs b/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs index 5a332fa76..4b03fcfe8 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs @@ -169,7 +169,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// Replaces loads of 'this' with the target expression. /// Async delegates use: ldobj(ldloca this). /// - class ReplaceDelegateTargetVisitor : ILVisitor + internal class ReplaceDelegateTargetVisitor : ILVisitor { readonly ILVariable thisVariable; readonly ILInstruction target; diff --git a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs index 9b69af499..b8f1e99fc 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs @@ -19,6 +19,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.Linq; using System.Reflection; using System.Reflection.Metadata; @@ -42,33 +43,46 @@ namespace ICSharpCode.Decompiler.IL.Transforms return; this.context = context; this.decompilationContext = new SimpleTypeResolveContext(function.Method); - var localFunctions = new Dictionary>(); + var localFunctions = new Dictionary>(); var cancellationToken = context.CancellationToken; // Find use-sites foreach (var inst in function.Descendants) { cancellationToken.ThrowIfCancellationRequested(); - if (inst is Call call && IsLocalFunctionMethod(call.Method)) { - context.StepStartGroup($"LocalFunctionDecompiler {call.StartILOffset}", call); + if (inst is CallInstruction call && IsLocalFunctionMethod(call.Method)) { + if (function.Ancestors.OfType().Any(f => f.LocalFunctions.ContainsKey(call.Method))) + continue; if (!localFunctions.TryGetValue(call.Method, out var info)) { - info = new List() { call }; + info = new List() { call }; localFunctions.Add(call.Method, info); } else { info.Add(call); } + } else if (inst is LdFtn ldftn && ldftn.Parent is NewObj newObj && IsLocalFunctionMethod(ldftn.Method) && DelegateConstruction.IsDelegateConstruction(newObj)) { + if (function.Ancestors.OfType().Any(f => f.LocalFunctions.ContainsKey(ldftn.Method))) + continue; + context.StepStartGroup($"LocalFunctionDecompiler {ldftn.StartILOffset}", ldftn); + if (!localFunctions.TryGetValue(ldftn.Method, out var info)) { + info = new List() { newObj }; + localFunctions.Add(ldftn.Method, info); + } else { + info.Add(newObj); + } context.StepEndGroup(); } } foreach (var (method, useSites) in localFunctions) { var insertionPoint = FindInsertionPoint(useSites); - ILFunction localFunction = TransformLocalFunction(method, (Block)insertionPoint.Parent, insertionPoint.ChildIndex + 1); - if (localFunction == null) - continue; - function.LocalFunctions.Add(localFunction.Method, (localFunction.Method.Name, localFunction)); + context.StepStartGroup($"LocalFunctionDecompiler {insertionPoint.StartILOffset}", insertionPoint); + try { + TransformLocalFunction(function, method, useSites, (Block)insertionPoint.Parent, insertionPoint.ChildIndex + 1); + } finally { + context.StepEndGroup(); + } } } - static ILInstruction FindInsertionPoint(List useSites) + static ILInstruction FindInsertionPoint(List useSites) { ILInstruction insertionPoint = null; foreach (var call in useSites) { @@ -85,14 +99,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms insertionPoint = ancestor; } - switch (insertionPoint) { - case BlockContainer bc: - return insertionPoint; - case Block b: - return insertionPoint; - default: - return insertionPoint; - } + return insertionPoint; } static ILInstruction FindCommonAncestorInstruction(ILInstruction a, ILInstruction b) @@ -117,7 +124,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return inst; } - private ILFunction TransformLocalFunction(IMethod targetMethod, Block parent, int insertionPoint) + private ILFunction TransformLocalFunction(ILFunction parentFunction, IMethod targetMethod, List useSites, Block parent, int insertionPoint) { var methodDefinition = context.PEFile.Metadata.GetMethodDefinition((MethodDefinitionHandle)targetMethod.MetadataToken); if (!methodDefinition.HasBody()) @@ -132,13 +139,55 @@ namespace ICSharpCode.Decompiler.IL.Transforms // how the local function body is being transformed. parent.Instructions.Insert(insertionPoint, function); function.CheckInvariant(ILPhase.Normal); - var nestedContext = new ILTransformContext(context, function); function.RunTransforms(CSharpDecompiler.GetILTransforms().TakeWhile(t => !(t is LocalFunctionDecompiler)), nestedContext); + if (IsNonLocalTarget(targetMethod, useSites, out var target)) { + Debug.Assert(target != null); + nestedContext.Step("LocalFunctionDecompiler (ReplaceDelegateTargetVisitor)", function); + var thisVar = function.Variables.SingleOrDefault(v => v.Index == -1 && v.Kind == VariableKind.Parameter); + function.AcceptVisitor(new DelegateConstruction.ReplaceDelegateTargetVisitor(target, thisVar)); + } + parentFunction.LocalFunctions.Add(function.Method, (function.Method.Name, function)); + // handle nested functions + nestedContext.StepStartGroup("LocalFunctionDecompiler (nested functions)", function); + new LocalFunctionDecompiler().Run(function, nestedContext); + nestedContext.StepEndGroup(); return function; } + bool IsNonLocalTarget(IMethod targetMethod, List useSites, out ILInstruction target) + { + target = null; + if (targetMethod.IsStatic) + return false; + ValidateUseSites(useSites); + target = useSites.Select(call => call.Arguments.First()).First(); + return !target.MatchLdThis(); + } + + [Conditional("DEBUG")] + static void ValidateUseSites(List useSites) + { + ILInstruction targetInstruction = null; + foreach (var site in useSites) { + if (targetInstruction == null) + targetInstruction = site.Arguments.First(); + else + Debug.Assert(targetInstruction.Match(site.Arguments[0]).Success); + } + } + + internal static bool IsLocalFunctionReference(NewObj inst) + { + if (inst == null || inst.Arguments.Count != 2 || inst.Method.DeclaringType.Kind != TypeKind.Delegate) + return false; + var opCode = inst.Arguments[1].OpCode; + + return (opCode == OpCode.LdFtn || opCode == OpCode.LdVirtFtn) + && IsLocalFunctionMethod(((IInstructionWithMethodOperand)inst.Arguments[1]).Method); + } + public static bool IsLocalFunctionMethod(IMethod method) { return IsLocalFunctionMethod(method.ParentModule.PEFile, (MethodDefinitionHandle)method.MetadataToken); diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs index 9034a8fdd..f5d63ac10 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs @@ -27,7 +27,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// /// Transforms closure fields to local variables. /// - /// This is a post-processing step of and . + /// This is a post-processing step of , and . /// class TransformDisplayClassUsage : ILVisitor, IILTransform { @@ -68,7 +68,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (IsClosure(v, out ITypeDefinition closureType, out var inst)) { AddOrUpdateDisplayClass(f, v, closureType, inst); } - if (v.Kind == VariableKind.Parameter && v.Index > -1 && f.Method.Parameters[v.Index ?? -1] is IParameter p && LocalFunctionDecompiler.IsClosureParameter(p)) { + if (f.Kind == ILFunctionKind.LocalFunction && v.Kind == VariableKind.Parameter && v.Index > -1 && f.Method.Parameters[v.Index.Value] is IParameter p && LocalFunctionDecompiler.IsClosureParameter(p)) { AddOrUpdateDisplayClass(f, v, ((ByReferenceType)p.Type).ElementType.GetDefinition(), f.Body); } } @@ -124,9 +124,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms } } closureType = variable.Type.GetDefinition(); - if (closureType?.Kind == TypeKind.Struct && variable.HasInitialValue) { + if (closureType?.Kind == TypeKind.Struct && variable.HasInitialValue && IsPotentialClosure(this.context, closureType)) { initializer = LocalFunctionDecompiler.GetStatement(variable.AddressInstructions.OrderBy(i => i.StartILOffset).First()); - return IsPotentialClosure(this.context, closureType); + return true; } return false; } From b42d4963e7be04a23a8039c04c4a7b8534d87bc4 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 14 Jul 2019 16:22:26 +0200 Subject: [PATCH 05/23] Fix assertions in TransformExpressionTrees. --- ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs | 2 ++ .../IL/Transforms/TransformExpressionTrees.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs index e0c61ead1..de6883a4a 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs @@ -144,6 +144,7 @@ namespace ICSharpCode.Decompiler.IL case ILFunctionKind.TopLevelFunction: Debug.Assert(Parent == null); Debug.Assert(DelegateType == null); + Debug.Assert(Method != null); break; case ILFunctionKind.Delegate: Debug.Assert(Parent != null && !(Parent is Block)); @@ -158,6 +159,7 @@ namespace ICSharpCode.Decompiler.IL case ILFunctionKind.LocalFunction: Debug.Assert(Parent is Block); Debug.Assert(DelegateType == null); + Debug.Assert(Method != null); break; } for (int i = 0; i < Variables.Count; i++) { diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs index 3487977ee..5d6794bf9 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs @@ -156,6 +156,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms var returnType = functionType.GetDelegateInvokeMethod()?.ReturnType; var function = new ILFunction(returnType, parameterList, context.Function.GenericContext, container); function.DelegateType = functionType; + function.Kind = IsExpressionTree(functionType) ? ILFunctionKind.ExpressionTree : ILFunctionKind.Delegate; function.Variables.AddRange(parameterVariablesList); function.AddILRange(instruction); lambdaStack.Push(function); @@ -343,6 +344,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms case ILFunction function: if (function.Kind == ILFunctionKind.ExpressionTree) { function.DelegateType = UnwrapExpressionTree(function.DelegateType); + function.Kind = ILFunctionKind.Delegate; } return (function, function.DelegateType); case LdLoc ldloc: From 305b47245e1739eef244d10c87f69c1f52d9a3f0 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Mon, 15 Jul 2019 10:10:40 +0200 Subject: [PATCH 06/23] Refactor representation of local functions in ILAst. --- .../CSharp/CSharpDecompiler.cs | 22 +++++ ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 8 +- .../CSharp/ExpressionBuilder.cs | 13 +-- .../CSharp/StatementBuilder.cs | 60 ++++++++++---- ICSharpCode.Decompiler/IL/ILVariable.cs | 3 +- ICSharpCode.Decompiler/IL/Instructions.cs | 47 ++++++----- ICSharpCode.Decompiler/IL/Instructions.tt | 14 ++-- .../IL/Instructions/ILFunction.cs | 34 ++++++-- .../IL/Transforms/AssignVariableNames.cs | 14 ++-- .../IL/Transforms/LocalFunctionDecompiler.cs | 82 +++++++++++-------- .../Transforms/TransformDisplayClassUsage.cs | 20 ++--- 11 files changed, 207 insertions(+), 110 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index f8e8b1a72..c7c5ff137 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -687,6 +687,28 @@ namespace ICSharpCode.Decompiler.CSharp connectedMethods.Enqueue((MethodDefinitionHandle)token); } break; + case ILOpCode.Call: + case ILOpCode.Callvirt: + // deal with call/callvirt instructions, i.e., local function invocations + token = MetadataTokenHelpers.EntityHandleOrNil(blob.ReadInt32()); + if (token.IsNil) + continue; + switch (token.Kind) { + case HandleKind.MethodDefinition: + break; + case HandleKind.MethodSpecification: + var methodSpec = module.Metadata.GetMethodSpecification((MethodSpecificationHandle)token); + if (methodSpec.Method.IsNil || methodSpec.Method.Kind != HandleKind.MethodDefinition) + continue; + token = methodSpec.Method; + break; + default: + continue; + } + if (LocalFunctionDecompiler.IsLocalFunctionMethod(module, (MethodDefinitionHandle)token)) { + connectedMethods.Enqueue((MethodDefinitionHandle)token); + } + break; default: blob.SkipOperand(code); break; diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index 489a12f1c..7ca971359 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -183,8 +183,8 @@ namespace ICSharpCode.Decompiler.CSharp CallOpCode = callOpCode, IsLocalFunction = expressionBuilder.IsLocalFunction(method) }; - (string Name, ILFunction Definition) localFunction = default; - if (expectedTargetDetails.IsLocalFunction && (localFunction = expressionBuilder.ResolveLocalFunction(method)).Definition == null) { + ILFunction localFunction = null; + if (expectedTargetDetails.IsLocalFunction && (localFunction = expressionBuilder.ResolveLocalFunction(method)) == null) { expectedTargetDetails.IsLocalFunction = false; } TranslatedExpression target; @@ -193,7 +193,7 @@ namespace ICSharpCode.Decompiler.CSharp } else if (expectedTargetDetails.IsLocalFunction) { target = new IdentifierExpression(localFunction.Name) .WithoutILInstruction() - .WithRR(new LocalFunctionReferenceResolveResult(localFunction.Definition)); + .WithRR(new LocalFunctionReferenceResolveResult(localFunction)); } else { target = expressionBuilder.TranslateTarget( callArguments.FirstOrDefault(), @@ -1248,7 +1248,7 @@ namespace ICSharpCode.Decompiler.CSharp if (expectedTargetDetails.IsLocalFunction) { requireTarget = false; var localFunction = expressionBuilder.ResolveLocalFunction(method); - result = new LocalFunctionReferenceResolveResult(localFunction.Definition); + result = new LocalFunctionReferenceResolveResult(localFunction); target = default; methodName = localFunction.Name; } else { diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 688475cdd..2c95aa686 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -198,8 +198,8 @@ namespace ICSharpCode.Decompiler.CSharp return true; } - foreach (var (key, (functionName, definition)) in function.LocalFunctions) { - if (functionName == name) + foreach (var f in function.LocalFunctions.OfType()) { + if (f.Name == name) return true; } @@ -212,15 +212,16 @@ namespace ICSharpCode.Decompiler.CSharp return settings.LocalFunctions && IL.Transforms.LocalFunctionDecompiler.IsLocalFunctionMethod(method); } - internal (string Name, ILFunction Definition) ResolveLocalFunction(IMethod method) + internal ILFunction ResolveLocalFunction(IMethod method) { Debug.Assert(IsLocalFunction(method)); foreach (var parent in currentFunction.Ancestors.OfType()) { - if (parent.LocalFunctions.TryGetValue(method, out var info)) { - return info; + var definition = parent.LocalFunctions.FirstOrDefault(f => f.Method == method); + if (definition != null) { + return definition; } } - return (null, null); + return null; } bool RequiresQualifier(IMember member, TranslatedExpression target) diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs index 759bbe8a9..73b07c70b 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -733,11 +733,24 @@ namespace ICSharpCode.Decompiler.CSharp { if (storeInst.Variable.LoadInstructions.Any(ld => !ld.IsDescendantOf(usingContainer))) return false; - if (storeInst.Variable.AddressInstructions.Any(la => !la.IsDescendantOf(usingContainer) || !ILInlining.IsUsedAsThisPointerInCall(la) || IsTargetOfSetterCall(la, la.Variable.Type))) + if (storeInst.Variable.AddressInstructions.Any(inst => !AddressUseAllowed(inst))) return false; if (storeInst.Variable.StoreInstructions.OfType().Any(st => st != storeInst)) return false; return true; + + bool AddressUseAllowed(LdLoca la) + { + if (!la.IsDescendantOf(usingContainer)) + return false; + if (ILInlining.IsUsedAsThisPointerInCall(la) && !IsTargetOfSetterCall(la, la.Variable.Type)) + return true; + var current = la.Parent; + while (current is LdFlda next) { + current = next.Parent; + } + return current is LdObj; + } } /// @@ -850,6 +863,7 @@ namespace ICSharpCode.Decompiler.CSharp if (blockStatement.LastOrDefault() is ContinueStatement continueStmt) continueStmt.Remove(); + DeclareLocalFunctions(currentFunction, container, blockStatement); return new WhileStatement(new PrimitiveExpression(true), blockStatement); case ContainerKind.While: continueTarget = container.EntryPoint; @@ -871,6 +885,7 @@ namespace ICSharpCode.Decompiler.CSharp if (blockStatement.LastOrDefault() is ContinueStatement continueStmt2) continueStmt2.Remove(); + DeclareLocalFunctions(currentFunction, container, blockStatement); return new WhileStatement(exprBuilder.TranslateCondition(condition), blockStatement); case ContainerKind.DoWhile: continueTarget = container.Blocks.Last(); @@ -889,6 +904,7 @@ namespace ICSharpCode.Decompiler.CSharp // to continue statements, we have to introduce an extra label. blockStatement.Add(new LabelStatement { Label = continueTarget.Label }); } + DeclareLocalFunctions(currentFunction, container, blockStatement); if (blockStatement.Statements.Count == 0) { return new WhileStatement { Condition = exprBuilder.TranslateCondition(condition), @@ -920,6 +936,7 @@ namespace ICSharpCode.Decompiler.CSharp } if (continueTarget.IncomingEdgeCount > continueCount) blockStatement.Add(new LabelStatement { Label = continueTarget.Label }); + DeclareLocalFunctions(currentFunction, container, blockStatement); return forStmt; default: throw new ArgumentOutOfRangeException(); @@ -928,7 +945,31 @@ namespace ICSharpCode.Decompiler.CSharp BlockStatement ConvertBlockContainer(BlockContainer container, bool isLoop) { - return ConvertBlockContainer(new BlockStatement(), container, container.Blocks, isLoop); + var blockStatement = ConvertBlockContainer(new BlockStatement(), container, container.Blocks, isLoop); + DeclareLocalFunctions(currentFunction, container, blockStatement); + return blockStatement; + } + + void DeclareLocalFunctions(ILFunction currentFunction, BlockContainer container, BlockStatement blockStatement) + { + foreach (var localFunction in currentFunction.LocalFunctions) { + if (localFunction.DeclarationScope != container) + continue; + blockStatement.Add(TranslateFunction(localFunction)); + } + + LocalFunctionDeclarationStatement TranslateFunction(ILFunction function) + { + var stmt = new LocalFunctionDeclarationStatement(); + var tsab = CSharpDecompiler.CreateAstBuilder(null); + var nestedBuilder = new StatementBuilder(typeSystem, exprBuilder.decompilationContext, function, settings, cancellationToken); + stmt.Name = function.Name; + stmt.Parameters.AddRange(exprBuilder.MakeParameters(function.Parameters, function)); + stmt.ReturnType = tsab.ConvertType(function.Method.ReturnType); + stmt.Body = nestedBuilder.ConvertAsBlock(function.Body); + stmt.AddAnnotation(new LocalFunctionReferenceResolveResult(function)); + return stmt; + } } BlockStatement ConvertBlockContainer(BlockStatement blockStatement, BlockContainer container, IEnumerable blocks, bool isLoop) @@ -1016,20 +1057,5 @@ namespace ICSharpCode.Decompiler.CSharp stmt.InsertChildAfter(null, new Comment(" IL cpblk instruction"), Roles.Comment); return stmt; } - - protected internal override Statement VisitILFunction(ILFunction function) - { - var stmt = new LocalFunctionDeclarationStatement(); - var tsab = CSharpDecompiler.CreateAstBuilder(null); - var nestedBuilder = new StatementBuilder(typeSystem, exprBuilder.decompilationContext, function, settings, cancellationToken); - var localFunction = exprBuilder.ResolveLocalFunction(function.Method); - Debug.Assert(localFunction.Definition == function); - stmt.Name = localFunction.Name; - stmt.Parameters.AddRange(exprBuilder.MakeParameters(function.Parameters, function)); - stmt.ReturnType = tsab.ConvertType(function.Method.ReturnType); - stmt.Body = nestedBuilder.ConvertAsBlock(function.Body); - stmt.AddAnnotation(new LocalFunctionReferenceResolveResult(function)); - return stmt; - } } } diff --git a/ICSharpCode.Decompiler/IL/ILVariable.cs b/ICSharpCode.Decompiler/IL/ILVariable.cs index 8a0e5fd17..1983089df 100644 --- a/ICSharpCode.Decompiler/IL/ILVariable.cs +++ b/ICSharpCode.Decompiler/IL/ILVariable.cs @@ -415,7 +415,8 @@ namespace ICSharpCode.Decompiler.IL output.Write(" init"); } if (CaptureScope != null) { - output.Write(" captured in " + CaptureScope.EntryPoint.Label); + output.Write(" captured in "); + output.WriteLocalReference(CaptureScope.EntryPoint.Label, CaptureScope); } if (StateMachineField != null) { output.Write(" from state-machine"); diff --git a/ICSharpCode.Decompiler/IL/Instructions.cs b/ICSharpCode.Decompiler/IL/Instructions.cs index 27e0af93d..6c41e3aa6 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.cs +++ b/ICSharpCode.Decompiler/IL/Instructions.cs @@ -476,7 +476,7 @@ namespace ICSharpCode.Decompiler.IL { switch (index) { default: - this.Arguments[index - 0] = value; + this.Arguments[index - 0] = (ILInstruction)value; break; } } @@ -491,7 +491,7 @@ namespace ICSharpCode.Decompiler.IL { var clone = (CallInstruction)ShallowClone(); clone.Arguments = new InstructionCollection(clone, 0); - clone.Arguments.AddRange(this.Arguments.Select(arg => arg.Clone())); + clone.Arguments.AddRange(this.Arguments.Select(arg => (ILInstruction)arg.Clone())); return clone; } protected override InstructionFlags ComputeFlags() @@ -742,9 +742,11 @@ namespace ICSharpCode.Decompiler.IL SetChildInstruction(ref this.body, value, 0); } } + public static readonly SlotInfo LocalFunctionsSlot = new SlotInfo("LocalFunctions"); + public InstructionCollection LocalFunctions { get; private set; } protected sealed override int GetChildCount() { - return 1; + return 1 + LocalFunctions.Count; } protected sealed override ILInstruction GetChild(int index) { @@ -752,7 +754,7 @@ namespace ICSharpCode.Decompiler.IL case 0: return this.body; default: - throw new IndexOutOfRangeException(); + return this.LocalFunctions[index - 1]; } } protected sealed override void SetChild(int index, ILInstruction value) @@ -762,7 +764,8 @@ namespace ICSharpCode.Decompiler.IL this.Body = value; break; default: - throw new IndexOutOfRangeException(); + this.LocalFunctions[index - 1] = (ILFunction)value; + break; } } protected sealed override SlotInfo GetChildSlot(int index) @@ -771,13 +774,15 @@ namespace ICSharpCode.Decompiler.IL case 0: return BodySlot; default: - throw new IndexOutOfRangeException(); + return LocalFunctionsSlot; } } public sealed override ILInstruction Clone() { var clone = (ILFunction)ShallowClone(); clone.Body = this.body.Clone(); + clone.LocalFunctions = new InstructionCollection(clone, 1); + clone.LocalFunctions.AddRange(this.LocalFunctions.Select(arg => (ILFunction)arg.Clone())); clone.CloneVariables(); return clone; } @@ -797,7 +802,7 @@ namespace ICSharpCode.Decompiler.IL protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match) { var o = other as ILFunction; - return o != null && this.body.PerformMatch(o.body, ref match); + return o != null && this.body.PerformMatch(o.body, ref match) && Patterns.ListMatch.DoMatch(this.LocalFunctions, o.LocalFunctions, ref match); } } } @@ -4254,7 +4259,7 @@ namespace ICSharpCode.Decompiler.IL { switch (index) { default: - this.Indices[index - 0] = value; + this.Indices[index - 0] = (ILInstruction)value; break; } } @@ -4269,7 +4274,7 @@ namespace ICSharpCode.Decompiler.IL { var clone = (NewArr)ShallowClone(); clone.Indices = new InstructionCollection(clone, 0); - clone.Indices.AddRange(this.Indices.Select(arg => arg.Clone())); + clone.Indices.AddRange(this.Indices.Select(arg => (ILInstruction)arg.Clone())); return clone; } public override StackType ResultType { get { return StackType.O; } } @@ -4607,7 +4612,7 @@ namespace ICSharpCode.Decompiler.IL this.Array = value; break; default: - this.Indices[index - 1] = value; + this.Indices[index - 1] = (ILInstruction)value; break; } } @@ -4625,7 +4630,7 @@ namespace ICSharpCode.Decompiler.IL var clone = (LdElema)ShallowClone(); clone.Array = this.array.Clone(); clone.Indices = new InstructionCollection(clone, 1); - clone.Indices.AddRange(this.Indices.Select(arg => arg.Clone())); + clone.Indices.AddRange(this.Indices.Select(arg => (ILInstruction)arg.Clone())); return clone; } public bool DelayExceptions; // NullReferenceException/IndexOutOfBoundsException only occurs when the reference is dereferenced @@ -5578,7 +5583,7 @@ namespace ICSharpCode.Decompiler.IL { switch (index) { default: - this.Arguments[index - 0] = value; + this.Arguments[index - 0] = (ILInstruction)value; break; } } @@ -5593,7 +5598,7 @@ namespace ICSharpCode.Decompiler.IL { var clone = (DynamicGetIndexInstruction)ShallowClone(); clone.Arguments = new InstructionCollection(clone, 0); - clone.Arguments.AddRange(this.Arguments.Select(arg => arg.Clone())); + clone.Arguments.AddRange(this.Arguments.Select(arg => (ILInstruction)arg.Clone())); return clone; } protected override InstructionFlags ComputeFlags() @@ -5646,7 +5651,7 @@ namespace ICSharpCode.Decompiler.IL { switch (index) { default: - this.Arguments[index - 0] = value; + this.Arguments[index - 0] = (ILInstruction)value; break; } } @@ -5661,7 +5666,7 @@ namespace ICSharpCode.Decompiler.IL { var clone = (DynamicSetIndexInstruction)ShallowClone(); clone.Arguments = new InstructionCollection(clone, 0); - clone.Arguments.AddRange(this.Arguments.Select(arg => arg.Clone())); + clone.Arguments.AddRange(this.Arguments.Select(arg => (ILInstruction)arg.Clone())); return clone; } protected override InstructionFlags ComputeFlags() @@ -5714,7 +5719,7 @@ namespace ICSharpCode.Decompiler.IL { switch (index) { default: - this.Arguments[index - 0] = value; + this.Arguments[index - 0] = (ILInstruction)value; break; } } @@ -5729,7 +5734,7 @@ namespace ICSharpCode.Decompiler.IL { var clone = (DynamicInvokeMemberInstruction)ShallowClone(); clone.Arguments = new InstructionCollection(clone, 0); - clone.Arguments.AddRange(this.Arguments.Select(arg => arg.Clone())); + clone.Arguments.AddRange(this.Arguments.Select(arg => (ILInstruction)arg.Clone())); return clone; } protected override InstructionFlags ComputeFlags() @@ -5782,7 +5787,7 @@ namespace ICSharpCode.Decompiler.IL { switch (index) { default: - this.Arguments[index - 0] = value; + this.Arguments[index - 0] = (ILInstruction)value; break; } } @@ -5797,7 +5802,7 @@ namespace ICSharpCode.Decompiler.IL { var clone = (DynamicInvokeConstructorInstruction)ShallowClone(); clone.Arguments = new InstructionCollection(clone, 0); - clone.Arguments.AddRange(this.Arguments.Select(arg => arg.Clone())); + clone.Arguments.AddRange(this.Arguments.Select(arg => (ILInstruction)arg.Clone())); return clone; } protected override InstructionFlags ComputeFlags() @@ -5850,7 +5855,7 @@ namespace ICSharpCode.Decompiler.IL { switch (index) { default: - this.Arguments[index - 0] = value; + this.Arguments[index - 0] = (ILInstruction)value; break; } } @@ -5865,7 +5870,7 @@ namespace ICSharpCode.Decompiler.IL { var clone = (DynamicInvokeInstruction)ShallowClone(); clone.Arguments = new InstructionCollection(clone, 0); - clone.Arguments.AddRange(this.Arguments.Select(arg => arg.Clone())); + clone.Arguments.AddRange(this.Arguments.Select(arg => (ILInstruction)arg.Clone())); return clone; } protected override InstructionFlags ComputeFlags() diff --git a/ICSharpCode.Decompiler/IL/Instructions.tt b/ICSharpCode.Decompiler/IL/Instructions.tt index 4d63cc974..a6c1d13f5 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.tt +++ b/ICSharpCode.Decompiler/IL/Instructions.tt @@ -49,7 +49,8 @@ VoidResult, NoArguments, CustomWriteTo), new OpCode("ILFunction", "A container of IL blocks.", CustomChildren(new [] { - new ChildInfo("body") + new ChildInfo("body"), + new ChildInfo("localFunctions") { IsCollection = true, Type = "ILFunction" } }), CustomConstructor, CustomWriteTo, CustomComputeFlags, CustomVariableName("function"), ResultType("O") ), new OpCode("BlockContainer", "A container of IL blocks.", @@ -769,6 +770,7 @@ namespace ICSharpCode.Decompiler.IL public readonly string SlotName; public bool IsCollection; + public string Type = "ILInstruction"; public bool CanInlineInto; public string[] ExpectedTypes; @@ -814,7 +816,7 @@ namespace ICSharpCode.Decompiler.IL childCount = children.Length - 1; opCode.Flags.Add(argProp + ".Aggregate(InstructionFlags.None, (f, arg) => f | arg.Flags)"); opCode.ConstructorParameters.Add("params ILInstruction[] " + arg); - opCode.ConstructorBody.Add("this." + argProp + " = new InstructionCollection(this, " + i + ");"); + opCode.ConstructorBody.Add("this." + argProp + " = new InstructionCollection<" + children[i].Type + ">(this, " + i + ");"); opCode.ConstructorBody.Add("this." + argProp + ".AddRange(" + arg + ");"); opCode.PerformMatchConditions.Add("Patterns.ListMatch.DoMatch(this." + argProp + ", o." + argProp + ", ref match)"); if (i == 0) @@ -827,7 +829,7 @@ namespace ICSharpCode.Decompiler.IL opCode.WriteArguments.Add("\t" + arg + ".WriteTo(output, options);"); opCode.WriteArguments.Add("}"); opCode.Members.Add("public static readonly SlotInfo " + children[i].SlotName + " = " + children[i].GetSlotInit() + ";"); - opCode.Members.Add("public InstructionCollection " + argProp + " { get; private set; }"); + opCode.Members.Add("public InstructionCollection<" + children[i].Type + "> " + argProp + " { get; private set; }"); } else { opCode.Flags.Add(arg + ".Flags"); opCode.ConstructorParameters.Add("ILInstruction " + arg); @@ -906,7 +908,7 @@ namespace ICSharpCode.Decompiler.IL if (collection == null) b.AppendLine("\t\t\tthrow new IndexOutOfRangeException();"); else { - b.AppendLine("\t\t\tthis." + collection.PropertyName + "[index - " + childCount + "] = value;"); + b.AppendLine("\t\t\tthis." + collection.PropertyName + "[index - " + childCount + "] = (" + collection.Type + ")value;"); b.AppendLine("\t\t\tbreak;"); } b.AppendLine("\t}"); @@ -936,8 +938,8 @@ namespace ICSharpCode.Decompiler.IL b.AppendLine("\tvar clone = (" + opCode.Name + ")ShallowClone();"); for (int i = 0; i < children.Length; i++) { if (children[i].IsCollection) { - b.AppendLine("\tclone." + children[i].PropertyName + " = new InstructionCollection(clone, " + i + ");"); - b.AppendLine("\tclone." + children[i].PropertyName + ".AddRange(this." + children[i].PropertyName + ".Select(arg => arg.Clone()));"); + b.AppendLine("\tclone." + children[i].PropertyName + " = new InstructionCollection<" + children[i].Type + ">(clone, " + i + ");"); + b.AppendLine("\tclone." + children[i].PropertyName + ".AddRange(this." + children[i].PropertyName + ".Select(arg => (" + children[i].Type + ")arg.Clone()));"); } else { b.AppendLine("\tclone." + children[i].PropertyName + " = this." + children[i].Name + ".Clone();"); } diff --git a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs index de6883a4a..95eadfcb0 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs @@ -18,11 +18,12 @@ using System; using System.Collections.Generic; -using ICSharpCode.Decompiler.IL.Transforms; +using System.Diagnostics; using System.Linq; + +using ICSharpCode.Decompiler.IL.Transforms; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.Util; -using System.Diagnostics; namespace ICSharpCode.Decompiler.IL { @@ -30,6 +31,8 @@ namespace ICSharpCode.Decompiler.IL { public readonly IMethod Method; public readonly GenericContext GenericContext; + public string Name; + /// /// Size of the IL code in this function. /// Note: after async/await transform, this is the code size of the MoveNext function. @@ -37,6 +40,12 @@ namespace ICSharpCode.Decompiler.IL public int CodeSize; public readonly ILVariableCollection Variables; + /// + /// Gets the scope in which the local function is declared. + /// Returns null, if this is not a local function. + /// + public BlockContainer DeclarationScope { get; internal set; } + /// /// List of warnings of ILReader. /// @@ -110,8 +119,6 @@ namespace ICSharpCode.Decompiler.IL } } - public Dictionary LocalFunctions { get; } = new Dictionary(); - public readonly IType ReturnType; public readonly IReadOnlyList Parameters; @@ -119,12 +126,14 @@ namespace ICSharpCode.Decompiler.IL public ILFunction(IMethod method, int codeSize, GenericContext genericContext, ILInstruction body, ILFunctionKind kind = ILFunctionKind.TopLevelFunction) : base(OpCode.ILFunction) { this.Method = method; + this.Name = method.Name; this.CodeSize = codeSize; this.GenericContext = genericContext; this.Body = body; this.ReturnType = Method?.ReturnType; this.Parameters = Method?.Parameters; this.Variables = new ILVariableCollection(this); + this.LocalFunctions = new InstructionCollection(this, 1); this.kind = kind; } @@ -135,6 +144,7 @@ namespace ICSharpCode.Decompiler.IL this.ReturnType = returnType; this.Parameters = parameters; this.Variables = new ILVariableCollection(this); + this.LocalFunctions = new InstructionCollection(this, 1); this.kind = ILFunctionKind.ExpressionTree; } @@ -144,20 +154,24 @@ namespace ICSharpCode.Decompiler.IL case ILFunctionKind.TopLevelFunction: Debug.Assert(Parent == null); Debug.Assert(DelegateType == null); + Debug.Assert(DeclarationScope == null); Debug.Assert(Method != null); break; case ILFunctionKind.Delegate: Debug.Assert(Parent != null && !(Parent is Block)); Debug.Assert(DelegateType != null); + Debug.Assert(DeclarationScope == null); Debug.Assert(!(DelegateType?.FullName == "System.Linq.Expressions.Expression" && DelegateType.TypeParameterCount == 1)); break; case ILFunctionKind.ExpressionTree: Debug.Assert(Parent != null && !(Parent is Block)); Debug.Assert(DelegateType != null); + Debug.Assert(DeclarationScope == null); Debug.Assert(DelegateType?.FullName == "System.Linq.Expressions.Expression" && DelegateType.TypeParameterCount == 1); break; case ILFunctionKind.LocalFunction: - Debug.Assert(Parent is Block); + Debug.Assert(Parent is ILFunction); + Debug.Assert(DeclarationScope != null); Debug.Assert(DelegateType == null); Debug.Assert(Method != null); break; @@ -205,6 +219,11 @@ namespace ICSharpCode.Decompiler.IL if (IsIterator) { output.WriteLine(".iterator"); } + if (DeclarationScope != null) { + output.Write("declared as " + Name + " in "); + output.WriteLocalReference(DeclarationScope.EntryPoint.Label, DeclarationScope); + output.WriteLine(); + } output.MarkFoldStart(Variables.Count + " variable(s)", true); foreach (var variable in Variables) { @@ -221,6 +240,11 @@ namespace ICSharpCode.Decompiler.IL body.WriteTo(output, options); output.WriteLine(); + foreach (var localFunction in LocalFunctions) { + output.WriteLine(); + localFunction.WriteTo(output, options); + } + if (options.ShowILRanges) { var unusedILRanges = FindUnusedILRanges(); if (!unusedILRanges.IsEmpty) { diff --git a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs index 5e377d221..71d4a545c 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs @@ -162,11 +162,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms break; } } - foreach (var method in function.LocalFunctions.Keys.ToArray()) { - var info = function.LocalFunctions[method]; - if (!LocalFunctionDecompiler.ParseLocalFunctionName(method.Name, out _, out var newName) || !IsValidName(newName)) + foreach (var localFunction in function.LocalFunctions) { + if (!LocalFunctionDecompiler.ParseLocalFunctionName(localFunction.Name, out _, out var newName) || !IsValidName(newName)) newName = null; - function.LocalFunctions[method] = (newName, info.Declaration); + localFunction.Name = newName; } // Now generate names: var mapping = new Dictionary(ILVariableEqualityComparer.Instance); @@ -180,13 +179,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms v.Name = name; } } - foreach (var method in function.LocalFunctions.Keys.ToArray()) { - var info = function.LocalFunctions[method]; - var newName = info.Name; + foreach (var localFunction in function.LocalFunctions) { + var newName = localFunction.Name; if (newName == null) { newName = GetAlternativeName("f"); } - function.LocalFunctions[method] = (newName, info.Declaration); + localFunction.Name = newName; } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs index b8f1e99fc..b65420b0f 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs @@ -49,7 +49,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms foreach (var inst in function.Descendants) { cancellationToken.ThrowIfCancellationRequested(); if (inst is CallInstruction call && IsLocalFunctionMethod(call.Method)) { - if (function.Ancestors.OfType().Any(f => f.LocalFunctions.ContainsKey(call.Method))) + if (function.Ancestors.OfType().SelectMany(f => f.LocalFunctions).Any(f => f.Method == call.Method)) continue; if (!localFunctions.TryGetValue(call.Method, out var info)) { info = new List() { call }; @@ -58,7 +58,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms info.Add(call); } } else if (inst is LdFtn ldftn && ldftn.Parent is NewObj newObj && IsLocalFunctionMethod(ldftn.Method) && DelegateConstruction.IsDelegateConstruction(newObj)) { - if (function.Ancestors.OfType().Any(f => f.LocalFunctions.ContainsKey(ldftn.Method))) + if (function.Ancestors.OfType().SelectMany(f => f.LocalFunctions).Any(f => f.Method == ldftn.Method)) continue; context.StepStartGroup($"LocalFunctionDecompiler {ldftn.StartILOffset}", ldftn); if (!localFunctions.TryGetValue(ldftn.Method, out var info)) { @@ -72,40 +72,40 @@ namespace ICSharpCode.Decompiler.IL.Transforms } foreach (var (method, useSites) in localFunctions) { - var insertionPoint = FindInsertionPoint(useSites); - context.StepStartGroup($"LocalFunctionDecompiler {insertionPoint.StartILOffset}", insertionPoint); + context.StepStartGroup($"LocalFunctionDecompiler {useSites[0].StartILOffset}", useSites[0]); try { - TransformLocalFunction(function, method, useSites, (Block)insertionPoint.Parent, insertionPoint.ChildIndex + 1); + TransformLocalFunction(function, method, useSites); } finally { context.StepEndGroup(); } } - } - - static ILInstruction FindInsertionPoint(List useSites) - { - ILInstruction insertionPoint = null; - foreach (var call in useSites) { - if (insertionPoint == null) { - insertionPoint = GetStatement(call); - continue; - } - - var ancestor = FindCommonAncestorInstruction(insertionPoint, GetStatement(call)); - if (ancestor == null) - return null; - - insertionPoint = ancestor; + foreach (var f in function.LocalFunctions) { + // handle nested functions + var nestedContext = new ILTransformContext(context, f); + nestedContext.StepStartGroup("LocalFunctionDecompiler (nested functions)", f); + new LocalFunctionDecompiler().Run(f, nestedContext); + nestedContext.StepEndGroup(); } - return insertionPoint; + if (function.Kind == ILFunctionKind.TopLevelFunction) { + var movableFunctions = TreeTraversal.PostOrder(function, f => f.LocalFunctions) + .Where(f => f.Kind == ILFunctionKind.LocalFunction && f.DeclarationScope == null) + .ToArray(); + foreach (var f in movableFunctions) { + var parent = (ILFunction)f.Parent; + f.DeclarationScope = (BlockContainer)function.Body; + parent.LocalFunctions.Remove(f); + function.LocalFunctions.Add(f); + } + } } - static ILInstruction FindCommonAncestorInstruction(ILInstruction a, ILInstruction b) + static T FindCommonAncestorInstruction(ILInstruction a, ILInstruction b) + where T : ILInstruction { - var ancestorsOfB = new HashSet(b.Ancestors); - return a.Ancestors.FirstOrDefault(ancestorsOfB.Contains); + var ancestorsOfB = new HashSet(b.Ancestors.OfType()); + return a.Ancestors.OfType().FirstOrDefault(ancestorsOfB.Contains); } internal static bool IsClosureParameter(IParameter parameter) @@ -124,7 +124,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return inst; } - private ILFunction TransformLocalFunction(ILFunction parentFunction, IMethod targetMethod, List useSites, Block parent, int insertionPoint) + private ILFunction TransformLocalFunction(ILFunction parentFunction, IMethod targetMethod, List useSites) { var methodDefinition = context.PEFile.Metadata.GetMethodDefinition((MethodDefinitionHandle)targetMethod.MetadataToken); if (!methodDefinition.HasBody()) @@ -137,7 +137,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms var function = ilReader.ReadIL((MethodDefinitionHandle)targetMethod.MetadataToken, body, genericContext.Value, ILFunctionKind.LocalFunction, context.CancellationToken); // Embed the local function into the parent function's ILAst, so that "Show steps" can show // how the local function body is being transformed. - parent.Instructions.Insert(insertionPoint, function); + parentFunction.LocalFunctions.Add(function); + function.DeclarationScope = (BlockContainer)parentFunction.Body; function.CheckInvariant(ILPhase.Normal); var nestedContext = new ILTransformContext(context, function); function.RunTransforms(CSharpDecompiler.GetILTransforms().TakeWhile(t => !(t is LocalFunctionDecompiler)), nestedContext); @@ -147,11 +148,26 @@ namespace ICSharpCode.Decompiler.IL.Transforms var thisVar = function.Variables.SingleOrDefault(v => v.Index == -1 && v.Kind == VariableKind.Parameter); function.AcceptVisitor(new DelegateConstruction.ReplaceDelegateTargetVisitor(target, thisVar)); } - parentFunction.LocalFunctions.Add(function.Method, (function.Method.Name, function)); - // handle nested functions - nestedContext.StepStartGroup("LocalFunctionDecompiler (nested functions)", function); - new LocalFunctionDecompiler().Run(function, nestedContext); - nestedContext.StepEndGroup(); + function.DeclarationScope = null; + foreach (var useSite in useSites) { + for (int i = useSite.Arguments.Count - 1; i >= 0; i--) { + if (!useSite.Arguments[i].MatchLdLocRef(out var closureVar)) + break; + if (!TransformDisplayClassUsage.IsPotentialClosure(context, closureVar.Type.GetDefinition())) + break; + var instructions = closureVar.StoreInstructions.OfType() + .Concat(closureVar.AddressInstructions).OrderBy(inst => inst.StartILOffset); + var additionalScope = BlockContainer.FindClosestContainer(instructions.First()); + if (closureVar.CaptureScope == null) + closureVar.CaptureScope = additionalScope; + else + closureVar.CaptureScope = FindCommonAncestorInstruction(closureVar.CaptureScope, additionalScope); + if (function.DeclarationScope == null) + function.DeclarationScope = closureVar.CaptureScope; + else + function.DeclarationScope = FindCommonAncestorInstruction(function.DeclarationScope, closureVar.CaptureScope); + } + } return function; } @@ -190,6 +206,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms public static bool IsLocalFunctionMethod(IMethod method) { + if (method.MetadataToken.IsNil) + return false; return IsLocalFunctionMethod(method.ParentModule.PEFile, (MethodDefinitionHandle)method.MetadataToken); } diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs index f5d63ac10..eb2d39d65 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs @@ -66,10 +66,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (HandleMonoStateMachine(function, v, decompilationContext, f)) continue; if (IsClosure(v, out ITypeDefinition closureType, out var inst)) { - AddOrUpdateDisplayClass(f, v, closureType, inst); + AddOrUpdateDisplayClass(f, v, closureType, inst, localFunctionClosureParameter: false); } - if (f.Kind == ILFunctionKind.LocalFunction && v.Kind == VariableKind.Parameter && v.Index > -1 && f.Method.Parameters[v.Index.Value] is IParameter p && LocalFunctionDecompiler.IsClosureParameter(p)) { - AddOrUpdateDisplayClass(f, v, ((ByReferenceType)p.Type).ElementType.GetDefinition(), f.Body); + if (context.Settings.LocalFunctions && f.Kind == ILFunctionKind.LocalFunction && v.Kind == VariableKind.Parameter && v.Index > -1 && f.Method.Parameters[v.Index.Value] is IParameter p && LocalFunctionDecompiler.IsClosureParameter(p)) { + AddOrUpdateDisplayClass(f, v, ((ByReferenceType)p.Type).ElementType.GetDefinition(), f.Body, localFunctionClosureParameter: true); } } foreach (var displayClass in displayClasses.Values.OrderByDescending(d => d.Initializer.StartILOffset).ToArray()) { @@ -91,21 +91,23 @@ namespace ICSharpCode.Decompiler.IL.Transforms } } - private void AddOrUpdateDisplayClass(ILFunction f, ILVariable v, ITypeDefinition closureType, ILInstruction inst) + private void AddOrUpdateDisplayClass(ILFunction f, ILVariable v, ITypeDefinition closureType, ILInstruction inst, bool localFunctionClosureParameter) { var displayClass = displayClasses.Values.FirstOrDefault(c => c.Definition == closureType); + // TODO : figure out whether it is a mono compiled closure, without relying on the type name + bool isMono = f.StateMachineCompiledWithMono || closureType.Name.Contains("AnonStorey"); if (displayClass == null) { - // TODO : figure out whether it is a mono compiled closure, without relying on the type name - bool isMono = f.StateMachineCompiledWithMono || closureType.Name.Contains("AnonStorey"); displayClasses.Add(v, new DisplayClass { IsMono = isMono, Initializer = inst, Variable = v, Definition = closureType, Variables = new Dictionary(), - CaptureScope = isMono && IsMonoNestedCaptureScope(closureType) ? null : v.CaptureScope + CaptureScope = (isMono && IsMonoNestedCaptureScope(closureType)) || localFunctionClosureParameter ? null : v.CaptureScope }); } else { + if (displayClass.CaptureScope == null && !localFunctionClosureParameter) + displayClass.CaptureScope = isMono && IsMonoNestedCaptureScope(closureType) ? null : v.CaptureScope; displayClass.Variable = v; displayClass.Initializer = inst; displayClasses.Add(v, displayClass); @@ -124,7 +126,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms } } closureType = variable.Type.GetDefinition(); - if (closureType?.Kind == TypeKind.Struct && variable.HasInitialValue && IsPotentialClosure(this.context, closureType)) { + if (context.Settings.LocalFunctions && closureType?.Kind == TypeKind.Struct && variable.HasInitialValue && IsPotentialClosure(this.context, closureType)) { initializer = LocalFunctionDecompiler.GetStatement(variable.AddressInstructions.OrderBy(i => i.StartILOffset).First()); return true; } @@ -242,8 +244,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms { if (target.MatchLdLoc(out variable) || target.MatchLdLoca(out variable)) return true; - if (target.MatchAddressOf(out var load) && load.MatchLdLoc(out variable)) - return true; return false; } From 6f98ed541536a2c829f7bc142c1cd26f2a026f37 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Mon, 15 Jul 2019 11:50:22 +0200 Subject: [PATCH 07/23] Fix unit tests. --- ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs | 4 ++-- ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs b/ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs index dea1e49f5..d6f969ec3 100644 --- a/ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs +++ b/ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs @@ -71,8 +71,8 @@ namespace ICSharpCode.Decompiler.Tests { try { RunWithTest("ICSharpCode.Decompiler", "ICSharpCode.Decompiler.dll", "ICSharpCode.Decompiler.Tests.exe"); - } catch (CompilationFailedException) { - Assert.Ignore("C# 7 local functions not yet supported."); + } catch (TestRunFailedException) { + Assert.Ignore("CorrectnessTestRunner.UndocumentedExpressions fails on recompilation."); } } diff --git a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs index 95eadfcb0..38747065c 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs @@ -126,7 +126,7 @@ namespace ICSharpCode.Decompiler.IL public ILFunction(IMethod method, int codeSize, GenericContext genericContext, ILInstruction body, ILFunctionKind kind = ILFunctionKind.TopLevelFunction) : base(OpCode.ILFunction) { this.Method = method; - this.Name = method.Name; + this.Name = Method?.Name; this.CodeSize = codeSize; this.GenericContext = genericContext; this.Body = body; From b83cb41f2ef6e7aa150f5675ca6cee9a43d94a18 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Mon, 15 Jul 2019 17:58:53 +0200 Subject: [PATCH 08/23] Add pretty tests for local functions. --- .../CorrectnessTestRunner.cs | 6 - .../ICSharpCode.Decompiler.Tests.csproj | 2 +- .../PrettyTestRunner.cs | 6 + .../TestCases/Correctness/LocalFunctions.cs | 76 ------ .../TestCases/Pretty/LocalFunctions.cs | 226 ++++++++++++++++++ .../CSharp/StatementBuilder.cs | 2 +- .../Transforms/TransformDisplayClassUsage.cs | 7 +- 7 files changed, 236 insertions(+), 89 deletions(-) delete mode 100644 ICSharpCode.Decompiler.Tests/TestCases/Correctness/LocalFunctions.cs create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs diff --git a/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs b/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs index e5f200486..2239f26aa 100644 --- a/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs @@ -295,12 +295,6 @@ namespace ICSharpCode.Decompiler.Tests RunCS(options: options); } - [Test] - public void LocalFunctions([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions options) - { - RunCS(options: options); - } - void RunCS([CallerMemberName] string testName = null, CompilerOptions options = CompilerOptions.UseDebug) { string testFileName = testName + ".cs"; diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index 51ed72689..68a1c2613 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -83,7 +83,7 @@ - + diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index e7786ac67..fa1bb5dd8 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -184,6 +184,12 @@ namespace ICSharpCode.Decompiler.Tests }); } + [Test] + public void LocalFunctions([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions) + { + RunForLibrary(cscOptions: cscOptions); + } + [Test] public void PropertiesAndEvents([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/LocalFunctions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/LocalFunctions.cs deleted file mode 100644 index f05d95f27..000000000 --- a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/LocalFunctions.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LocalFunctions -{ - class LocalFunctions - { - int field; - - public static void Main(string[] args) - { - StaticContextNoCapture(10); - StaticContextSimpleCapture(10); - StaticContextCaptureInForLoop(10); - var inst = new LocalFunctions() { field = 10 }; - inst.ContextNoCapture(); - inst.ContextSimpleCapture(); - inst.ContextCaptureInForLoop(); - } - - public static void StaticContextNoCapture(int length) - { - for (int i = 0; i < length; i++) { - LocalWrite("Hello " + i); - } - - void LocalWrite(string s) => Console.WriteLine(s); - } - - public static void StaticContextSimpleCapture(int length) - { - for (int i = 0; i < length; i++) { - LocalWrite(); - } - - void LocalWrite() => Console.WriteLine("Hello " + length); - } - - public static void StaticContextCaptureInForLoop(int length) - { - for (int i = 0; i < length; i++) { - void LocalWrite() => Console.WriteLine("Hello " + i + "/" + length); - LocalWrite(); - } - } - - public void ContextNoCapture() - { - for (int i = 0; i < field; i++) { - LocalWrite("Hello " + i); - } - - void LocalWrite(string s) => Console.WriteLine(s); - } - - public void ContextSimpleCapture() - { - for (int i = 0; i < field; i++) { - LocalWrite(); - } - - void LocalWrite() => Console.WriteLine("Hello " + field); - } - - public void ContextCaptureInForLoop() - { - for (int i = 0; i < field; i++) { - void LocalWrite() => Console.WriteLine("Hello " + i + "/" + field); - LocalWrite(); - } - } - } -} diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs new file mode 100644 index 000000000..e06d9a892 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs @@ -0,0 +1,226 @@ +// Copyright (c) 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.Linq; + +namespace LocalFunctions +{ + internal class LocalFunctions + { + private int field; + + private static void Test(int x) + { + } + + private static int GetInt(string a) + { + return a.Length; + } + + private static string GetString(int a) + { + return a.ToString(); + } + + public static void StaticContextNoCapture(int length) + { + for (int i = 0; i < length; i++) { + LocalWrite("Hello " + i); + } + + void LocalWrite(string s) + { + Console.WriteLine(s); + } + } + + public static void StaticContextSimpleCapture(int length) + { + for (int i = 0; i < length; i++) { + LocalWrite(); + } + + void LocalWrite() + { + Console.WriteLine("Hello " + length); + } + } + + public static void StaticContextCaptureForLoopVariable(int length) + { + int i; + for (i = 0; i < length; i++) { + LocalWrite(); + } + void LocalWrite() + { + Console.WriteLine("Hello " + i + "/" + length); + } + } + + public void ContextNoCapture() + { + for (int i = 0; i < field; i++) { + LocalWrite("Hello " + i); + } + + void LocalWrite(string s) + { + Console.WriteLine(s); + } + } + + public void ContextSimpleCapture() + { + for (int i = 0; i < field; i++) { + LocalWrite(); + } + + void LocalWrite() + { + Console.WriteLine("Hello " + field); + } + } + + public void ContextCaptureForLoopVariable() + { + int i; + for (i = 0; i < field; i++) { + LocalWrite(); + } + void LocalWrite() + { + Console.WriteLine("Hello " + i + "/" + field); + } + } + + public void CapturedOutsideLoop() + { + int i = 0; + while (i < field) { + i = GetInt("asdf"); + LocalWrite(); + } + + void LocalWrite() + { + Console.WriteLine("Hello " + i + "/" + field); + } + } + + public void CapturedInForeachLoop(IEnumerable args) + { + foreach (string arg2 in args) { + string arg = arg2; + LocalWrite(); + void LocalWrite() + { + Console.WriteLine("Hello " + arg); + } + } + } + + public void Overloading() + { + Test(5); + LocalFunctions.Test(2); + + void Test(int x) + { + Console.WriteLine("x: {0}", x); + } + } + + public void NamedArgument() + { + Use(Get(1), Get(2), Get(3)); + Use(Get(1), c: Get(2), b: Get(3)); + + int Get(int i) + { + return i; + } + + void Use(int a, int b, int c) + { + Console.WriteLine(a + b + c); + } + } + + public static Func LambdaInLocalFunction() + { + int x = (int)Math.Pow(2.0, 10.0); + Enumerable.Range(1, 100).Select((Func)Transform); + return Create(); + + Func Create() + { + return () => x; + } + + int Transform(int y) + { + return 2 * y; + } + } + + public static Func MethodRef() + { + int x = (int)Math.Pow(2.0, 10.0); + Enumerable.Range(1, 100).Select((Func)F); + return null; + + int F(int y) + { + return x * y; + } + } + + public static int Fib(int i) + { + return FibHelper(i); + + int FibHelper(int n) + { + if (n <= 0) { + return 0; + } + + return FibHelper(n - 1) + FibHelper(n - 2); + } + } + + public static int NestedLocalFunctions(int i) + { + return A(); + + int A() + { + double x = Math.Pow(10.0, 2.0); + return B(); + + int B() + { + return i + (int)x; + } + } + } + } +} diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs index 73b07c70b..bd72ac66d 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -952,7 +952,7 @@ namespace ICSharpCode.Decompiler.CSharp void DeclareLocalFunctions(ILFunction currentFunction, BlockContainer container, BlockStatement blockStatement) { - foreach (var localFunction in currentFunction.LocalFunctions) { + foreach (var localFunction in currentFunction.LocalFunctions.OrderBy(f => f.Name)) { if (localFunction.DeclarationScope != container) continue; blockStatement.Add(TranslateFunction(localFunction)); diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs index eb2d39d65..38020dc3b 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs @@ -263,14 +263,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (inst.Value.MatchLdLoc(out var closureVariable) && displayClasses.TryGetValue(closureVariable, out var displayClass)) { displayClasses[inst.Variable] = displayClass; instructionsToRemove.Add(inst); + } else if (inst.Variable.Kind == VariableKind.Local && inst.Variable.IsSingleDefinition && inst.Variable.LoadCount == 0 && inst.Value is StLoc) { + inst.ReplaceWith(inst.Value); } } - protected internal override void VisitLdLoc(LdLoc inst) - { - base.VisitLdLoc(inst); - } - protected internal override void VisitStObj(StObj inst) { base.VisitStObj(inst); From 01e42b772c60260c0c0787eca96bf5fdb6d469cc Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Mon, 15 Jul 2019 18:37:59 +0200 Subject: [PATCH 09/23] Fix local functions inside lambdas. --- .../TestCases/Pretty/LocalFunctions.cs | 12 ++++++++++++ .../IL/Transforms/LocalFunctionDecompiler.cs | 5 +++++ 2 files changed, 17 insertions(+) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs index e06d9a892..4c39b485e 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs @@ -222,5 +222,17 @@ namespace LocalFunctions } } } + + public static int LocalFunctionInLambda(IEnumerable xs) + { + return xs.First(delegate(int x) { + return Do(); + + bool Do() + { + return x == 3; + } + }); + } } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs index b65420b0f..ab3fd8523 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs @@ -169,6 +169,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms } } + if (function.DeclarationScope != null && function.DeclarationScope.Parent is ILFunction betterParentFunction) { + parentFunction.LocalFunctions.Remove(function); + betterParentFunction.LocalFunctions.Add(function); + } + return function; } From 19a78987862a16a1c66921947ded38557ec42e63 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Mon, 15 Jul 2019 23:34:23 +0200 Subject: [PATCH 10/23] Add addressof(ldloc) => ldloca transform to EarlyExpressionTransforms --- .../Transforms/EarlyExpressionTransforms.cs | 22 +++++++++++++++++++ .../IL/Transforms/ExpressionTransforms.cs | 17 +------------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/EarlyExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/EarlyExpressionTransforms.cs index f60e2d490..5ee5b2fbe 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/EarlyExpressionTransforms.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/EarlyExpressionTransforms.cs @@ -66,6 +66,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms protected internal override void VisitLdObj(LdObj inst) { base.VisitLdObj(inst); + AddressOfLdLocToLdLoca(inst, context); LdObjToLdLoc(inst, context); } @@ -83,6 +84,27 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; } + internal static void AddressOfLdLocToLdLoca(LdObj inst, ILTransformContext context) + { + // ldobj(...(addressof(ldloc V))) where ... can be zero or more ldflda instructions + // => + // ldobj(...(ldloca V)) + var temp = inst.Target; + var range = temp.ILRanges; + while (temp.MatchLdFlda(out var ldfldaTarget, out _)) { + temp = ldfldaTarget; + range = range.Concat(temp.ILRanges); + } + if (temp.MatchAddressOf(out var addressOfTarget) && addressOfTarget.MatchLdLoc(out var v)) { + context.Step($"ldobj(...(addressof(ldloca {v.Name}))) => ldobj(...(ldloca {v.Name}))", inst); + var replacement = new LdLoca(v).WithILRange(addressOfTarget); + foreach (var r in range) { + replacement = replacement.WithILRange(r); + } + temp.ReplaceWith(replacement); + } + } + protected internal override void VisitCall(Call inst) { var expr = HandleCall(inst, context); diff --git a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs index 93575cae4..834e0d804 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs @@ -393,23 +393,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms protected internal override void VisitLdObj(LdObj inst) { base.VisitLdObj(inst); + EarlyExpressionTransforms.AddressOfLdLocToLdLoca(inst, context); EarlyExpressionTransforms.LdObjToLdLoc(inst, context); - // ldobj(...(addressof(ldloc V)) where ... can be zero or more ldflda instructions - // => - // ldobj(...(ldloca V)) - var temp = inst.Target; - var range = temp.ILRanges; - while (temp.MatchLdFlda(out var ldfldaTarget, out _)) { - temp = ldfldaTarget; - range = range.Concat(temp.ILRanges); - } - if (temp.MatchAddressOf(out var addressOfTarget) && addressOfTarget.MatchLdLoc(out var v)) { - var replacement = new LdLoca(v).WithILRange(addressOfTarget); - foreach (var r in range) { - replacement = replacement.WithILRange(r); - } - temp.ReplaceWith(replacement); - } } protected internal override void VisitStObj(StObj inst) From bb066dbd04565b5508ae6624556cd1be0586fa24 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Mon, 15 Jul 2019 23:35:04 +0200 Subject: [PATCH 11/23] Additional test (deactivated) --- .../TestCases/Pretty/LocalFunctions.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs index 4c39b485e..33bcab1e2 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs @@ -234,5 +234,16 @@ namespace LocalFunctions } }); } + //public static void LocalFunctionInUsing() + //{ + // using (MemoryStream memoryStream = new MemoryStream()) { + // Do(); + + // void Do() + // { + // memoryStream.WriteByte(42); + // } + // } + //} } } From 0719aa9f08f1df4514c9dcd504c744ecb088b5a4 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Wed, 17 Jul 2019 18:20:12 +0200 Subject: [PATCH 12/23] Enable DecompilerSettings.IntroduceLocalFunctions --- ICSharpCode.Decompiler/CSharp/StatementBuilder.cs | 3 ++- ICSharpCode.Decompiler/DecompilerSettings.cs | 11 ++++------- ILSpy/Properties/Resources.Designer.cs | 6 +++--- ILSpy/Properties/Resources.resx | 4 ++-- ILSpy/Properties/Resources.zh-Hans.resx | 6 +++--- 5 files changed, 14 insertions(+), 16 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs index bd72ac66d..25d8658ba 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -726,7 +726,8 @@ namespace ICSharpCode.Decompiler.CSharp /// /// Determines whether storeInst.Variable is only assigned once and used only inside . - /// Loads by reference (ldloca) are only allowed in the context of this pointer in call instructions. + /// Loads by reference (ldloca) are only allowed in the context of this pointer in call instructions, + /// or as target of ldobj. /// (This only applies to value types.) /// bool VariableIsOnlyUsedInBlock(StLoc storeInst, BlockContainer usingContainer) diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index f6e7a94dd..ed6f98191 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -945,19 +945,16 @@ namespace ICSharpCode.Decompiler bool localFunctions = true; /// - /// Gets/Sets whether C# 7.0 local functions should be used. - /// Note: this language feature is currently not implemented and this setting is always false. + /// Gets/Sets whether C# 7.0 local functions should be transformed. /// [Category("C# 7.0 / VS 2017")] - [Description("DecompilerSettings.IntroduceLocalFunctionsNOTIMPLEMENTED")] - [Browsable(false)] + [Description("DecompilerSettings.IntroduceLocalFunctions")] public bool LocalFunctions { get { return localFunctions; } set { if (localFunctions != value) { - throw new NotImplementedException("C# 7.0 local functions are not implemented!"); - //localFunctions = value; - //OnPropertyChanged(); + localFunctions = value; + OnPropertyChanged(); } } } diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs index 5c86e06e2..9a8af3ff6 100644 --- a/ILSpy/Properties/Resources.Designer.cs +++ b/ILSpy/Properties/Resources.Designer.cs @@ -700,11 +700,11 @@ namespace ICSharpCode.ILSpy.Properties { } /// - /// Looks up a localized string similar to Introduce local functions (NOT IMPLEMENTED!). + /// Looks up a localized string similar to Introduce local functions. /// - public static string DecompilerSettings_IntroduceLocalFunctionsNOTIMPLEMENTED { + public static string DecompilerSettings_IntroduceLocalFunctions { get { - return ResourceManager.GetString("DecompilerSettings.IntroduceLocalFunctionsNOTIMPLEMENTED", resourceCulture); + return ResourceManager.GetString("DecompilerSettings.IntroduceLocalFunctions", resourceCulture); } } diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx index 0128389d9..60701dc91 100644 --- a/ILSpy/Properties/Resources.resx +++ b/ILSpy/Properties/Resources.resx @@ -702,8 +702,8 @@ Remove optional arguments, if possible - - Introduce local functions (NOT IMPLEMENTED!) + + Introduce local functions Nullable reference types diff --git a/ILSpy/Properties/Resources.zh-Hans.resx b/ILSpy/Properties/Resources.zh-Hans.resx index 4ab21794f..4f04dba0b 100644 --- a/ILSpy/Properties/Resources.zh-Hans.resx +++ b/ILSpy/Properties/Resources.zh-Hans.resx @@ -676,7 +676,7 @@ IsByRefLikeAttribute应替换为结构上的 "ref" 修饰符 - IsReadOnlyAttribute 应替为结构参数上的 "readonly"/"中的修饰符 + IsReadOnlyAttribute 应替为结构参数上的 "readonly"/"in"中的修饰符 类型参数上的IsUnmanagedAttribute 应替换为 "非托管" 约束 @@ -702,8 +702,8 @@ 如果可能, 删除可选参数 - - 引入本地功能 (未实现!) + + 引入本地功能 C# 7.0 本地函数未实现! From f3f38783df5464a349126a140eb376d8fbeeb657 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Wed, 17 Jul 2019 21:45:52 +0200 Subject: [PATCH 13/23] ICSharpCode.Decompiler roundtrip test now completes --- ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs | 6 +----- .../IL/Transforms/LocalFunctionDecompiler.cs | 4 ++-- .../IL/Transforms/TransformDisplayClassUsage.cs | 2 +- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs b/ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs index d6f969ec3..975c92511 100644 --- a/ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs +++ b/ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs @@ -69,11 +69,7 @@ namespace ICSharpCode.Decompiler.Tests [Test] public void ICSharpCode_Decompiler() { - try { - RunWithTest("ICSharpCode.Decompiler", "ICSharpCode.Decompiler.dll", "ICSharpCode.Decompiler.Tests.exe"); - } catch (TestRunFailedException) { - Assert.Ignore("CorrectnessTestRunner.UndocumentedExpressions fails on recompilation."); - } + RunWithTest("ICSharpCode.Decompiler", "ICSharpCode.Decompiler.dll", "ICSharpCode.Decompiler.Tests.exe"); } [Test] diff --git a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs index ab3fd8523..822054648 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs @@ -256,8 +256,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms } /// - /// Newer Roslyn versions use the format "<callerName>g__functionName|x_y" - /// Older versions use "<callerName>g__functionNamex_y" + /// Newer Roslyn versions use the format "<callerName>g__functionName|x_y" + /// Older versions use "<callerName>g__functionNamex_y" /// static readonly Regex functionNameRegex = new Regex(@"^<(.*)>g__([^\|]*)\|{0,1}\d+(_\d+)?$", RegexOptions.Compiled); diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs index 6caa8ed41..027d96722 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs @@ -338,7 +338,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (inst.Parent is LdObj || inst.Parent is StObj) return; // Get display class info - if (!(inst.Target is LdLoc displayClassLoad && displayClasses.TryGetValue(displayClassLoad.Variable, out var displayClass))) + if (!IsDisplayClassLoad(inst.Target, out var displayClassLoad) || !displayClasses.TryGetValue(displayClassLoad, out var displayClass)) return; var field = (IField)inst.Field.MemberDefinition; if (!displayClass.Variables.TryGetValue(field, out DisplayClassVariable info)) { From 37e14f43e3cadf9122032d66923d2960e40585d1 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Thu, 18 Jul 2019 20:13:37 +0200 Subject: [PATCH 14/23] Refactor/Clean up local functions representation in type system. --- .../TestCases/Pretty/LocalFunctions.cs | 10 +- ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 68 ++-- .../CSharp/ExpressionBuilder.cs | 3 +- .../Resolver/CSharpInvocationResolveResult.cs | 14 - .../LocalFunctionReferenceResolveResult.cs | 37 --- .../CSharp/Resolver/ReducedExtensionMethod.cs | 306 ------------------ .../CSharp/StatementBuilder.cs | 2 +- .../ICSharpCode.Decompiler.csproj | 3 +- .../IL/Instructions/ILFunction.cs | 69 +++- .../IL/Transforms/DelegateConstruction.cs | 5 +- .../IL/Transforms/LocalFunctionDecompiler.cs | 177 +++++++--- .../Output/TextTokenWriter.cs | 5 +- ICSharpCode.Decompiler/TypeSystem/IMethod.cs | 4 +- .../TypeSystem/Implementation/FakeMember.cs | 1 + .../Implementation/LocalFunctionMethod.cs | 139 ++++++++ .../Implementation/MetadataMethod.cs | 1 + .../Implementation/SpecializedMethod.cs | 8 +- .../TypeSystem/VarArgInstanceMethod.cs | 2 + 18 files changed, 398 insertions(+), 456 deletions(-) delete mode 100644 ICSharpCode.Decompiler/CSharp/Resolver/LocalFunctionReferenceResolveResult.cs delete mode 100644 ICSharpCode.Decompiler/CSharp/Resolver/ReducedExtensionMethod.cs create mode 100644 ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs index 33bcab1e2..806e9a57f 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs @@ -167,27 +167,21 @@ namespace LocalFunctions public static Func LambdaInLocalFunction() { int x = (int)Math.Pow(2.0, 10.0); - Enumerable.Range(1, 100).Select((Func)Transform); return Create(); Func Create() { return () => x; } - - int Transform(int y) - { - return 2 * y; - } } public static Func MethodRef() { int x = (int)Math.Pow(2.0, 10.0); - Enumerable.Range(1, 100).Select((Func)F); + Enumerable.Range(1, 100).Select(LocalFunction); return null; - int F(int y) + int LocalFunction(int y) { return x * y; } diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index c674d8589..3cc2ed7e2 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -38,7 +38,6 @@ namespace ICSharpCode.Decompiler.CSharp { public OpCode CallOpCode; public bool NeedsBoxingConversion; - public bool IsLocalFunction; } struct ArgumentList @@ -53,18 +52,13 @@ namespace ICSharpCode.Decompiler.CSharp public bool AddNamesToPrimitiveValues; public bool IsExpandedForm; - public int LocalFunctionParameterCount; public int Length => Arguments.Length; private int GetActualArgumentCount() { - if (LocalFunctionParameterCount < 0 && FirstOptionalArgumentIndex < 0) - return Arguments.Length; - if (LocalFunctionParameterCount < 0) - return FirstOptionalArgumentIndex; if (FirstOptionalArgumentIndex < 0) - return LocalFunctionParameterCount; - return Math.Min(FirstOptionalArgumentIndex, LocalFunctionParameterCount); + return Arguments.Length; + return FirstOptionalArgumentIndex; } public IEnumerable GetArgumentResolveResults(int skipCount = 0) @@ -180,20 +174,19 @@ namespace ICSharpCode.Decompiler.CSharp { // Used for Call, CallVirt and NewObj var expectedTargetDetails = new ExpectedTargetDetails { - CallOpCode = callOpCode, - IsLocalFunction = expressionBuilder.IsLocalFunction(method) + CallOpCode = callOpCode }; ILFunction localFunction = null; - if (expectedTargetDetails.IsLocalFunction && (localFunction = expressionBuilder.ResolveLocalFunction(method)) == null) { - expectedTargetDetails.IsLocalFunction = false; + if (method.IsLocalFunction) { + localFunction = expressionBuilder.ResolveLocalFunction(method); } TranslatedExpression target; if (callOpCode == OpCode.NewObj) { target = default(TranslatedExpression); // no target - } else if (expectedTargetDetails.IsLocalFunction) { + } else if (method.IsLocalFunction) { target = new IdentifierExpression(localFunction.Name) .WithoutILInstruction() - .WithRR(new LocalFunctionReferenceResolveResult(localFunction)); + .WithRR(ToMethodGroup(method, localFunction)); } else { target = expressionBuilder.TranslateTarget( callArguments.FirstOrDefault(), @@ -221,17 +214,7 @@ namespace ICSharpCode.Decompiler.CSharp var argumentList = BuildArgumentList(expectedTargetDetails, target.ResolveResult, method, firstParamIndex, callArguments, argumentToParameterMap); - if (expectedTargetDetails.IsLocalFunction) { - int parameterCount = 0; - foreach (var param in method.Parameters) { - if (param.IsRef && param.Type is ByReferenceType byRef) { - var type = byRef.ElementType.GetDefinition(); - if (type != null && type.IsCompilerGenerated()) - break; - } - parameterCount++; - } - argumentList.LocalFunctionParameterCount = parameterCount; + if (method.IsLocalFunction) { return new InvocationExpression(target, argumentList.GetArgumentExpressions()) .WithRR(new CSharpInvocationResolveResult(target.ResolveResult, method, argumentList.GetArgumentResolveResults().ToList(), isExpandedForm: argumentList.IsExpandedForm, isLocalFunctionInvocation: true)); @@ -412,7 +395,7 @@ namespace ICSharpCode.Decompiler.CSharp argumentList.ArgumentNames = null; argumentList.AddNamesToPrimitiveValues = false; var transform = GetRequiredTransformationsForCall(expectedTargetDetails, method, ref unused, - ref argumentList, CallTransformation.None, out IParameterizedMember foundMethod); + ref argumentList, CallTransformation.None, out _); Debug.Assert(transform == CallTransformation.None || transform == CallTransformation.NoOptionalArgumentAllowed); // Calls with only one argument do not need an array initializer expression to wrap them. @@ -591,12 +574,10 @@ namespace ICSharpCode.Decompiler.CSharp } } - private ArgumentList BuildArgumentList(ExpectedTargetDetails expectedTargetDetails, ResolveResult target, IMethod method, int firstParamIndex, IReadOnlyList callArguments, IReadOnlyList argumentToParameterMap) { ArgumentList list = new ArgumentList(); - list.LocalFunctionParameterCount = -1; // Translate arguments to the expected parameter types var arguments = new List(method.Parameters.Count); @@ -775,7 +756,7 @@ namespace ICSharpCode.Decompiler.CSharp if (expressionBuilder.HidesVariableWithName(method.Name)) { requireTarget = true; } else { - if (expectedTargetDetails.IsLocalFunction) + if (method.IsLocalFunction) requireTarget = false; else if (method.IsStatic) requireTarget = !expressionBuilder.IsCurrentOrContainingType(method.DeclaringTypeDefinition) || method.Name == ".cctor"; @@ -1139,8 +1120,7 @@ namespace ICSharpCode.Decompiler.CSharp } var op = AssignmentOperatorType.Assign; - var parentEvent = method.AccessorOwner as IEvent; - if (parentEvent != null) { + if (method.AccessorOwner is IEvent parentEvent) { if (method.Equals(parentEvent.AddAccessor)) { op = AssignmentOperatorType.Add; } @@ -1243,15 +1223,14 @@ namespace ICSharpCode.Decompiler.CSharp IType targetType; bool requireTarget; var expectedTargetDetails = new ExpectedTargetDetails { - CallOpCode = inst.OpCode, - IsLocalFunction = expressionBuilder.IsLocalFunction(method) + CallOpCode = inst.OpCode }; ResolveResult result = null; string methodName; - if (expectedTargetDetails.IsLocalFunction) { + if (method.IsLocalFunction) { requireTarget = false; var localFunction = expressionBuilder.ResolveLocalFunction(method); - result = new LocalFunctionReferenceResolveResult(localFunction); + result = ToMethodGroup(method, localFunction); target = default; methodName = localFunction.Name; } else { @@ -1321,15 +1300,32 @@ namespace ICSharpCode.Decompiler.CSharp .WithRR(result); targetExpression = ide; } + if (target.ResolveResult != null) { + result = new MemberResolveResult(target.ResolveResult, method); + } var oce = new ObjectCreateExpression(expressionBuilder.ConvertType(inst.Method.DeclaringType), targetExpression) .WithILInstruction(inst) .WithRR(new ConversionResolveResult( inst.Method.DeclaringType, - target.ResolveResult != null ? new MemberResolveResult(target.ResolveResult, method) : result, + result, Conversion.MethodGroupConversion(method, func.OpCode == OpCode.LdVirtFtn, false))); return oce; } + static MethodGroupResolveResult ToMethodGroup(IMethod method, ILFunction localFunction) + { + return new MethodGroupResolveResult( + null, + localFunction.Name, + new[] { + new MethodListWithDeclaringType( + method.DeclaringType, + new IParameterizedMember[] { method } + ) + }, EmptyList.Instance + ); + } + internal TranslatedExpression CallWithNamedArgs(Block block) { Debug.Assert(block.Kind == BlockKind.CallWithNamedArgs); diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index c6f5dca21..5fb95a319 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -214,7 +214,8 @@ namespace ICSharpCode.Decompiler.CSharp internal ILFunction ResolveLocalFunction(IMethod method) { - Debug.Assert(IsLocalFunction(method)); + Debug.Assert(method.IsLocalFunction); + method = method.ReducedFrom; foreach (var parent in currentFunction.Ancestors.OfType()) { var definition = parent.LocalFunctions.FirstOrDefault(f => f.Method == method); if (definition != null) { diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpInvocationResolveResult.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpInvocationResolveResult.cs index 823887940..784c6b9e5 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpInvocationResolveResult.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpInvocationResolveResult.cs @@ -53,20 +53,6 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver public readonly bool IsLocalFunctionInvocation; readonly IReadOnlyList argumentToParameterMap; - - /// - /// If IsExtensionMethodInvocation is true this property holds the reduced method. - /// - IMethod reducedMethod; - public IMethod ReducedMethod { - get { - if (!IsExtensionMethodInvocation) - return null; - if (reducedMethod == null && Member is IMethod) - reducedMethod = new ReducedExtensionMethod ((IMethod)Member); - return reducedMethod; - } - } public CSharpInvocationResolveResult( ResolveResult targetResult, IParameterizedMember member, diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/LocalFunctionReferenceResolveResult.cs b/ICSharpCode.Decompiler/CSharp/Resolver/LocalFunctionReferenceResolveResult.cs deleted file mode 100644 index e5253eba4..000000000 --- a/ICSharpCode.Decompiler/CSharp/Resolver/LocalFunctionReferenceResolveResult.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2019 Siegfried Pammer -// -// 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.Text; -using ICSharpCode.Decompiler.IL; -using ICSharpCode.Decompiler.Semantics; -using ICSharpCode.Decompiler.TypeSystem; - -namespace ICSharpCode.Decompiler.CSharp.Resolver -{ - class LocalFunctionReferenceResolveResult : ResolveResult - { - public readonly ILFunction Function; - - public LocalFunctionReferenceResolveResult(ILFunction function) - : base(SpecialType.NoType) - { - this.Function = function; - } - } -} diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/ReducedExtensionMethod.cs b/ICSharpCode.Decompiler/CSharp/Resolver/ReducedExtensionMethod.cs deleted file mode 100644 index 185e626c0..000000000 --- a/ICSharpCode.Decompiler/CSharp/Resolver/ReducedExtensionMethod.cs +++ /dev/null @@ -1,306 +0,0 @@ -// -// ReducedExtensionMethod.cs -// -// Author: -// Mike Krüger -// -// Copyright (c) 2013 Xamarin Inc. (http://xamarin.com) -// -// 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.Linq; -using System.Reflection; -using ICSharpCode.Decompiler.TypeSystem; - -namespace ICSharpCode.Decompiler.CSharp.Resolver -{ - /// - /// An invocated extension method hides the extension parameter in its parameter list. - /// It's used to hide the internals of extension method invocation in certain situation to simulate the - /// syntactic way of writing extension methods on semantic level. - /// - public class ReducedExtensionMethod : IMethod - { - readonly IMethod baseMethod; - - public ReducedExtensionMethod(IMethod baseMethod) - { - this.baseMethod = baseMethod; - } - - public bool Equals(IMember obj, TypeVisitor typeNormalization) - { - var other = obj as ReducedExtensionMethod; - if (other == null) - return false; - return baseMethod.Equals(other.baseMethod, typeNormalization); - } - - public override bool Equals(object obj) - { - var other = obj as ReducedExtensionMethod; - if (other == null) - return false; - return baseMethod.Equals(other.baseMethod); - } - - public override int GetHashCode() - { - unchecked { - return baseMethod.GetHashCode() + 1; - } - } - - public override string ToString() - { - return string.Format("[ReducedExtensionMethod: ReducedFrom={0}]", ReducedFrom); - } - - #region IMember implementation - public IMember MemberDefinition { - get { - return baseMethod.MemberDefinition; - } - } - - public IType ReturnType { - get { - return baseMethod.ReturnType; - } - } - - public IEnumerable ExplicitlyImplementedInterfaceMembers { - get { - return baseMethod.ExplicitlyImplementedInterfaceMembers; - } - } - - public bool IsExplicitInterfaceImplementation { - get { - return baseMethod.IsExplicitInterfaceImplementation; - } - } - - public bool IsVirtual { - get { - return baseMethod.IsVirtual; - } - } - - public bool IsOverride { - get { - return baseMethod.IsOverride; - } - } - - public bool IsOverridable { - get { - return baseMethod.IsOverridable; - } - } - - public TypeParameterSubstitution Substitution { - get { - return baseMethod.Substitution; - } - } - - public IMethod Specialize(TypeParameterSubstitution substitution) - { - return new ReducedExtensionMethod((IMethod)baseMethod.Specialize(substitution)); - } - - IMember IMember.Specialize(TypeParameterSubstitution substitution) - { - return Specialize(substitution); - } - - #endregion - - #region IMethod implementation - - public IReadOnlyList TypeParameters { - get { - return baseMethod.TypeParameters; - } - } - - public bool IsExtensionMethod { - get { - return true; - } - } - - public bool IsConstructor { - get { - return baseMethod.IsConstructor; - } - } - - public bool IsDestructor { - get { - return baseMethod.IsDestructor; - } - } - - public bool IsOperator { - get { - return baseMethod.IsOperator; - } - } - - public bool HasBody { - get { - return baseMethod.HasBody; - } - } - - public bool IsAccessor => baseMethod.IsAccessor; - public IMember AccessorOwner => baseMethod.AccessorOwner; - public MethodSemanticsAttributes AccessorKind => baseMethod.AccessorKind; - - public IMethod ReducedFrom { - get { - return baseMethod; - } - } - - public IReadOnlyList TypeArguments { - get { - return baseMethod.TypeArguments; - } - } - #endregion - - #region IParameterizedMember implementation - List parameters; - public IReadOnlyList Parameters { - get { - if (parameters == null) - parameters = new List (baseMethod.Parameters.Skip (1)); - return parameters; - } - } - - #endregion - - #region IEntity implementation - - public System.Reflection.Metadata.EntityHandle MetadataToken => baseMethod.MetadataToken; - - public SymbolKind SymbolKind { - get { - return baseMethod.SymbolKind; - } - } - - public ITypeDefinition DeclaringTypeDefinition { - get { - return baseMethod.DeclaringTypeDefinition; - } - } - - public IType DeclaringType { - get { - return baseMethod.DeclaringType; - } - } - - public IModule ParentModule { - get { - return baseMethod.ParentModule; - } - } - - IEnumerable IEntity.GetAttributes() => baseMethod.GetAttributes(); - IEnumerable IMethod.GetReturnTypeAttributes() => baseMethod.GetReturnTypeAttributes(); - bool IMethod.ReturnTypeIsRefReadOnly => baseMethod.ReturnTypeIsRefReadOnly; - - public bool IsStatic { - get { - return false; - } - } - - public bool IsAbstract { - get { - return baseMethod.IsAbstract; - } - } - - public bool IsSealed { - get { - return baseMethod.IsSealed; - } - } - - #endregion - - #region IHasAccessibility implementation - - public Accessibility Accessibility { - get { - return baseMethod.Accessibility; - } - } - - #endregion - - #region INamedElement implementation - - public string FullName { - get { - return baseMethod.FullName; - } - } - - public string Name { - get { - return baseMethod.Name; - } - } - - public string ReflectionName { - get { - return baseMethod.ReflectionName; - } - } - - public string Namespace { - get { - return baseMethod.Namespace; - } - } - - #endregion - - #region ICompilationProvider implementation - - public ICompilation Compilation { - get { - return baseMethod.Compilation; - } - } - - #endregion - } -} - diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs index 25d8658ba..9730fc1f9 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -968,7 +968,7 @@ namespace ICSharpCode.Decompiler.CSharp stmt.Parameters.AddRange(exprBuilder.MakeParameters(function.Parameters, function)); stmt.ReturnType = tsab.ConvertType(function.Method.ReturnType); stmt.Body = nestedBuilder.ConvertAsBlock(function.Body); - stmt.AddAnnotation(new LocalFunctionReferenceResolveResult(function)); + stmt.AddAnnotation(new MemberResolveResult(null, function.ReducedMethod)); return stmt; } } diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 85dac7988..0de498900 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -60,7 +60,6 @@ - @@ -224,7 +223,7 @@ - + diff --git a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs index 49d7b8011..29370678b 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs @@ -29,8 +29,30 @@ namespace ICSharpCode.Decompiler.IL { partial class ILFunction { + /// + /// Gets the method definition from metadata. + /// May be null for functions that were not constructed from metadata, + /// e.g., expression trees. + /// public readonly IMethod Method; + + /// + /// Gets the generic context of this function. + /// public readonly GenericContext GenericContext; + + /// + /// Gets the name of this function, usually this returns the name from metadata. + /// + /// For local functions: + /// This is the name that is used to declare and use the function. + /// It may not conflict with the names of local variables of ancestor functions + /// and may be overwritten by the AssignVariableNames step. + /// + /// For top-level functions, delegates and expressions trees modifying this usually + /// has no effect, as the name should not be used in the final AST construction. + /// + /// public string Name; /// @@ -38,6 +60,10 @@ namespace ICSharpCode.Decompiler.IL /// Note: after async/await transform, this is the code size of the MoveNext function. /// public int CodeSize; + + /// + /// List of ILVariables used in this function. + /// public readonly ILVariableCollection Variables; /// @@ -60,6 +86,9 @@ namespace ICSharpCode.Decompiler.IL /// public bool IsIterator; + /// + /// Gets whether the YieldReturnDecompiler determined that the Mono C# compiler was used to compile this function. + /// public bool StateMachineCompiledWithMono; /// @@ -79,6 +108,11 @@ namespace ICSharpCode.Decompiler.IL /// public IMethod MoveNextMethod; + /// + /// If this function is a local function, this field stores the reduced version of the function. + /// + internal TypeSystem.Implementation.LocalFunctionMethod ReducedMethod; + internal DebugInfo.AsyncDebugInfo AsyncDebugInfo; int ctorCallStart = int.MinValue; @@ -105,24 +139,43 @@ namespace ICSharpCode.Decompiler.IL /// /// If this is an expression tree or delegate, returns the expression tree type Expression{T} or T. /// T is the delegate type that matches the signature of this method. + /// Otherwise this must be null. /// public IType DelegateType; ILFunctionKind kind; + /// + /// Gets which kind of function this is. + /// public ILFunctionKind Kind { get => kind; - set { + internal set { if (kind == ILFunctionKind.TopLevelFunction || kind == ILFunctionKind.LocalFunction) throw new InvalidOperationException("ILFunction.Kind of a top-level or local function may not be changed."); kind = value; } } + /// + /// Return type of this function. + /// Might be null, if this function was not created from metadata. + /// public readonly IType ReturnType; + /// + /// List of parameters of this function. + /// Might be null, if this function was not created from metadata. + /// public readonly IReadOnlyList Parameters; + /// + /// Constructs a new ILFunction from the given metadata and with the given ILAst body. + /// + /// + /// Use to create ILAst. + /// may be null. + /// public ILFunction(IMethod method, int codeSize, GenericContext genericContext, ILInstruction body, ILFunctionKind kind = ILFunctionKind.TopLevelFunction) : base(OpCode.ILFunction) { this.Method = method; @@ -137,7 +190,10 @@ namespace ICSharpCode.Decompiler.IL this.kind = kind; } - public ILFunction(IType returnType, IReadOnlyList parameters, GenericContext genericContext, ILInstruction body) : base(OpCode.ILFunction) + /// + /// This constructor is only to be used by the TransformExpressionTrees step. + /// + internal ILFunction(IType returnType, IReadOnlyList parameters, GenericContext genericContext, ILInstruction body) : base(OpCode.ILFunction) { this.GenericContext = genericContext; this.Body = body; @@ -366,14 +422,23 @@ namespace ICSharpCode.Decompiler.IL /// /// ILFunction is a delegate or lambda expression. /// + /// + /// This kind is introduced by the DelegateConstruction and TransformExpressionTrees steps in the decompiler pipeline. + /// Delegate, /// /// ILFunction is an expression tree lambda. /// + /// + /// This kind is introduced by the TransformExpressionTrees step in the decompiler pipeline. + /// ExpressionTree, /// /// ILFunction is a C# 7.0 local function. /// + /// + /// This kind is introduced by the LocalFunctionDecompiler step in the decompiler pipeline. + /// LocalFunction } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs b/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs index 4b03fcfe8..b3f617029 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs @@ -24,7 +24,10 @@ using ICSharpCode.Decompiler.TypeSystem; namespace ICSharpCode.Decompiler.IL.Transforms { - public class DelegateConstruction : IILTransform + /// + /// + /// + class DelegateConstruction : IILTransform { ILTransformContext context; ITypeResolveContext decompilationContext; diff --git a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs index 822054648..228611a51 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs @@ -28,38 +28,45 @@ using System.Text.RegularExpressions; using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.Decompiler.TypeSystem.Implementation; using ICSharpCode.Decompiler.Util; namespace ICSharpCode.Decompiler.IL.Transforms { + /// + /// Decompiler step for C# 7.0 local functions + /// class LocalFunctionDecompiler : IILTransform { ILTransformContext context; - ITypeResolveContext decompilationContext; + /// + /// The transform works like this: + /// + /// + /// local functions can either be used in method calls, i.e., call and callvirt instructions, + /// or can be used as part of the "delegate construction" pattern, i.e., newobj Delegate(<target-expression>, ldftn <method>). Note that this pattern would also be possible with ldvirtftn, but I haven't yet disovered a case where a compiler generates such code. + /// + /// As local functions can be declared practically anywhere, we have to take a look at all use-sites and infer the declaration location from that. + /// public void Run(ILFunction function, ILTransformContext context) { if (!context.Settings.LocalFunctions) return; this.context = context; - this.decompilationContext = new SimpleTypeResolveContext(function.Method); var localFunctions = new Dictionary>(); var cancellationToken = context.CancellationToken; // Find use-sites foreach (var inst in function.Descendants) { cancellationToken.ThrowIfCancellationRequested(); - if (inst is CallInstruction call && IsLocalFunctionMethod(call.Method)) { - if (function.Ancestors.OfType().SelectMany(f => f.LocalFunctions).Any(f => f.Method == call.Method)) - continue; + if (inst is CallInstruction call && IsLocalFunctionMethod(call.Method) && !call.Method.IsLocalFunction) { if (!localFunctions.TryGetValue(call.Method, out var info)) { info = new List() { call }; localFunctions.Add(call.Method, info); } else { info.Add(call); } - } else if (inst is LdFtn ldftn && ldftn.Parent is NewObj newObj && IsLocalFunctionMethod(ldftn.Method) && DelegateConstruction.IsDelegateConstruction(newObj)) { - if (function.Ancestors.OfType().SelectMany(f => f.LocalFunctions).Any(f => f.Method == ldftn.Method)) - continue; + } else if (inst is LdFtn ldftn && !ldftn.Method.IsLocalFunction && ldftn.Parent is NewObj newObj && IsLocalFunctionMethod(ldftn.Method) && DelegateConstruction.IsDelegateConstruction(newObj)) { context.StepStartGroup($"LocalFunctionDecompiler {ldftn.StartILOffset}", ldftn); if (!localFunctions.TryGetValue(ldftn.Method, out var info)) { info = new List() { newObj }; @@ -111,7 +118,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms internal static bool IsClosureParameter(IParameter parameter) { return parameter.IsRef - && ((ByReferenceType)parameter.Type).ElementType.GetDefinition()?.IsCompilerGenerated() == true; + && ((ByReferenceType)parameter.Type).ElementType + .GetDefinition()?.IsCompilerGenerated() == true; + } + + static IType UnwrapByRef(IType type) + { + if (type is ByReferenceType byRef) { + type = byRef.ElementType; + } + return type; } internal static ILInstruction GetStatement(ILInstruction inst) @@ -132,44 +148,49 @@ namespace ICSharpCode.Decompiler.IL.Transforms var genericContext = DelegateConstruction.GenericContextFromTypeArguments(targetMethod.Substitution); if (genericContext == null) return null; - var ilReader = context.CreateILReader(); - var body = context.PEFile.Reader.GetMethodBody(methodDefinition.RelativeVirtualAddress); - var function = ilReader.ReadIL((MethodDefinitionHandle)targetMethod.MetadataToken, body, genericContext.Value, ILFunctionKind.LocalFunction, context.CancellationToken); - // Embed the local function into the parent function's ILAst, so that "Show steps" can show - // how the local function body is being transformed. - parentFunction.LocalFunctions.Add(function); - function.DeclarationScope = (BlockContainer)parentFunction.Body; - function.CheckInvariant(ILPhase.Normal); - var nestedContext = new ILTransformContext(context, function); - function.RunTransforms(CSharpDecompiler.GetILTransforms().TakeWhile(t => !(t is LocalFunctionDecompiler)), nestedContext); - if (IsNonLocalTarget(targetMethod, useSites, out var target)) { - Debug.Assert(target != null); - nestedContext.Step("LocalFunctionDecompiler (ReplaceDelegateTargetVisitor)", function); - var thisVar = function.Variables.SingleOrDefault(v => v.Index == -1 && v.Kind == VariableKind.Parameter); - function.AcceptVisitor(new DelegateConstruction.ReplaceDelegateTargetVisitor(target, thisVar)); + var function = parentFunction.Ancestors.OfType().SelectMany(f => f.LocalFunctions).FirstOrDefault(f => f.Method == targetMethod); + if (function == null) { + var ilReader = context.CreateILReader(); + var body = context.PEFile.Reader.GetMethodBody(methodDefinition.RelativeVirtualAddress); + function = ilReader.ReadIL((MethodDefinitionHandle)targetMethod.MetadataToken, body, genericContext.Value, ILFunctionKind.LocalFunction, context.CancellationToken); + // Embed the local function into the parent function's ILAst, so that "Show steps" can show + // how the local function body is being transformed. + parentFunction.LocalFunctions.Add(function); + function.DeclarationScope = (BlockContainer)parentFunction.Body; + function.CheckInvariant(ILPhase.Normal); + var nestedContext = new ILTransformContext(context, function); + function.RunTransforms(CSharpDecompiler.GetILTransforms().TakeWhile(t => !(t is LocalFunctionDecompiler)), nestedContext); + if (IsNonLocalTarget(targetMethod, useSites, out var target)) { + Debug.Assert(target != null); + nestedContext.Step("LocalFunctionDecompiler (ReplaceDelegateTargetVisitor)", function); + var thisVar = function.Variables.SingleOrDefault(v => v.Index == -1 && v.Kind == VariableKind.Parameter); + function.AcceptVisitor(new DelegateConstruction.ReplaceDelegateTargetVisitor(target, thisVar)); + } + function.DeclarationScope = null; + function.ReducedMethod = ReduceToLocalFunction(targetMethod); + + foreach (var innerUseSite in function.Descendants.OfType()) { + if (innerUseSite.Method != function.Method) + continue; + if (innerUseSite.OpCode == OpCode.NewObj) { + TransformToLocalFunctionReference(function, innerUseSite); + } else { + TransformToLocalFunctionInvocation(function.ReducedMethod, innerUseSite); + } + } } - function.DeclarationScope = null; foreach (var useSite in useSites) { - for (int i = useSite.Arguments.Count - 1; i >= 0; i--) { - if (!useSite.Arguments[i].MatchLdLocRef(out var closureVar)) - break; - if (!TransformDisplayClassUsage.IsPotentialClosure(context, closureVar.Type.GetDefinition())) - break; - var instructions = closureVar.StoreInstructions.OfType() - .Concat(closureVar.AddressInstructions).OrderBy(inst => inst.StartILOffset); - var additionalScope = BlockContainer.FindClosestContainer(instructions.First()); - if (closureVar.CaptureScope == null) - closureVar.CaptureScope = additionalScope; - else - closureVar.CaptureScope = FindCommonAncestorInstruction(closureVar.CaptureScope, additionalScope); - if (function.DeclarationScope == null) - function.DeclarationScope = closureVar.CaptureScope; - else - function.DeclarationScope = FindCommonAncestorInstruction(function.DeclarationScope, closureVar.CaptureScope); + if (useSite.OpCode == OpCode.NewObj) { + TransformToLocalFunctionReference(function, useSite); + } else { + DetermineCaptureAndDeclarationScope(function, useSite); + TransformToLocalFunctionInvocation(function.ReducedMethod, useSite); } } - if (function.DeclarationScope != null && function.DeclarationScope.Parent is ILFunction betterParentFunction) { + if (function.DeclarationScope != null + && parentFunction.LocalFunctions.Contains(function) + && function.DeclarationScope.Parent is ILFunction betterParentFunction) { parentFunction.LocalFunctions.Remove(function); betterParentFunction.LocalFunctions.Add(function); } @@ -177,6 +198,76 @@ namespace ICSharpCode.Decompiler.IL.Transforms return function; } + LocalFunctionMethod ReduceToLocalFunction(IMethod method) + { + int parametersToRemove = 0; + for (int i = method.Parameters.Count - 1; i >= 0; i--) { + if (!IsClosureParameter(method.Parameters[i])) + break; + parametersToRemove++; + } + return new LocalFunctionMethod(method, parametersToRemove); + } + + static void TransformToLocalFunctionReference(ILFunction function, CallInstruction useSite) + { + useSite.Arguments[0].ReplaceWith(new LdNull().WithILRange(useSite.Arguments[0])); + var fnptr = (IInstructionWithMethodOperand)useSite.Arguments[1]; + var replacement = new LdFtn(function.ReducedMethod).WithILRange((ILInstruction)fnptr); + useSite.Arguments[1].ReplaceWith(replacement); + } + + void TransformToLocalFunctionInvocation(LocalFunctionMethod reducedMethod, CallInstruction useSite) + { + bool wasInstanceCall = !useSite.Method.IsStatic; + var replacement = new Call(reducedMethod); + int firstArgumentIndex = wasInstanceCall ? 1 : 0; + int argumentCount = useSite.Arguments.Count; + int reducedArgumentCount = argumentCount - (reducedMethod.NumberOfCompilerGeneratedParameters + firstArgumentIndex); + replacement.Arguments.AddRange(useSite.Arguments.Skip(firstArgumentIndex).Take(reducedArgumentCount)); + // copy flags: + replacement.ConstrainedTo = useSite.ConstrainedTo; + replacement.ILStackWasEmpty = useSite.ILStackWasEmpty; + replacement.IsTail = useSite.IsTail; + // copy IL ranges + replacement = replacement.WithILRange(useSite); + if (wasInstanceCall) { + replacement = replacement.WithILRange(useSite.Arguments[0]); + } + for (int i = 0; i < reducedMethod.NumberOfCompilerGeneratedParameters; i++) { + replacement = replacement.WithILRange(useSite.Arguments[argumentCount - i - 1]); + } + useSite.ReplaceWith(replacement); + } + + void DetermineCaptureAndDeclarationScope(ILFunction function, CallInstruction useSite) + { + for (int i = useSite.Arguments.Count - 1; i >= 0; i--) { + var arg = useSite.Arguments[i]; + ILVariable closureVar; + if (!(arg.MatchLdLoc(out closureVar) || arg.MatchLdLoca(out closureVar))) + break; + if (closureVar.Kind == VariableKind.NamedArgument) + break; + if (!TransformDisplayClassUsage.IsPotentialClosure(context, UnwrapByRef(closureVar.Type).GetDefinition())) + break; + if (closureVar.AddressCount == 0 && closureVar.StoreInstructions.Count == 0) + continue; + // determine the capture scope of closureVar and the declaration scope of the function + var instructions = closureVar.StoreInstructions.OfType() + .Concat(closureVar.AddressInstructions).OrderBy(inst => inst.StartILOffset); + var additionalScope = BlockContainer.FindClosestContainer(instructions.First()); + if (closureVar.CaptureScope == null) + closureVar.CaptureScope = additionalScope; + else + closureVar.CaptureScope = FindCommonAncestorInstruction(closureVar.CaptureScope, additionalScope); + if (function.DeclarationScope == null) + function.DeclarationScope = closureVar.CaptureScope; + else + function.DeclarationScope = FindCommonAncestorInstruction(function.DeclarationScope, closureVar.CaptureScope); + } + } + bool IsNonLocalTarget(IMethod targetMethod, List useSites, out ILInstruction target) { target = null; @@ -275,7 +366,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms struct FindTypeDecoder : ISignatureTypeProvider { - TypeDefinitionHandle handle; + readonly TypeDefinitionHandle handle; public FindTypeDecoder(TypeDefinitionHandle handle) { diff --git a/ICSharpCode.Decompiler/Output/TextTokenWriter.cs b/ICSharpCode.Decompiler/Output/TextTokenWriter.cs index 3b572088f..85d62c704 100644 --- a/ICSharpCode.Decompiler/Output/TextTokenWriter.cs +++ b/ICSharpCode.Decompiler/Output/TextTokenWriter.cs @@ -24,6 +24,7 @@ using ICSharpCode.Decompiler.CSharp.OutputVisitor; using ICSharpCode.Decompiler.CSharp.Resolver; using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.IL; +using ICSharpCode.Decompiler.Semantics; using ICSharpCode.Decompiler.TypeSystem; namespace ICSharpCode.Decompiler @@ -177,9 +178,9 @@ namespace ICSharpCode.Decompiler } if (node is LocalFunctionDeclarationStatement) { - var localFunction = node.GetResolveResult() as LocalFunctionReferenceResolveResult; + var localFunction = node.GetResolveResult() as MemberResolveResult; if (localFunction != null) - return localFunction.Function.Method; + return localFunction.Member; } return null; diff --git a/ICSharpCode.Decompiler/TypeSystem/IMethod.cs b/ICSharpCode.Decompiler/TypeSystem/IMethod.cs index 17f29aab6..c53d1cc60 100644 --- a/ICSharpCode.Decompiler/TypeSystem/IMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/IMethod.cs @@ -53,6 +53,7 @@ namespace ICSharpCode.Decompiler.TypeSystem IReadOnlyList TypeArguments { get; } bool IsExtensionMethod { get; } + bool IsLocalFunction { get; } bool IsConstructor { get; } bool IsDestructor { get; } bool IsOperator { get; } @@ -81,8 +82,9 @@ namespace ICSharpCode.Decompiler.TypeSystem MethodSemanticsAttributes AccessorKind { get; } /// - /// If this method is reduced from an extension method return the original method, null otherwise. + /// If this method is reduced from an extension method or a local function returns the original method, null otherwise. /// A reduced method doesn't contain the extension method parameter. That means that it has one parameter less than its definition. + /// A local function doesn't contain compiler-generated method parameters at the end. /// IMethod ReducedFrom { get; } diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs index 02a84fa79..02d439f84 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs @@ -139,6 +139,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation IReadOnlyList IMethod.TypeArguments => TypeParameters; bool IMethod.IsExtensionMethod => false; + bool IMethod.IsLocalFunction => false; bool IMethod.IsConstructor => symbolKind == SymbolKind.Constructor; bool IMethod.IsDestructor => symbolKind == SymbolKind.Destructor; bool IMethod.IsOperator => symbolKind == SymbolKind.Operator; diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs new file mode 100644 index 000000000..be3201461 --- /dev/null +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs @@ -0,0 +1,139 @@ +// Copyright (c) 2019 Siegfried Pammer +// +// 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.Reflection; +using ICSharpCode.Decompiler.Util; + +namespace ICSharpCode.Decompiler.TypeSystem.Implementation +{ + /// + /// A local function has zero or more compiler-generated parameters added at the end. + /// + class LocalFunctionMethod : IMethod + { + readonly IMethod baseMethod; + + public LocalFunctionMethod(IMethod baseMethod, int numberOfCompilerGeneratedParameters) + { + this.baseMethod = baseMethod; + this.NumberOfCompilerGeneratedParameters = numberOfCompilerGeneratedParameters; + } + + + public bool Equals(IMember obj, TypeVisitor typeNormalization) + { + if (!(obj is LocalFunctionMethod other)) + return false; + return baseMethod.Equals(other.baseMethod, typeNormalization); + } + + public override bool Equals(object obj) + { + var other = obj as LocalFunctionMethod; + if (other == null) + return false; + return baseMethod.Equals(other.baseMethod); + } + + public override int GetHashCode() + { + unchecked { + return baseMethod.GetHashCode() + 1; + } + } + + public override string ToString() + { + return string.Format("[LocalFunctionMethod: ReducedFrom={0}, NumberOfGeneratedParameters={1}]", ReducedFrom, NumberOfCompilerGeneratedParameters); + } + + internal int NumberOfCompilerGeneratedParameters { get; } + + public IMember MemberDefinition { + get { + if (baseMethod.MemberDefinition == baseMethod) + return this; + return new LocalFunctionMethod((IMethod)baseMethod.MemberDefinition, NumberOfCompilerGeneratedParameters); + } + } + + public IType ReturnType => baseMethod.ReturnType; + IEnumerable IMember.ExplicitlyImplementedInterfaceMembers => baseMethod.ExplicitlyImplementedInterfaceMembers; + bool IMember.IsExplicitInterfaceImplementation => baseMethod.IsExplicitInterfaceImplementation; + public bool IsVirtual => baseMethod.IsVirtual; + public bool IsOverride => baseMethod.IsOverride; + public bool IsOverridable => baseMethod.IsOverridable; + public TypeParameterSubstitution Substitution => baseMethod.Substitution; + + public IMethod Specialize(TypeParameterSubstitution substitution) + { + return SpecializedMethod.Create(this, substitution); + } + + IMember IMember.Specialize(TypeParameterSubstitution substitution) + { + return Specialize(substitution); + } + + public IReadOnlyList TypeParameters => baseMethod.TypeParameters; + public bool IsExtensionMethod => baseMethod.IsExtensionMethod; + public bool IsLocalFunction => true; + public bool IsConstructor => baseMethod.IsConstructor; + public bool IsDestructor => baseMethod.IsDestructor; + public bool IsOperator => baseMethod.IsOperator; + public bool HasBody => baseMethod.HasBody; + public bool IsAccessor => baseMethod.IsAccessor; + public IMember AccessorOwner => baseMethod.AccessorOwner; + public MethodSemanticsAttributes AccessorKind => baseMethod.AccessorKind; + public IMethod ReducedFrom => baseMethod; + public IReadOnlyList TypeArguments => baseMethod.TypeArguments; + + List parameters; + public IReadOnlyList Parameters { + get { + if (parameters == null) + parameters = new List(baseMethod.Parameters.SkipLast(NumberOfCompilerGeneratedParameters)); + return parameters; + } + } + + public System.Reflection.Metadata.EntityHandle MetadataToken => baseMethod.MetadataToken; + public SymbolKind SymbolKind => baseMethod.SymbolKind; + public ITypeDefinition DeclaringTypeDefinition => baseMethod.DeclaringTypeDefinition; + public IType DeclaringType => baseMethod.DeclaringType; + public IModule ParentModule => baseMethod.ParentModule; + IEnumerable IEntity.GetAttributes() => baseMethod.GetAttributes(); + IEnumerable IMethod.GetReturnTypeAttributes() => baseMethod.GetReturnTypeAttributes(); + bool IMethod.ReturnTypeIsRefReadOnly => baseMethod.ReturnTypeIsRefReadOnly; + public bool IsStatic => true; + public bool IsAbstract => baseMethod.IsAbstract; + public bool IsSealed => baseMethod.IsSealed; + + public Accessibility Accessibility => baseMethod.Accessibility; + + public string FullName => baseMethod.FullName; + public string Name => baseMethod.Name; + public string ReflectionName => baseMethod.ReflectionName; + public string Namespace => baseMethod.Namespace; + + public ICompilation Compilation => baseMethod.Compilation; + } +} + diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs index 2406051e3..a05ec147e 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs @@ -41,6 +41,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation readonly EntityHandle accessorOwner; public MethodSemanticsAttributes AccessorKind { get; } public bool IsExtensionMethod { get; } + bool IMethod.IsLocalFunction => false; // lazy-loaded fields: ITypeDefinition declaringType; diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMethod.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMethod.cs index 8f9937d87..9929e103b 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMethod.cs @@ -102,11 +102,15 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation return specializedTypeParameters ?? methodDefinition.TypeParameters; } } - + public bool IsExtensionMethod { get { return methodDefinition.IsExtensionMethod; } } - + + public bool IsLocalFunction { + get { return methodDefinition.IsLocalFunction; } + } + public bool IsConstructor { get { return methodDefinition.IsConstructor; } } diff --git a/ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs b/ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs index 0356f544a..d88900caf 100644 --- a/ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs @@ -129,6 +129,8 @@ namespace ICSharpCode.Decompiler.TypeSystem get { return baseMethod.IsExtensionMethod; } } + bool IMethod.IsLocalFunction => false; + public bool IsConstructor { get { return baseMethod.IsConstructor; } } From e215f69b2d0968c3d35888a4d1029651cff88d33 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Thu, 18 Jul 2019 21:00:04 +0200 Subject: [PATCH 15/23] Extend description of LocalFunctionDecompiler --- .../IL/Transforms/LocalFunctionDecompiler.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs index 228611a51..7937cf2a7 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs @@ -45,9 +45,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// /// /// local functions can either be used in method calls, i.e., call and callvirt instructions, - /// or can be used as part of the "delegate construction" pattern, i.e., newobj Delegate(<target-expression>, ldftn <method>). Note that this pattern would also be possible with ldvirtftn, but I haven't yet disovered a case where a compiler generates such code. + /// or can be used as part of the "delegate construction" pattern, i.e., newobj Delegate(<target-expression>, ldftn <method>). /// - /// As local functions can be declared practically anywhere, we have to take a look at all use-sites and infer the declaration location from that. + /// As local functions can be declared practically anywhere, we have to take a look at all use-sites and infer the declaration location from that. Use-sites can be call, callvirt and ldftn instructions. + /// After all use-sites are collected we construct the ILAst of the local function and add it to the parent function. + /// Then all use-sites of the local-function are transformed to a call to the LocalFunctionMethod or a ldftn of the LocalFunctionMethod. + /// In a next step we handle all nested local functions. + /// After all local functions are transformed, we move all local functions that do not capture any variables to the top-level function. /// public void Run(ILFunction function, ILTransformContext context) { From 647f4fd5455caa267966add13048c6245c0a8893 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Thu, 18 Jul 2019 21:42:25 +0200 Subject: [PATCH 16/23] Do not crash if the local function was not correctly transformed. This should only happen for generic local functions, which are currently not supported. --- ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index 3cc2ed7e2..510930f1e 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -179,11 +179,12 @@ namespace ICSharpCode.Decompiler.CSharp ILFunction localFunction = null; if (method.IsLocalFunction) { localFunction = expressionBuilder.ResolveLocalFunction(method); + Debug.Assert(localFunction != null); } TranslatedExpression target; if (callOpCode == OpCode.NewObj) { target = default(TranslatedExpression); // no target - } else if (method.IsLocalFunction) { + } else if (method.IsLocalFunction && localFunction != null) { target = new IdentifierExpression(localFunction.Name) .WithoutILInstruction() .WithRR(ToMethodGroup(method, localFunction)); From 763ea386447b4061e4e1e39e1450ba0198846535 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Thu, 18 Jul 2019 23:20:36 +0200 Subject: [PATCH 17/23] Clean up CallBuilder.HandleDelegateConstruction --- .../TestCases/Pretty/LocalFunctions.cs | 17 ++++ ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 98 +++++++++---------- .../Resolver/CSharpInvocationResolveResult.cs | 5 - .../Transforms/IntroduceExtensionMethods.cs | 2 +- 4 files changed, 66 insertions(+), 56 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs index 806e9a57f..03c37273a 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs @@ -148,6 +148,23 @@ namespace LocalFunctions } } + private void Name() + { + + } + + private void LocalFunctionHidingMethod() + { + Action action = this.Name; + Name(); + action(); + + void Name() + { + + } + } + public void NamedArgument() { Use(Get(1), Get(2), Get(3)); diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index 510930f1e..fc50633d5 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -1227,69 +1227,67 @@ namespace ICSharpCode.Decompiler.CSharp CallOpCode = inst.OpCode }; ResolveResult result = null; - string methodName; + string methodName = method.Name; if (method.IsLocalFunction) { requireTarget = false; var localFunction = expressionBuilder.ResolveLocalFunction(method); result = ToMethodGroup(method, localFunction); target = default; + targetType = default; methodName = localFunction.Name; - } else { - methodName = method.Name; - if (method.IsExtensionMethod && invokeMethod != null && method.Parameters.Count - 1 == invokeMethod.Parameters.Count) { - targetType = method.Parameters[0].Type; - if (targetType.Kind == TypeKind.ByReference && thisArg is Box thisArgBox) { - targetType = ((ByReferenceType)targetType).ElementType; - thisArg = thisArgBox.Argument; - } - target = expressionBuilder.Translate(thisArg, targetType); - requireTarget = true; - } else { - targetType = method.DeclaringType; - if (targetType.IsReferenceType == false && thisArg is Box thisArgBox) { - // Normal struct instance method calls (which TranslateTarget is meant for) expect a 'ref T', - // but delegate construction uses a 'box T'. - if (thisArgBox.Argument is LdObj ldobj) { - thisArg = ldobj.Target; - } else { - thisArg = new AddressOf(thisArgBox.Argument); - } - } - target = expressionBuilder.TranslateTarget(thisArg, - nonVirtualInvocation: func.OpCode == OpCode.LdFtn, - memberStatic: method.IsStatic, - memberDeclaringType: method.DeclaringType); - requireTarget = expressionBuilder.HidesVariableWithName(method.Name) - || (method.IsStatic ? !expressionBuilder.IsCurrentOrContainingType(method.DeclaringTypeDefinition) : !(target.Expression is ThisReferenceExpression)); + } else if (method.IsExtensionMethod && invokeMethod != null && method.Parameters.Count - 1 == invokeMethod.Parameters.Count) { + targetType = method.Parameters[0].Type; + if (targetType.Kind == TypeKind.ByReference && thisArg is Box thisArgBox) { + targetType = ((ByReferenceType)targetType).ElementType; + thisArg = thisArgBox.Argument; } - var or = new OverloadResolution(resolver.Compilation, method.Parameters.SelectReadOnlyArray(p => new TypeResolveResult(p.Type))); - if (!requireTarget) { - result = resolver.ResolveSimpleName(method.Name, method.TypeArguments, isInvocationTarget: false); - if (result is MethodGroupResolveResult mgrr) { - or.AddMethodLists(mgrr.MethodsGroupedByDeclaringType.ToArray()); - requireTarget = (or.BestCandidateErrors != OverloadResolutionErrors.None || !IsAppropriateCallTarget(expectedTargetDetails, method, or.BestCandidate)); + target = expressionBuilder.Translate(thisArg, targetType); + requireTarget = true; + } else { + targetType = method.DeclaringType; + if (targetType.IsReferenceType == false && thisArg is Box thisArgBox) { + // Normal struct instance method calls (which TranslateTarget is meant for) expect a 'ref T', + // but delegate construction uses a 'box T'. + if (thisArgBox.Argument is LdObj ldobj) { + thisArg = ldobj.Target; } else { - requireTarget = true; + thisArg = new AddressOf(thisArgBox.Argument); } } - MemberLookup lookup = null; - bool needsCast = false; - if (requireTarget) { - lookup = new MemberLookup(resolver.CurrentTypeDefinition, resolver.CurrentTypeDefinition.ParentModule); - var rr = lookup.Lookup(target.ResolveResult, method.Name, method.TypeArguments, false); - needsCast = true; - result = rr; - if (rr is MethodGroupResolveResult mgrr) { - or.AddMethodLists(mgrr.MethodsGroupedByDeclaringType.ToArray()); - needsCast = (or.BestCandidateErrors != OverloadResolutionErrors.None || !IsAppropriateCallTarget(expectedTargetDetails, method, or.BestCandidate)); - } + target = expressionBuilder.TranslateTarget(thisArg, + nonVirtualInvocation: func.OpCode == OpCode.LdFtn, + memberStatic: method.IsStatic, + memberDeclaringType: method.DeclaringType); + requireTarget = expressionBuilder.HidesVariableWithName(method.Name) + || (method.IsStatic ? !expressionBuilder.IsCurrentOrContainingType(method.DeclaringTypeDefinition) : !(target.Expression is ThisReferenceExpression)); + } + var or = new OverloadResolution(resolver.Compilation, method.Parameters.SelectReadOnlyArray(p => new TypeResolveResult(p.Type))); + if (!method.IsLocalFunction && !requireTarget) { + result = resolver.ResolveSimpleName(method.Name, method.TypeArguments, isInvocationTarget: false); + if (result is MethodGroupResolveResult mgrr) { + or.AddMethodLists(mgrr.MethodsGroupedByDeclaringType.ToArray()); + requireTarget = (or.BestCandidateErrors != OverloadResolutionErrors.None || !IsAppropriateCallTarget(expectedTargetDetails, method, or.BestCandidate)); + } else { + requireTarget = true; } - if (needsCast) { - Debug.Assert(requireTarget); - target = target.ConvertTo(targetType, expressionBuilder); - result = lookup.Lookup(target.ResolveResult, method.Name, method.TypeArguments, false); + } + MemberLookup lookup = null; + bool needsCast = false; + if (requireTarget) { + lookup = new MemberLookup(resolver.CurrentTypeDefinition, resolver.CurrentTypeDefinition.ParentModule); + var rr = lookup.Lookup(target.ResolveResult, method.Name, method.TypeArguments, false); + needsCast = true; + result = rr; + if (rr is MethodGroupResolveResult mgrr) { + or.AddMethodLists(mgrr.MethodsGroupedByDeclaringType.ToArray()); + needsCast = (or.BestCandidateErrors != OverloadResolutionErrors.None || !IsAppropriateCallTarget(expectedTargetDetails, method, or.BestCandidate)); } } + if (needsCast) { + Debug.Assert(requireTarget); + target = target.ConvertTo(targetType, expressionBuilder); + result = lookup.Lookup(target.ResolveResult, method.Name, method.TypeArguments, false); + } Expression targetExpression; if (requireTarget) { var mre = new MemberReferenceExpression(target, methodName); diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpInvocationResolveResult.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpInvocationResolveResult.cs index 784c6b9e5..2b71dfc82 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpInvocationResolveResult.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpInvocationResolveResult.cs @@ -47,11 +47,6 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver /// public readonly bool IsExpandedForm; - /// - /// Gets whether this invocation is calling a local function. - /// - public readonly bool IsLocalFunctionInvocation; - readonly IReadOnlyList argumentToParameterMap; public CSharpInvocationResolveResult( diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceExtensionMethods.cs b/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceExtensionMethods.cs index e3bd8d7fc..3b11cd87f 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceExtensionMethods.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceExtensionMethods.cs @@ -154,7 +154,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms var newResolveResult = new CSharpInvocationResolveResult( irr.TargetResult, irr.Member, irr.Arguments, irr.OverloadResolutionErrors, isExtensionMethodInvocation: true, irr.IsExpandedForm, irr.IsDelegateInvocation, - irr.IsLocalFunctionInvocation, irr.GetArgumentToParameterMap(), irr.InitializerStatements); + irr.GetArgumentToParameterMap(), irr.InitializerStatements); invocationExpression.AddAnnotation(newResolveResult); } } From dc74e4ee9d0c2c5892f67e979c1b75e4c3d25726 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Thu, 18 Jul 2019 23:22:52 +0200 Subject: [PATCH 18/23] Fix build. --- .../CSharp/Resolver/CSharpInvocationResolveResult.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpInvocationResolveResult.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpInvocationResolveResult.cs index 2b71dfc82..61e64a7c0 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpInvocationResolveResult.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpInvocationResolveResult.cs @@ -56,7 +56,6 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver bool isExtensionMethodInvocation = false, bool isExpandedForm = false, bool isDelegateInvocation = false, - bool isLocalFunctionInvocation = false, IReadOnlyList argumentToParameterMap = null, IList initializerStatements = null, IType returnTypeOverride = null @@ -67,7 +66,6 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver this.IsExtensionMethodInvocation = isExtensionMethodInvocation; this.IsExpandedForm = isExpandedForm; this.IsDelegateInvocation = isDelegateInvocation; - this.IsLocalFunctionInvocation = isLocalFunctionInvocation; this.argumentToParameterMap = argumentToParameterMap; } From 5a8796c05c00952170a22c6c7a08b64892777a70 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Thu, 18 Jul 2019 23:30:49 +0200 Subject: [PATCH 19/23] Reuse the existing TSAB. --- ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs | 2 +- ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 3 --- ICSharpCode.Decompiler/CSharp/StatementBuilder.cs | 3 +-- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index c7c5ff137..01a8ad23f 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -397,7 +397,7 @@ namespace ICSharpCode.Decompiler.CSharp return new DecompilerTypeSystem(file, resolver); } - internal static TypeSystemAstBuilder CreateAstBuilder(ITypeResolveContext decompilationContext) + static TypeSystemAstBuilder CreateAstBuilder(ITypeResolveContext decompilationContext) { var typeSystemAstBuilder = new TypeSystemAstBuilder(); typeSystemAstBuilder.ShowAttributes = true; diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index fc50633d5..05df1d6d4 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -1299,9 +1299,6 @@ namespace ICSharpCode.Decompiler.CSharp .WithRR(result); targetExpression = ide; } - if (target.ResolveResult != null) { - result = new MemberResolveResult(target.ResolveResult, method); - } var oce = new ObjectCreateExpression(expressionBuilder.ConvertType(inst.Method.DeclaringType), targetExpression) .WithILInstruction(inst) .WithRR(new ConversionResolveResult( diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs index 9730fc1f9..f10d53493 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -962,11 +962,10 @@ namespace ICSharpCode.Decompiler.CSharp LocalFunctionDeclarationStatement TranslateFunction(ILFunction function) { var stmt = new LocalFunctionDeclarationStatement(); - var tsab = CSharpDecompiler.CreateAstBuilder(null); var nestedBuilder = new StatementBuilder(typeSystem, exprBuilder.decompilationContext, function, settings, cancellationToken); stmt.Name = function.Name; stmt.Parameters.AddRange(exprBuilder.MakeParameters(function.Parameters, function)); - stmt.ReturnType = tsab.ConvertType(function.Method.ReturnType); + stmt.ReturnType = exprBuilder.ConvertType(function.Method.ReturnType); stmt.Body = nestedBuilder.ConvertAsBlock(function.Body); stmt.AddAnnotation(new MemberResolveResult(null, function.ReducedMethod)); return stmt; From b7bf6b7720abd672874c58407840a85a4051f144 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 19 Jul 2019 14:04:22 +0200 Subject: [PATCH 20/23] Fix build again. --- ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index 05df1d6d4..b65aeb71b 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -218,7 +218,7 @@ namespace ICSharpCode.Decompiler.CSharp if (method.IsLocalFunction) { return new InvocationExpression(target, argumentList.GetArgumentExpressions()) .WithRR(new CSharpInvocationResolveResult(target.ResolveResult, method, - argumentList.GetArgumentResolveResults().ToList(), isExpandedForm: argumentList.IsExpandedForm, isLocalFunctionInvocation: true)); + argumentList.GetArgumentResolveResults().ToList(), isExpandedForm: argumentList.IsExpandedForm)); } if (method is VarArgInstanceMethod) { From 6234ff7c9acdea017e655184007de2a3840cc532 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 20 Jul 2019 07:40:43 +0200 Subject: [PATCH 21/23] Apply changes as requested per code review. --- .../TypeSystem/DecompilerTypeSystem.cs | 4 ---- .../Implementation/LocalFunctionMethod.cs | 23 +++++++++---------- .../TypeSystem/VarArgInstanceMethod.cs | 4 +++- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs index 06f07cdd9..04ce3ef8f 100644 --- a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs +++ b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs @@ -104,10 +104,6 @@ namespace ICSharpCode.Decompiler.TypeSystem /// NullabilityAnnotations = 0x400, /// - /// If this option is active, - /// - LocalFunctions = 0x800, - /// /// Default settings: typical options for the decompiler, with all C# languages features enabled. /// Default = Dynamic | Tuple | ExtensionMethods | DecimalConstants | ReadOnlyStructsAndParameters diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs index be3201461..6a42b4364 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs @@ -41,21 +41,22 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation { if (!(obj is LocalFunctionMethod other)) return false; - return baseMethod.Equals(other.baseMethod, typeNormalization); + return baseMethod.Equals(other.baseMethod, typeNormalization) + && NumberOfCompilerGeneratedParameters == other.NumberOfCompilerGeneratedParameters; } public override bool Equals(object obj) { - var other = obj as LocalFunctionMethod; - if (other == null) + if (!(obj is LocalFunctionMethod other)) return false; - return baseMethod.Equals(other.baseMethod); + return baseMethod.Equals(other.baseMethod) + && NumberOfCompilerGeneratedParameters == other.NumberOfCompilerGeneratedParameters; } public override int GetHashCode() { unchecked { - return baseMethod.GetHashCode() + 1; + return baseMethod.GetHashCode() + NumberOfCompilerGeneratedParameters + 1; } } @@ -66,13 +67,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation internal int NumberOfCompilerGeneratedParameters { get; } - public IMember MemberDefinition { - get { - if (baseMethod.MemberDefinition == baseMethod) - return this; - return new LocalFunctionMethod((IMethod)baseMethod.MemberDefinition, NumberOfCompilerGeneratedParameters); - } - } + public IMember MemberDefinition => this; public IType ReturnType => baseMethod.ReturnType; IEnumerable IMember.ExplicitlyImplementedInterfaceMembers => baseMethod.ExplicitlyImplementedInterfaceMembers; @@ -122,6 +117,10 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation IEnumerable IEntity.GetAttributes() => baseMethod.GetAttributes(); IEnumerable IMethod.GetReturnTypeAttributes() => baseMethod.GetReturnTypeAttributes(); bool IMethod.ReturnTypeIsRefReadOnly => baseMethod.ReturnTypeIsRefReadOnly; + /// + /// We consider local functions as always static, because they do not have a "this parameter". + /// Even local functions in instance methods capture this. + /// public bool IsStatic => true; public bool IsAbstract => baseMethod.IsAbstract; public bool IsSealed => baseMethod.IsSealed; diff --git a/ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs b/ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs index d88900caf..a151d1464 100644 --- a/ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs @@ -129,7 +129,9 @@ namespace ICSharpCode.Decompiler.TypeSystem get { return baseMethod.IsExtensionMethod; } } - bool IMethod.IsLocalFunction => false; + bool IMethod.IsLocalFunction { + get { return baseMethod.IsLocalFunction; } + } public bool IsConstructor { get { return baseMethod.IsConstructor; } From a109b7785899d3980ece0188c41a1175b074731a Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Tue, 23 Jul 2019 01:13:12 +0200 Subject: [PATCH 22/23] Refactor LocalFunctionDecompiler to allow mutually recursive local functions to be decompiled correctly. --- .../TestCases/Pretty/LocalFunctions.cs | 28 +++ .../CSharp/ExpressionBuilder.cs | 5 - .../IL/Transforms/DelegateConstruction.cs | 2 +- .../IL/Transforms/LocalFunctionDecompiler.cs | 224 ++++++++---------- .../Transforms/TransformDisplayClassUsage.cs | 8 +- 5 files changed, 130 insertions(+), 137 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs index 03c37273a..f66883c7b 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs @@ -217,6 +217,34 @@ namespace LocalFunctions return FibHelper(n - 1) + FibHelper(n - 2); } } + public int MutuallyRecursiveLocalFunctions() + { + return B(4) + C(3); + + int A(int i) + { + if (i > 0) { + return A(i - 1) + 2 * B(i - 1) + 3 * C(i - 1); + } + return 1; + } + + int B(int i) + { + if (i > 0) { + return 3 * A(i - 1) + B(i - 1); + } + return 1; + } + + int C(int i) + { + if (i > 0) { + return 2 * A(i - 1) + C(i - 1); + } + return 1; + } + } public static int NestedLocalFunctions(int i) { diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 5fb95a319..87602db3f 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -207,11 +207,6 @@ namespace ICSharpCode.Decompiler.CSharp } } - internal bool IsLocalFunction(IMethod method) - { - return settings.LocalFunctions && IL.Transforms.LocalFunctionDecompiler.IsLocalFunctionMethod(method); - } - internal ILFunction ResolveLocalFunction(IMethod method) { Debug.Assert(method.IsLocalFunction); diff --git a/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs b/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs index b3f617029..7edfeb1ef 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs @@ -126,7 +126,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return null; if (targetMethod.MetadataToken.IsNil) return null; - if (LocalFunctionDecompiler.IsLocalFunctionMethod(targetMethod)) + if (LocalFunctionDecompiler.IsLocalFunctionMethod(targetMethod, context)) return null; target = value.Arguments[0]; var methodDefinition = context.PEFile.Metadata.GetMethodDefinition((MethodDefinitionHandle)targetMethod.MetadataToken); diff --git a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs index 7937cf2a7..dafda4357 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs @@ -40,6 +40,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms { ILTransformContext context; + struct LocalFunctionInfo + { + public List UseSites; + public ILFunction Definition; + } + /// /// The transform works like this: /// @@ -51,67 +57,103 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// After all use-sites are collected we construct the ILAst of the local function and add it to the parent function. /// Then all use-sites of the local-function are transformed to a call to the LocalFunctionMethod or a ldftn of the LocalFunctionMethod. /// In a next step we handle all nested local functions. - /// After all local functions are transformed, we move all local functions that do not capture any variables to the top-level function. + /// After all local functions are transformed, we move all local functions that capture any variables to their respective declaration scope. /// public void Run(ILFunction function, ILTransformContext context) { if (!context.Settings.LocalFunctions) return; this.context = context; - var localFunctions = new Dictionary>(); + var localFunctions = new Dictionary(); var cancellationToken = context.CancellationToken; - // Find use-sites - foreach (var inst in function.Descendants) { + // Find all local functions declared inside this method, including nested local functions or local functions declared in lambdas. + FindUseSites(function, context, localFunctions); + foreach (var (method, info) in localFunctions) { cancellationToken.ThrowIfCancellationRequested(); - if (inst is CallInstruction call && IsLocalFunctionMethod(call.Method) && !call.Method.IsLocalFunction) { - if (!localFunctions.TryGetValue(call.Method, out var info)) { - info = new List() { call }; - localFunctions.Add(call.Method, info); - } else { - info.Add(call); + var firstUseSite = info.UseSites[0]; + context.StepStartGroup($"Transform " + info.Definition.Name, info.Definition); + try { + var localFunction = info.Definition; + if (!localFunction.Method.IsStatic) { + var target = firstUseSite.Arguments[0]; + context.Step($"Replace 'this' with {target}", localFunction); + var thisVar = localFunction.Variables.SingleOrDefault(VariableKindExtensions.IsThis); + localFunction.AcceptVisitor(new DelegateConstruction.ReplaceDelegateTargetVisitor(target, thisVar)); } - } else if (inst is LdFtn ldftn && !ldftn.Method.IsLocalFunction && ldftn.Parent is NewObj newObj && IsLocalFunctionMethod(ldftn.Method) && DelegateConstruction.IsDelegateConstruction(newObj)) { - context.StepStartGroup($"LocalFunctionDecompiler {ldftn.StartILOffset}", ldftn); - if (!localFunctions.TryGetValue(ldftn.Method, out var info)) { - info = new List() { newObj }; - localFunctions.Add(ldftn.Method, info); - } else { - info.Add(newObj); + + foreach (var useSite in info.UseSites) { + context.Step("Transform use site at " + useSite.StartILOffset, useSite); + if (useSite.OpCode == OpCode.NewObj) { + TransformToLocalFunctionReference(localFunction, useSite); + } else { + DetermineCaptureAndDeclarationScope(localFunction, useSite); + TransformToLocalFunctionInvocation(localFunction.ReducedMethod, useSite); + } } - context.StepEndGroup(); - } - } - foreach (var (method, useSites) in localFunctions) { - context.StepStartGroup($"LocalFunctionDecompiler {useSites[0].StartILOffset}", useSites[0]); - try { - TransformLocalFunction(function, method, useSites); + if (localFunction.DeclarationScope == null) { + localFunction.DeclarationScope = (BlockContainer)function.Body; + } else if (localFunction.DeclarationScope != function.Body && localFunction.DeclarationScope.Parent is ILFunction declaringFunction) { + function.LocalFunctions.Remove(localFunction); + declaringFunction.LocalFunctions.Add(localFunction); + } } finally { context.StepEndGroup(); } } + } - foreach (var f in function.LocalFunctions) { - // handle nested functions - var nestedContext = new ILTransformContext(context, f); - nestedContext.StepStartGroup("LocalFunctionDecompiler (nested functions)", f); - new LocalFunctionDecompiler().Run(f, nestedContext); - nestedContext.StepEndGroup(); + void FindUseSites(ILFunction function, ILTransformContext context, Dictionary localFunctions) + { + foreach (var inst in function.Body.Descendants) { + context.CancellationToken.ThrowIfCancellationRequested(); + if (inst is CallInstruction call && !call.Method.IsLocalFunction && IsLocalFunctionMethod(call.Method, context)) { + HandleUseSite(call.Method, call); + } else if (inst is LdFtn ldftn && !ldftn.Method.IsLocalFunction && ldftn.Parent is NewObj newObj && IsLocalFunctionMethod(ldftn.Method, context) && DelegateConstruction.IsDelegateConstruction(newObj)) { + HandleUseSite(ldftn.Method, newObj); + } } - if (function.Kind == ILFunctionKind.TopLevelFunction) { - var movableFunctions = TreeTraversal.PostOrder(function, f => f.LocalFunctions) - .Where(f => f.Kind == ILFunctionKind.LocalFunction && f.DeclarationScope == null) - .ToArray(); - foreach (var f in movableFunctions) { - var parent = (ILFunction)f.Parent; - f.DeclarationScope = (BlockContainer)function.Body; - parent.LocalFunctions.Remove(f); - function.LocalFunctions.Add(f); + void HandleUseSite(IMethod targetMethod, CallInstruction inst) + { + if (!localFunctions.TryGetValue((MethodDefinitionHandle)targetMethod.MetadataToken, out var info)) { + context.StepStartGroup($"Read local function '{targetMethod.Name}'", inst); + info = new LocalFunctionInfo() { + UseSites = new List() { inst }, + Definition = ReadLocalFunctionDefinition(context.Function, targetMethod) + }; + localFunctions.Add((MethodDefinitionHandle)targetMethod.MetadataToken, info); + FindUseSites(info.Definition, context, localFunctions); + context.StepEndGroup(); + } else { + info.UseSites.Add(inst); } } } + ILFunction ReadLocalFunctionDefinition(ILFunction rootFunction, IMethod targetMethod) + { + var methodDefinition = context.PEFile.Metadata.GetMethodDefinition((MethodDefinitionHandle)targetMethod.MetadataToken); + if (!methodDefinition.HasBody()) + return null; + var genericContext = DelegateConstruction.GenericContextFromTypeArguments(targetMethod.Substitution); + if (genericContext == null) + return null; + var ilReader = context.CreateILReader(); + var body = context.PEFile.Reader.GetMethodBody(methodDefinition.RelativeVirtualAddress); + var function = ilReader.ReadIL((MethodDefinitionHandle)targetMethod.MetadataToken, body, genericContext.Value, ILFunctionKind.LocalFunction, context.CancellationToken); + // Embed the local function into the parent function's ILAst, so that "Show steps" can show + // how the local function body is being transformed. + rootFunction.LocalFunctions.Add(function); + function.DeclarationScope = (BlockContainer)rootFunction.Body; + function.CheckInvariant(ILPhase.Normal); + var nestedContext = new ILTransformContext(context, function); + function.RunTransforms(CSharpDecompiler.GetILTransforms().TakeWhile(t => !(t is LocalFunctionDecompiler)), nestedContext); + function.DeclarationScope = null; + function.ReducedMethod = ReduceToLocalFunction(targetMethod); + return function; + } + static T FindCommonAncestorInstruction(ILInstruction a, ILInstruction b) where T : ILInstruction { @@ -137,71 +179,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms internal static ILInstruction GetStatement(ILInstruction inst) { while (inst.Parent != null) { - if (inst.Parent is Block) + if (inst.Parent is Block b && b.Kind == BlockKind.ControlFlow) return inst; inst = inst.Parent; } return inst; } - private ILFunction TransformLocalFunction(ILFunction parentFunction, IMethod targetMethod, List useSites) - { - var methodDefinition = context.PEFile.Metadata.GetMethodDefinition((MethodDefinitionHandle)targetMethod.MetadataToken); - if (!methodDefinition.HasBody()) - return null; - var genericContext = DelegateConstruction.GenericContextFromTypeArguments(targetMethod.Substitution); - if (genericContext == null) - return null; - var function = parentFunction.Ancestors.OfType().SelectMany(f => f.LocalFunctions).FirstOrDefault(f => f.Method == targetMethod); - if (function == null) { - var ilReader = context.CreateILReader(); - var body = context.PEFile.Reader.GetMethodBody(methodDefinition.RelativeVirtualAddress); - function = ilReader.ReadIL((MethodDefinitionHandle)targetMethod.MetadataToken, body, genericContext.Value, ILFunctionKind.LocalFunction, context.CancellationToken); - // Embed the local function into the parent function's ILAst, so that "Show steps" can show - // how the local function body is being transformed. - parentFunction.LocalFunctions.Add(function); - function.DeclarationScope = (BlockContainer)parentFunction.Body; - function.CheckInvariant(ILPhase.Normal); - var nestedContext = new ILTransformContext(context, function); - function.RunTransforms(CSharpDecompiler.GetILTransforms().TakeWhile(t => !(t is LocalFunctionDecompiler)), nestedContext); - if (IsNonLocalTarget(targetMethod, useSites, out var target)) { - Debug.Assert(target != null); - nestedContext.Step("LocalFunctionDecompiler (ReplaceDelegateTargetVisitor)", function); - var thisVar = function.Variables.SingleOrDefault(v => v.Index == -1 && v.Kind == VariableKind.Parameter); - function.AcceptVisitor(new DelegateConstruction.ReplaceDelegateTargetVisitor(target, thisVar)); - } - function.DeclarationScope = null; - function.ReducedMethod = ReduceToLocalFunction(targetMethod); - - foreach (var innerUseSite in function.Descendants.OfType()) { - if (innerUseSite.Method != function.Method) - continue; - if (innerUseSite.OpCode == OpCode.NewObj) { - TransformToLocalFunctionReference(function, innerUseSite); - } else { - TransformToLocalFunctionInvocation(function.ReducedMethod, innerUseSite); - } - } - } - foreach (var useSite in useSites) { - if (useSite.OpCode == OpCode.NewObj) { - TransformToLocalFunctionReference(function, useSite); - } else { - DetermineCaptureAndDeclarationScope(function, useSite); - TransformToLocalFunctionInvocation(function.ReducedMethod, useSite); - } - } - - if (function.DeclarationScope != null - && parentFunction.LocalFunctions.Contains(function) - && function.DeclarationScope.Parent is ILFunction betterParentFunction) { - parentFunction.LocalFunctions.Remove(function); - betterParentFunction.LocalFunctions.Add(function); - } - - return function; - } - LocalFunctionMethod ReduceToLocalFunction(IMethod method) { int parametersToRemove = 0; @@ -272,47 +256,28 @@ namespace ICSharpCode.Decompiler.IL.Transforms } } - bool IsNonLocalTarget(IMethod targetMethod, List useSites, out ILInstruction target) - { - target = null; - if (targetMethod.IsStatic) - return false; - ValidateUseSites(useSites); - target = useSites.Select(call => call.Arguments.First()).First(); - return !target.MatchLdThis(); - } - - [Conditional("DEBUG")] - static void ValidateUseSites(List useSites) - { - ILInstruction targetInstruction = null; - foreach (var site in useSites) { - if (targetInstruction == null) - targetInstruction = site.Arguments.First(); - else - Debug.Assert(targetInstruction.Match(site.Arguments[0]).Success); - } - } - - internal static bool IsLocalFunctionReference(NewObj inst) + internal static bool IsLocalFunctionReference(NewObj inst, ILTransformContext context) { if (inst == null || inst.Arguments.Count != 2 || inst.Method.DeclaringType.Kind != TypeKind.Delegate) return false; var opCode = inst.Arguments[1].OpCode; return (opCode == OpCode.LdFtn || opCode == OpCode.LdVirtFtn) - && IsLocalFunctionMethod(((IInstructionWithMethodOperand)inst.Arguments[1]).Method); + && IsLocalFunctionMethod(((IInstructionWithMethodOperand)inst.Arguments[1]).Method, context); } - public static bool IsLocalFunctionMethod(IMethod method) + public static bool IsLocalFunctionMethod(IMethod method, ILTransformContext context) { if (method.MetadataToken.IsNil) return false; - return IsLocalFunctionMethod(method.ParentModule.PEFile, (MethodDefinitionHandle)method.MetadataToken); + return IsLocalFunctionMethod(method.ParentModule.PEFile, (MethodDefinitionHandle)method.MetadataToken, context); } - public static bool IsLocalFunctionMethod(PEFile module, MethodDefinitionHandle methodHandle) + public static bool IsLocalFunctionMethod(PEFile module, MethodDefinitionHandle methodHandle, ILTransformContext context = null) { + if (context != null && context.PEFile != module) + return false; + var metadata = module.Metadata; var method = metadata.GetMethodDefinition(methodHandle); var declaringType = method.GetDeclaringType(); @@ -326,12 +291,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms return true; } - public static bool IsLocalFunctionDisplayClass(PEFile module, TypeDefinitionHandle typeHandle) + public static bool IsLocalFunctionDisplayClass(PEFile module, TypeDefinitionHandle typeHandle, ILTransformContext context = null) { + if (context != null && context.PEFile != module) + return false; + var metadata = module.Metadata; var type = metadata.GetTypeDefinition(typeHandle); - if ((type.Attributes & TypeAttributes.NestedPrivate) == 0) + if ((type.Attributes & TypeAttributes.VisibilityMask) != TypeAttributes.NestedPrivate) return false; if (!type.HasGeneratedName(metadata)) return false; @@ -340,7 +308,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms var declaringType = metadata.GetTypeDefinition(declaringTypeHandle); foreach (var method in declaringType.GetMethods()) { - if (!IsLocalFunctionMethod(module, method)) + if (!IsLocalFunctionMethod(module, method, context)) continue; var md = metadata.GetMethodDefinition(method); if (md.DecodeSignature(new FindTypeDecoder(typeHandle), default).ParameterTypes.Any()) diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs index 4fb8ddb9e..382abe96a 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs @@ -39,6 +39,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms public ITypeDefinition Definition; public Dictionary Variables; public BlockContainer CaptureScope; + public ILFunction DeclaringFunction; } struct DisplayClassVariable @@ -105,7 +106,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms Variable = v, Definition = closureType, Variables = new Dictionary(), - CaptureScope = (isMono && IsMonoNestedCaptureScope(closureType)) || localFunctionClosureParameter ? null : v.CaptureScope + CaptureScope = (isMono && IsMonoNestedCaptureScope(closureType)) || localFunctionClosureParameter ? null : v.CaptureScope, + DeclaringFunction = localFunctionClosureParameter ? f.DeclarationScope.Ancestors.OfType().First() : f }); } else { if (displayClass.CaptureScope == null && !localFunctionClosureParameter) @@ -296,10 +298,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms } else { Debug.Assert(displayClass.Definition == field.DeclaringTypeDefinition); // Introduce a fresh variable for the display class field. - v = currentFunction.RegisterVariable(VariableKind.Local, field.Type, field.Name); if (displayClass.IsMono && displayClass.CaptureScope == null && !IsOuterClosureReference(field)) { displayClass.CaptureScope = BlockContainer.FindClosestContainer(inst); } + v = displayClass.DeclaringFunction.RegisterVariable(VariableKind.Local, field.Type, field.Name); v.CaptureScope = displayClass.CaptureScope; inst.ReplaceWith(new StLoc(v, inst.Value).WithILRange(inst)); value = new LdLoc(v); @@ -346,7 +348,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (!displayClass.Variables.TryGetValue(field, out DisplayClassVariable info)) { // Introduce a fresh variable for the display class field. Debug.Assert(displayClass.Definition == field.DeclaringTypeDefinition); - var v = currentFunction.RegisterVariable(VariableKind.Local, field.Type, field.Name); + var v = displayClass.DeclaringFunction.RegisterVariable(VariableKind.Local, field.Type, field.Name); v.CaptureScope = displayClass.CaptureScope; inst.ReplaceWith(new LdLoca(v).WithILRange(inst)); displayClass.Variables.Add(field, new DisplayClassVariable { Value = new LdLoc(v), Variable = v }); From f10ab693281a9ebea2502fd16c7e04bd258dff6e Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Tue, 23 Jul 2019 18:29:48 +0200 Subject: [PATCH 23/23] Improve local-function detection to minimize false positives. --- .../CSharp/ExpressionBuilder.cs | 2 +- .../IL/Transforms/LocalFunctionDecompiler.cs | 29 ++++++++++++------- .../Transforms/TransformDisplayClassUsage.cs | 2 +- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 87602db3f..e7e560d16 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -1877,7 +1877,7 @@ namespace ICSharpCode.Decompiler.CSharp // needs to be consistent with logic in ILReader.CreateILVarable(ParameterDefinition) pd.Name = "P_" + i; // if this is a local function, we have to skip the parameters for closure references - if (settings.LocalFunctions && function.Kind == ILFunctionKind.LocalFunction && IL.Transforms.LocalFunctionDecompiler.IsClosureParameter(parameter)) + if (settings.LocalFunctions && function.Kind == ILFunctionKind.LocalFunction && IL.Transforms.LocalFunctionDecompiler.IsClosureParameter(parameter, decompilationContext)) break; } if (settings.AnonymousTypes && parameter.Type.ContainsAnonymousType()) diff --git a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs index dafda4357..85753bd9e 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs @@ -39,6 +39,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms class LocalFunctionDecompiler : IILTransform { ILTransformContext context; + ITypeResolveContext resolveContext; struct LocalFunctionInfo { @@ -64,6 +65,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (!context.Settings.LocalFunctions) return; this.context = context; + this.resolveContext = new SimpleTypeResolveContext(function.Method); var localFunctions = new Dictionary(); var cancellationToken = context.CancellationToken; // Find all local functions declared inside this method, including nested local functions or local functions declared in lambdas. @@ -161,11 +163,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms return a.Ancestors.OfType().FirstOrDefault(ancestorsOfB.Contains); } - internal static bool IsClosureParameter(IParameter parameter) + internal static bool IsClosureParameter(IParameter parameter, ITypeResolveContext context) { - return parameter.IsRef - && ((ByReferenceType)parameter.Type).ElementType - .GetDefinition()?.IsCompilerGenerated() == true; + if (!parameter.IsRef) + return false; + var type = ((ByReferenceType)parameter.Type).ElementType.GetDefinition(); + return type != null + && type.Kind == TypeKind.Struct + && TransformDisplayClassUsage.IsPotentialClosure(context.CurrentTypeDefinition, type); } static IType UnwrapByRef(IType type) @@ -190,7 +195,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms { int parametersToRemove = 0; for (int i = method.Parameters.Count - 1; i >= 0; i--) { - if (!IsClosureParameter(method.Parameters[i])) + if (!IsClosureParameter(method.Parameters[i], resolveContext)) break; parametersToRemove++; } @@ -213,23 +218,24 @@ namespace ICSharpCode.Decompiler.IL.Transforms int argumentCount = useSite.Arguments.Count; int reducedArgumentCount = argumentCount - (reducedMethod.NumberOfCompilerGeneratedParameters + firstArgumentIndex); replacement.Arguments.AddRange(useSite.Arguments.Skip(firstArgumentIndex).Take(reducedArgumentCount)); - // copy flags: + // copy flags replacement.ConstrainedTo = useSite.ConstrainedTo; replacement.ILStackWasEmpty = useSite.ILStackWasEmpty; replacement.IsTail = useSite.IsTail; // copy IL ranges - replacement = replacement.WithILRange(useSite); + replacement.AddILRange(useSite); if (wasInstanceCall) { - replacement = replacement.WithILRange(useSite.Arguments[0]); + replacement.AddILRange(useSite.Arguments[0]); } for (int i = 0; i < reducedMethod.NumberOfCompilerGeneratedParameters; i++) { - replacement = replacement.WithILRange(useSite.Arguments[argumentCount - i - 1]); + replacement.AddILRange(useSite.Arguments[argumentCount - i - 1]); } useSite.ReplaceWith(replacement); } void DetermineCaptureAndDeclarationScope(ILFunction function, CallInstruction useSite) { + int firstArgumentIndex = function.Method.IsStatic ? 0 : 1; for (int i = useSite.Arguments.Count - 1; i >= 0; i--) { var arg = useSite.Arguments[i]; ILVariable closureVar; @@ -239,6 +245,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms break; if (!TransformDisplayClassUsage.IsPotentialClosure(context, UnwrapByRef(closureVar.Type).GetDefinition())) break; + if (i - firstArgumentIndex >= 0) { + Debug.Assert(i - firstArgumentIndex < function.Method.Parameters.Count && IsClosureParameter(function.Method.Parameters[i - firstArgumentIndex], resolveContext)); + } if (closureVar.AddressCount == 0 && closureVar.StoreInstructions.Count == 0) continue; // determine the capture scope of closureVar and the declaration scope of the function @@ -262,7 +271,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; var opCode = inst.Arguments[1].OpCode; - return (opCode == OpCode.LdFtn || opCode == OpCode.LdVirtFtn) + return opCode == OpCode.LdFtn && IsLocalFunctionMethod(((IInstructionWithMethodOperand)inst.Arguments[1]).Method, context); } diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs index 382abe96a..d12191033 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs @@ -69,7 +69,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (IsClosure(v, out ITypeDefinition closureType, out var inst)) { AddOrUpdateDisplayClass(f, v, closureType, inst, localFunctionClosureParameter: false); } - if (context.Settings.LocalFunctions && f.Kind == ILFunctionKind.LocalFunction && v.Kind == VariableKind.Parameter && v.Index > -1 && f.Method.Parameters[v.Index.Value] is IParameter p && LocalFunctionDecompiler.IsClosureParameter(p)) { + if (context.Settings.LocalFunctions && f.Kind == ILFunctionKind.LocalFunction && v.Kind == VariableKind.Parameter && v.Index > -1 && f.Method.Parameters[v.Index.Value] is IParameter p && LocalFunctionDecompiler.IsClosureParameter(p, decompilationContext)) { AddOrUpdateDisplayClass(f, v, ((ByReferenceType)p.Type).ElementType.GetDefinition(), f.Body, localFunctionClosureParameter: true); } }