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
} }
} }
public interface IM3 public interface IM3
{ {
void M3(); void M3();
@ -177,6 +176,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public virtual void M3() public virtual void M3()
{ {
} }
public static void StaticMethod()
{
}
} }
public class SubClass : BaseClass public class SubClass : BaseClass
@ -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> 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> 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); 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
return (T _) => _; 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 #if CS90
public static Func<int, int, int, int> LambdaParameterDiscard() 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
public void MethodGroupAsExtensionMethod() 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() public void MethodGroupConstant()

178
ICSharpCode.Decompiler/CSharp/CallBuilder.cs

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

Loading…
Cancel
Save