Browse Source

Add support for local function references and recursive local functions.

pull/1586/head
Siegfried Pammer 7 years ago
parent
commit
16d6e16da7
  1. 122
      ICSharpCode.Decompiler/CSharp/CallBuilder.cs
  2. 2
      ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs
  3. 87
      ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs
  4. 8
      ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs

122
ICSharpCode.Decompiler/CSharp/CallBuilder.cs

@ -183,11 +183,14 @@ namespace ICSharpCode.Decompiler.CSharp @@ -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 @@ -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 @@ -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;
}

2
ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs

@ -169,7 +169,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -169,7 +169,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// Replaces loads of 'this' with the target expression.
/// Async delegates use: ldobj(ldloca this).
/// </summary>
class ReplaceDelegateTargetVisitor : ILVisitor
internal class ReplaceDelegateTargetVisitor : ILVisitor
{
readonly ILVariable thisVariable;
readonly ILInstruction target;

87
ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs

@ -19,6 +19,7 @@ @@ -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 @@ -42,33 +43,46 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return;
this.context = context;
this.decompilationContext = new SimpleTypeResolveContext(function.Method);
var localFunctions = new Dictionary<IMethod, List<Call>>();
var localFunctions = new Dictionary<IMethod, List<CallInstruction>>();
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<ILFunction>().Any(f => f.LocalFunctions.ContainsKey(call.Method)))
continue;
if (!localFunctions.TryGetValue(call.Method, out var info)) {
info = new List<Call>() { call };
info = new List<CallInstruction>() { 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<ILFunction>().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<CallInstruction>() { 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<Call> useSites)
static ILInstruction FindInsertionPoint(List<CallInstruction> useSites)
{
ILInstruction insertionPoint = null;
foreach (var call in useSites) {
@ -85,14 +99,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -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 @@ -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<CallInstruction> 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 @@ -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<CallInstruction> 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<CallInstruction> 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);

8
ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs

@ -27,7 +27,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -27,7 +27,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// <summary>
/// Transforms closure fields to local variables.
///
/// This is a post-processing step of <see cref="DelegateConstruction"/> and <see cref="TransformExpressionTrees"/>.
/// This is a post-processing step of <see cref="DelegateConstruction"/>, <see cref="LocalFunctionDecompiler"/> and <see cref="TransformExpressionTrees"/>.
/// </summary>
class TransformDisplayClassUsage : ILVisitor, IILTransform
{
@ -68,7 +68,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -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 @@ -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;
}

Loading…
Cancel
Save