Browse Source

Fix #2634: target expressions of delegate references were not decompiled correctly

pull/2670/head
Siegfried Pammer 3 years ago
parent
commit
0bc11d05d3
  1. 66
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs
  2. 2
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs
  3. 178
      ICSharpCode.Decompiler/CSharp/CallBuilder.cs

66
ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs

@ -160,7 +160,6 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -160,7 +160,6 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
}
}
public interface IM3
{
void M3();
@ -177,6 +176,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -177,6 +176,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public virtual void M3()
{
}
public static void StaticMethod()
{
}
}
public class SubClass : BaseClass
@ -247,6 +251,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -247,6 +251,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
}
}
private delegate void GenericDelegate<T>();
public static Func<string, string, bool> test0 = (string a, string b) => string.IsNullOrEmpty(a) || string.IsNullOrEmpty(b);
public static Func<string, string, bool> test1 = (string a, string b) => string.IsNullOrEmpty(a) || !string.IsNullOrEmpty(b);
public static Func<string, string, bool> test2 = (string a, string b) => !string.IsNullOrEmpty(a) || string.IsNullOrEmpty(b);
@ -457,6 +463,64 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -457,6 +463,64 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
return (T _) => _;
}
private static void Use(Action a)
{
}
private static void Use2(Func<Func<int, int>, IEnumerable<int>> a)
{
}
private static void Use2<T>(GenericDelegate<T> a)
{
}
private static void Use3<T>(Func<Func<T, T>> a)
{
}
public static void SimpleDelegateReference()
{
Use(SimpleDelegateReference);
#if !MCS2
Use3(Identity<int>);
#endif
}
public static void DelegateReferenceWithStaticTarget()
{
Use(NameConflict);
Use(BaseClass.StaticMethod);
}
public static void ExtensionDelegateReference(IEnumerable<int> ints)
{
Use2((Func<Func<int, int>, IEnumerable<int>>)ints.Select<int, int>);
}
#if CS70
public static void LocalFunctionDelegateReference()
{
Use(LocalFunction);
Use2<int>(LocalFunction1<int>);
#if CS80
static void LocalFunction()
#else
void LocalFunction()
#endif
{
}
#if CS80
static void LocalFunction1<T>()
#else
void LocalFunction1<T>()
#endif
{
}
}
#endif
#if CS90
public static Func<int, int, int, int> LambdaParameterDiscard()
{

2
ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs

@ -467,7 +467,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -467,7 +467,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public void MethodGroupAsExtensionMethod()
{
ToCode(X(), (Expression<Func<Func<bool>>>)(() => ((IEnumerable<int>)new int[4] { 2000, 2004, 2008, 2012 }).Any));
ToCode(X(), (Expression<Func<Func<bool>>>)(() => ((IEnumerable<int>)new int[4] { 2000, 2004, 2008, 2012 }).Any<int>));
}
public void MethodGroupConstant()

178
ICSharpCode.Decompiler/CSharp/CallBuilder.cs

@ -1570,58 +1570,76 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1570,58 +1570,76 @@ namespace ICSharpCode.Decompiler.CSharp
ExpressionWithResolveResult BuildDelegateReference(IMethod method, IMethod invokeMethod, ExpectedTargetDetails expectedTargetDetails, ILInstruction thisArg)
{
TranslatedExpression target;
IType targetType;
bool requireTarget;
ResolveResult result = null;
string methodName = method.Name;
// There are three possible adjustments, we can make, to solve conflicts:
// 1. add target (represented as bit 0)
// 2. add type arguments (represented as bit 1)
// 3. cast target (represented as bit 2)
int step;
ILFunction localFunction = null;
if (method.IsLocalFunction)
ExpressionBuilder expressionBuilder = this.expressionBuilder;
ExpressionWithResolveResult targetExpression;
(TranslatedExpression target, bool addTypeArguments, string methodName, ResolveResult result) = DisambiguateDelegateReference(method, invokeMethod, expectedTargetDetails, thisArg);
if (target.Expression != null)
{
localFunction = expressionBuilder.ResolveLocalFunction(method);
Debug.Assert(localFunction != null);
var mre = new MemberReferenceExpression(target, methodName);
if (addTypeArguments)
{
mre.TypeArguments.AddRange(method.TypeArguments.Select(expressionBuilder.ConvertType));
}
targetExpression = mre.WithRR(result);
}
if (localFunction != null)
else
{
step = 2;
requireTarget = false;
result = ToMethodGroup(method, localFunction);
target = default;
targetType = default;
methodName = localFunction.Name;
var ide = new IdentifierExpression(methodName);
if (addTypeArguments)
{
ide.TypeArguments.AddRange(method.TypeArguments.Select(expressionBuilder.ConvertType));
}
targetExpression = ide.WithRR(result);
}
else if (method.IsExtensionMethod && invokeMethod != null && method.Parameters.Count - 1 == invokeMethod.Parameters.Count)
return targetExpression;
}
(TranslatedExpression target, bool addTypeArguments, string methodName, ResolveResult result) DisambiguateDelegateReference(IMethod method, IMethod invokeMethod, ExpectedTargetDetails expectedTargetDetails, ILInstruction thisArg)
{
if (method.IsLocalFunction)
{
step = 5;
targetType = method.Parameters[0].Type;
ILFunction localFunction = expressionBuilder.ResolveLocalFunction(method);
Debug.Assert(localFunction != null);
return (default, addTypeArguments: true, localFunction.Name, ToMethodGroup(method, localFunction));
}
if (method.IsExtensionMethod && method.Parameters.Count - 1 == invokeMethod?.Parameters.Count)
{
IType 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);
// TODO : check if cast is necessary
TranslatedExpression target = expressionBuilder.Translate(thisArg, targetType);
bool targetCasted = false;
bool addTypeArguments = false;
// Initial inputs for IsUnambiguousMethodReference:
ResolveResult targetResolveResult = target.ResolveResult;
IReadOnlyList<IType> typeArguments = EmptyList<IType>.Instance;
// Find somewhat minimal solution:
ResolveResult result;
targetCasted = true;
target = target.ConvertTo(targetType, expressionBuilder);
requireTarget = true;
targetResolveResult = target.ResolveResult;
addTypeArguments = true;
typeArguments = method.TypeArguments;
result = new MethodGroupResolveResult(
target.ResolveResult, method.Name,
new MethodListWithDeclaringType[] {
new MethodListWithDeclaringType(
null,
new[] { method }
)
},
method.TypeArguments
);
targetResolveResult, method.Name,
new[] {
new MethodListWithDeclaringType(
null,
new[] { method }
)
},
typeArguments
);
return (target, addTypeArguments, method.Name, result);
}
else
{
targetType = method.DeclaringType;
// Prepare call target
IType 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',
@ -1635,71 +1653,57 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1635,71 +1653,57 @@ namespace ICSharpCode.Decompiler.CSharp
thisArg = new AddressOf(thisArgBox.Argument, thisArgBox.Type);
}
}
target = expressionBuilder.TranslateTarget(thisArg,
TranslatedExpression target = expressionBuilder.TranslateTarget(thisArg,
nonVirtualInvocation: expectedTargetDetails.CallOpCode == OpCode.Call,
memberStatic: method.IsStatic,
memberDeclaringType: method.DeclaringType);
requireTarget = expressionBuilder.HidesVariableWithName(method.Name)
// check if target is required
bool requireTarget = expressionBuilder.HidesVariableWithName(method.Name)
|| (method.IsStatic ? !expressionBuilder.IsCurrentOrContainingType(method.DeclaringTypeDefinition) : !(target.Expression is ThisReferenceExpression));
step = requireTarget ? 1 : 0;
var savedTarget = target;
for (; step < 7; step++)
{
ResolveResult targetResolveResult;
//TODO: why there is an check for IsLocalFunction here, it should be unreachable in old code
if (localFunction == null && (step & 1) != 0)
{
targetResolveResult = savedTarget.ResolveResult;
target = savedTarget;
}
else
{
targetResolveResult = null;
}
IReadOnlyList<IType> typeArguments;
if ((step & 2) != 0)
// Try to find minimal expression
// If target is required, include it from the start
bool targetAdded = requireTarget;
TranslatedExpression currentTarget = targetAdded ? target : default;
// Remember other decisions:
bool targetCasted = false;
bool addTypeArguments = false;
// Initial inputs for IsUnambiguousMethodReference:
ResolveResult targetResolveResult = targetAdded ? target.ResolveResult : null;
IReadOnlyList<IType> typeArguments = EmptyList<IType>.Instance;
// Find somewhat minimal solution:
ResolveResult result;
while (!IsUnambiguousMethodReference(expectedTargetDetails, method, targetResolveResult, typeArguments, out result))
{
if (!addTypeArguments)
{
// try adding type arguments
addTypeArguments = true;
typeArguments = method.TypeArguments;
continue;
}
else
if (!targetAdded)
{
typeArguments = EmptyList<IType>.Instance;
// try adding target
targetAdded = true;
currentTarget = target;
targetResolveResult = target.ResolveResult;
continue;
}
if (targetResolveResult != null && targetType != null && (step & 4) != 0)
if (!targetCasted)
{
target = target.ConvertTo(targetType, expressionBuilder);
targetResolveResult = target.ResolveResult;
// try casting target
targetCasted = true;
currentTarget = currentTarget.ConvertTo(targetType, expressionBuilder);
targetResolveResult = currentTarget.ResolveResult;
continue;
}
bool success = IsUnambiguousMethodReference(expectedTargetDetails, method, targetResolveResult, typeArguments, out var newResult);
if (newResult is MethodGroupResolveResult || result == null)
result = newResult;
if (success)
break;
}
}
requireTarget = localFunction == null && (step & 1) != 0;
ExpressionWithResolveResult targetExpression;
Debug.Assert(result != null);
if (requireTarget)
{
Debug.Assert(target.Expression != null);
var mre = new MemberReferenceExpression(target, methodName);
if ((step & 2) != 0)
mre.TypeArguments.AddRange(method.TypeArguments.Select(expressionBuilder.ConvertType));
targetExpression = mre.WithRR(result);
}
else
{
var ide = new IdentifierExpression(methodName);
if ((step & 2) != 0)
{
ide.TypeArguments.AddRange(method.TypeArguments.Select(expressionBuilder.ConvertType));
break;
}
targetExpression = ide.WithRR(result);
return (currentTarget, addTypeArguments, method.Name, result);
}
return targetExpression;
}
TranslatedExpression HandleDelegateConstruction(IType delegateType, IMethod method, ExpectedTargetDetails expectedTargetDetails, ILInstruction thisArg, ILInstruction inst)
{
var invokeMethod = delegateType.GetDelegateInvokeMethod();

Loading…
Cancel
Save