Browse Source

Fix #1590: Cast from override method to Delegate is not properly simplified

pull/1612/head
Siegfried Pammer 6 years ago
parent
commit
3d60c7bd70
  1. 53
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs
  2. 2
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs
  3. 116
      ICSharpCode.Decompiler/CSharp/CallBuilder.cs

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

@ -146,6 +146,59 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -146,6 +146,59 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
}
}
public interface IM3
{
void M3();
}
public class BaseClass : IM3
{
protected virtual void M1()
{
}
protected virtual void M2()
{
}
public virtual void M3()
{
}
}
public class SubClass : BaseClass
{
protected override void M2()
{
}
public new void M3()
{
}
public void Test()
{
Noop("M1.base", base.M1);
Noop("M1", M1);
Noop("M2.base", base.M2);
Noop("M2", M2);
Noop("M3.base", base.M3);
Noop("M3.base_virt", ((BaseClass)this).M3);
Noop("M3.base_interface", ((IM3)this).M3);
#if CS70
Noop("M3", this.M3);
Noop("M3", M3);
void M3()
{
}
#else
Noop("M3", M3);
#endif
}
private void Noop(string name, Action _)
{
}
}
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);

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

@ -585,7 +585,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -585,7 +585,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
2004,
2008,
2012
}).Any<int>));
}).Any));
}
public void MethodGroupConstant()

116
ICSharpCode.Decompiler/CSharp/CallBuilder.cs

@ -1209,12 +1209,15 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1209,12 +1209,15 @@ namespace ICSharpCode.Decompiler.CSharp
ILInstruction thisArg = inst.Arguments[0];
ILInstruction func = inst.Arguments[1];
IMethod method;
ExpectedTargetDetails expectedTargetDetails = default;
switch (func.OpCode) {
case OpCode.LdFtn:
method = ((LdFtn)func).Method;
expectedTargetDetails.CallOpCode = OpCode.Call;
break;
case OpCode.LdVirtFtn:
method = ((LdVirtFtn)func).Method;
expectedTargetDetails.CallOpCode = OpCode.CallVirt;
break;
default:
throw new ArgumentException($"Unknown instruction type: {func.OpCode}");
@ -1223,26 +1226,43 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1223,26 +1226,43 @@ namespace ICSharpCode.Decompiler.CSharp
TranslatedExpression target;
IType targetType;
bool requireTarget;
var expectedTargetDetails = new ExpectedTargetDetails {
CallOpCode = inst.OpCode
};
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;
if (method.IsLocalFunction) {
step = 0;
requireTarget = false;
var localFunction = expressionBuilder.ResolveLocalFunction(method);
result = ToMethodGroup(method, localFunction);
target = default;
targetType = default;
methodName = localFunction.Name;
// TODO : think about how to handle generic local functions
} else if (method.IsExtensionMethod && invokeMethod != null && method.Parameters.Count - 1 == invokeMethod.Parameters.Count) {
step = 5;
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
target = target.ConvertTo(targetType, expressionBuilder);
requireTarget = true;
result = new MethodGroupResolveResult(
target.ResolveResult, method.Name,
new MethodListWithDeclaringType[] {
new MethodListWithDeclaringType(
null,
new[] { method }
)
},
method.TypeArguments
);
} else {
targetType = method.DeclaringType;
if (targetType.IsReferenceType == false && thisArg is Box thisArgBox) {
@ -1260,43 +1280,48 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1260,43 +1280,48 @@ namespace ICSharpCode.Decompiler.CSharp
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;
}
}
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));
var savedTarget = target;
for (step = requireTarget ? 1 : 0; step < 7; step++) {
ResolveResult targetResolveResult;
if (!method.IsLocalFunction && (step & 1) != 0) {
targetResolveResult = savedTarget.ResolveResult;
target = savedTarget;
} else {
targetResolveResult = null;
}
IReadOnlyList<IType> typeArguments;
if ((step & 2) != 0) {
typeArguments = method.TypeArguments;
} else {
typeArguments = EmptyList<IType>.Instance;
}
if (targetResolveResult != null && targetType != null && (step & 4) != 0) {
target = target.ConvertTo(targetType, expressionBuilder);
targetResolveResult = target.ResolveResult;
}
bool success = IsUnambiguousMethodReference(expectedTargetDetails, method, targetResolveResult, typeArguments, out var newResult);
if (newResult is MethodGroupResolveResult || result == null)
result = newResult;
if (success)
break;
}
}
if (needsCast) {
Debug.Assert(requireTarget);
target = target.ConvertTo(targetType, expressionBuilder);
result = lookup.Lookup(target.ResolveResult, method.Name, method.TypeArguments, false);
}
requireTarget = !method.IsLocalFunction && (step & 1) != 0;
Expression targetExpression;
Debug.Assert(result != null);
if (requireTarget) {
Debug.Assert(target.Expression != null);
var mre = new MemberReferenceExpression(target, methodName);
mre.TypeArguments.AddRange(method.TypeArguments.Select(expressionBuilder.ConvertType));
if ((step & 2) != 0)
mre.TypeArguments.AddRange(method.TypeArguments.Select(expressionBuilder.ConvertType));
mre.WithRR(result);
targetExpression = mre;
} else {
var ide = new IdentifierExpression(methodName)
.WithRR(result);
var ide = new IdentifierExpression(methodName);
if ((step & 2) != 0)
ide.TypeArguments.AddRange(method.TypeArguments.Select(expressionBuilder.ConvertType));
ide.WithRR(result);
targetExpression = ide;
}
var oce = new ObjectCreateExpression(expressionBuilder.ConvertType(inst.Method.DeclaringType), targetExpression)
@ -1308,6 +1333,33 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1308,6 +1333,33 @@ namespace ICSharpCode.Decompiler.CSharp
return oce;
}
bool IsUnambiguousMethodReference(ExpectedTargetDetails expectedTargetDetails, IMethod method, ResolveResult target, IReadOnlyList<IType> typeArguments, out ResolveResult result)
{
var lookup = new MemberLookup(resolver.CurrentTypeDefinition, resolver.CurrentTypeDefinition.ParentModule);
var or = new OverloadResolution(resolver.Compilation,
arguments: method.Parameters.SelectReadOnlyArray(p => new TypeResolveResult(p.Type)), // there are no arguments, use parameter types
argumentNames: null, // argument names are not possible
typeArguments.ToArray(),
conversions: expressionBuilder.resolver.conversions
);
if (target == null) {
result = resolver.ResolveSimpleName(method.Name, typeArguments, isInvocationTarget: false);
if (!(result is MethodGroupResolveResult mgrr))
return false;
or.AddMethodLists(mgrr.MethodsGroupedByDeclaringType.ToArray());
} else {
result = lookup.Lookup(target, method.Name, typeArguments, isInvocation: false);
if (!(result is MethodGroupResolveResult mgrr))
return false;
or.AddMethodLists(mgrr.MethodsGroupedByDeclaringType.ToArray());
}
var foundMethod = or.GetBestCandidateWithSubstitutedTypeArguments();
if (!IsAppropriateCallTarget(expectedTargetDetails, method, foundMethod))
return false;
return result is MethodGroupResolveResult;
}
static MethodGroupResolveResult ToMethodGroup(IMethod method, ILFunction localFunction)
{
return new MethodGroupResolveResult(

Loading…
Cancel
Save