From 706419544fcb0f1f2f20a01e636a680d5a6eeafa Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Wed, 20 Sep 2017 13:10:43 +0200 Subject: [PATCH 1/4] Implement support for unwrapping params arrays. --- .../CSharp/ExpressionBuilder.cs | 114 ++++++++++++------ .../Util/CollectionExtensions.cs | 14 +++ 2 files changed, 90 insertions(+), 38 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index f2cb414e1..f10ddb7f1 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -313,16 +313,21 @@ namespace ICSharpCode.Decompiler.CSharp protected internal override TranslatedExpression VisitLdNull(LdNull inst, TranslationContext context) { - return new NullReferenceExpression() - .WithILInstruction(inst) - .WithRR(new ConstantResolveResult(SpecialType.NullType, null)); + return GetDefaultValueExpression(SpecialType.NullType, inst); } protected internal override TranslatedExpression VisitDefaultValue(DefaultValue inst, TranslationContext context) { - return new DefaultValueExpression(ConvertType(inst.Type)) - .WithILInstruction(inst) - .WithRR(new ConstantResolveResult(inst.Type, null)); + return GetDefaultValueExpression(inst.Type, inst); + } + + TranslatedExpression GetDefaultValueExpression(IType type, ILInstruction inst = null) + { + var expr = type.IsReferenceType == true ? (Expression)new NullReferenceExpression() : new DefaultValueExpression(ConvertType(type)); + var constantType = type.IsReferenceType == true ? SpecialType.NullType : type; + if (inst == null) + return expr.WithoutILInstruction().WithRR(new ConstantResolveResult(constantType, null)); + return expr.WithILInstruction(inst).WithRR(new ConstantResolveResult(constantType, null)); } protected internal override TranslatedExpression VisitSizeOf(SizeOf inst, TranslationContext context) @@ -1125,13 +1130,43 @@ namespace ICSharpCode.Decompiler.CSharp int firstParamIndex = (method.IsStatic || inst.OpCode == OpCode.NewObj) ? 0 : 1; // Translate arguments to the expected parameter types - TranslatedExpression[] arguments = new TranslatedExpression[inst.Method.Parameters.Count]; - Debug.Assert(inst.Arguments.Count == firstParamIndex + inst.Method.Parameters.Count); - for (int i = 0; i < arguments.Length; i++) { - var parameter = method.Parameters[i]; - arguments[i] = Translate(inst.Arguments[firstParamIndex + i]) - .ConvertTo(parameter.Type, this, allowImplicitConversion: true); - + var arguments = new List(method.Parameters.Count); + Debug.Assert(inst.Arguments.Count == firstParamIndex + method.Parameters.Count); + var expectedParameters = method.Parameters.ToList(); + for (int i = 0; i < method.Parameters.Count; i++) { + var parameter = expectedParameters[i]; + var arg = Translate(inst.Arguments[firstParamIndex + i]); + if (parameter.IsParams) { + // Parameter is marked params + // If the argument is an array creation, inline all elements into the call and add missing default values. + // Otherwise handle it normally. + if (arg.ResolveResult is ArrayCreateResolveResult acrr && + acrr.SizeArguments.Count == 1 && + acrr.SizeArguments[0].IsCompileTimeConstant && + acrr.SizeArguments[0].ConstantValue is int length) { + var expectedParametersCopy = expectedParameters.Take(expectedParameters.Count - 1).ToList(); + var argumentsCopy = new List(arguments); + if (length > 0) { + var arrayElements = ((ArrayCreateExpression)arg.Expression).Initializer.Elements.ToArray(); + var elementType = ((ArrayType)acrr.Type).ElementType; + for (int j = 0; j < length; j++) { + expectedParametersCopy.Add(new DefaultParameter(elementType, parameter.Name + j)); + if (j < arrayElements.Length) + argumentsCopy.Add(new TranslatedExpression(arrayElements[j])); + else + argumentsCopy.Add(GetDefaultValueExpression(elementType)); + } + } + if (IsUnambiguousCall(inst, target, method, Array.Empty(), argumentsCopy) == OverloadResolutionErrors.None) { + expectedParameters = expectedParametersCopy; + arguments = argumentsCopy.SelectList(a => new TranslatedExpression(a.Expression.Detach())); + continue; + } + } + } + + arguments.Add(arg.ConvertTo(parameter.Type, this, allowImplicitConversion: true)); + if (parameter.IsOut && arguments[i].Expression is DirectionExpression dirExpr) { dirExpr.FieldDirection = FieldDirection.Out; } @@ -1142,11 +1177,12 @@ namespace ICSharpCode.Decompiler.CSharp var argListArg = new UndocumentedExpression(); argListArg.UndocumentedExpressionType = UndocumentedExpressionType.ArgList; int paramIndex = regularParameterCount; - argListArg.Arguments.AddRange(arguments.Skip(regularParameterCount).Select(arg => arg.ConvertTo(method.Parameters[paramIndex++].Type, this).Expression)); + argListArg.Arguments.AddRange(arguments.Skip(regularParameterCount).Select(arg => arg.ConvertTo(expectedParameters[paramIndex++].Type, this).Expression)); var argListRR = new ResolveResult(SpecialType.ArgList); arguments = arguments.Take(regularParameterCount) - .Concat(new[] { argListArg.WithoutILInstruction().WithRR(argListRR) }).ToArray(); + .Concat(new[] { argListArg.WithoutILInstruction().WithRR(argListRR) }).ToList(); method = (IMethod)method.MemberDefinition; + expectedParameters = method.Parameters.ToList(); } var argumentResolveResults = arguments.Select(arg => arg.ResolveResult).ToList(); @@ -1157,14 +1193,13 @@ namespace ICSharpCode.Decompiler.CSharp var argumentExpressions = arguments.SelectArray(arg => arg.Expression); if (settings.AnonymousTypes && method.DeclaringType.IsAnonymousType()) { AnonymousTypeCreateExpression atce = new AnonymousTypeCreateExpression(); - var parameters = inst.Method.Parameters; - if (CanInferAnonymousTypePropertyNamesFromArguments(argumentExpressions, parameters)) { + if (CanInferAnonymousTypePropertyNamesFromArguments(argumentExpressions, expectedParameters)) { atce.Initializers.AddRange(argumentExpressions); } else { for (int i = 0; i < argumentExpressions.Length; i++) { atce.Initializers.Add( new NamedExpression { - Name = parameters[i].Name, + Name = expectedParameters[i].Name, Expression = argumentExpressions[i] }); } @@ -1178,7 +1213,7 @@ namespace ICSharpCode.Decompiler.CSharp } } else { int allowedParamCount = (method.ReturnType.IsKnownType(KnownTypeCode.Void) ? 1 : 0); - if (method.IsAccessor && (method.AccessorOwner.SymbolKind == SymbolKind.Indexer || method.Parameters.Count == allowedParamCount)) { + if (method.IsAccessor && (method.AccessorOwner.SymbolKind == SymbolKind.Indexer || expectedParameters.Count == allowedParamCount)) { return HandleAccessorCall(inst, target, method, arguments.ToList()); } else { bool requireTypeArguments = false; @@ -1186,23 +1221,8 @@ namespace ICSharpCode.Decompiler.CSharp bool argumentsCasted = false; IType[] typeArguments = Array.Empty(); - OverloadResolutionErrors IsUnambiguousCall() - { - var lookup = new MemberLookup(resolver.CurrentTypeDefinition, resolver.CurrentTypeDefinition.ParentAssembly); - var result = lookup.Lookup(target.ResolveResult, method.Name, EmptyList.Instance, true) as MethodGroupResolveResult; - if (result == null) - return OverloadResolutionErrors.AmbiguousMatch; - var or = new OverloadResolution(resolver.Compilation, arguments.SelectArray(a => a.ResolveResult), typeArguments: typeArguments); - or.AddMethodLists(result.MethodsGroupedByDeclaringType.ToArray()); - if (or.BestCandidateErrors != OverloadResolutionErrors.None) - return or.BestCandidateErrors; - if (!IsAppropriateCallTarget(method, or.GetBestCandidateWithSubstitutedTypeArguments(), inst.OpCode == OpCode.CallVirt)) - return OverloadResolutionErrors.AmbiguousMatch; - return OverloadResolutionErrors.None; - } - OverloadResolutionErrors errors; - while ((errors = IsUnambiguousCall()) != OverloadResolutionErrors.None) { + while ((errors = IsUnambiguousCall(inst, target, method, typeArguments, arguments)) != OverloadResolutionErrors.None) { switch (errors) { case OverloadResolutionErrors.TypeInferenceFailed: case OverloadResolutionErrors.WrongNumberOfTypeArguments: @@ -1213,9 +1233,9 @@ namespace ICSharpCode.Decompiler.CSharp default: if (!argumentsCasted) { argumentsCasted = true; - for (int i = 0; i < arguments.Length; i++) { - if (!settings.AnonymousTypes || !method.Parameters[i].Type.ContainsAnonymousType()) - arguments[i] = arguments[i].ConvertTo(method.Parameters[i].Type, this); + for (int i = 0; i < arguments.Count; i++) { + if (!settings.AnonymousTypes || !expectedParameters[i].Type.ContainsAnonymousType()) + arguments[i] = arguments[i].ConvertTo(expectedParameters[i].Type, this); } } else if (!targetCasted) { targetCasted = true; @@ -1247,6 +1267,24 @@ namespace ICSharpCode.Decompiler.CSharp } } + OverloadResolutionErrors IsUnambiguousCall(ILInstruction inst, TranslatedExpression target, IMethod method, IType[] typeArguments, IList arguments) + { + // TODO : MemberLookup does not support ctors. (target is null in that case! -> NullArgumentException is thrown) + if (inst.OpCode == OpCode.NewObj) + return OverloadResolutionErrors.None; + var lookup = new MemberLookup(resolver.CurrentTypeDefinition, resolver.CurrentTypeDefinition.ParentAssembly); + var result = lookup.Lookup(target.ResolveResult, method.Name, EmptyList.Instance, true) as MethodGroupResolveResult; + if (result == null) + return OverloadResolutionErrors.AmbiguousMatch; + var or = new OverloadResolution(resolver.Compilation, arguments.SelectArray(a => a.ResolveResult), typeArguments: typeArguments); + or.AddMethodLists(result.MethodsGroupedByDeclaringType.ToArray()); + if (or.BestCandidateErrors != OverloadResolutionErrors.None) + return or.BestCandidateErrors; + if (!IsAppropriateCallTarget(method, or.GetBestCandidateWithSubstitutedTypeArguments(), inst.OpCode == OpCode.CallVirt)) + return OverloadResolutionErrors.AmbiguousMatch; + return OverloadResolutionErrors.None; + } + static bool CanInferAnonymousTypePropertyNamesFromArguments(IList args, IList parameters) { for (int i = 0; i < args.Count; i++) { diff --git a/ICSharpCode.Decompiler/Util/CollectionExtensions.cs b/ICSharpCode.Decompiler/Util/CollectionExtensions.cs index 92c243718..3263bb735 100644 --- a/ICSharpCode.Decompiler/Util/CollectionExtensions.cs +++ b/ICSharpCode.Decompiler/Util/CollectionExtensions.cs @@ -67,6 +67,20 @@ namespace ICSharpCode.Decompiler.Util return result; } + /// + /// Equivalent to collection.Select(func).ToList(), but more efficient as it makes + /// use of the input collection's known size. + /// + public static List SelectList(this ICollection collection, Func func) + { + List result = new List(collection.Count); + int index = 0; + foreach (var element in collection) { + result.Add(func(element)); + } + return result; + } + public static IEnumerable SelectWithIndex(this IEnumerable source, Func func) { int index = 0; From 02184f426de54b766816de4a4640867bbcdc70b8 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Wed, 20 Sep 2017 20:38:53 +0200 Subject: [PATCH 2/4] Use ExpressionWithResolveResult in GetDefaultValueExpression --- ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index f10ddb7f1..0d55f7e1a 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -313,21 +313,19 @@ namespace ICSharpCode.Decompiler.CSharp protected internal override TranslatedExpression VisitLdNull(LdNull inst, TranslationContext context) { - return GetDefaultValueExpression(SpecialType.NullType, inst); + return GetDefaultValueExpression(SpecialType.NullType).WithILInstruction(inst); } protected internal override TranslatedExpression VisitDefaultValue(DefaultValue inst, TranslationContext context) { - return GetDefaultValueExpression(inst.Type, inst); + return GetDefaultValueExpression(inst.Type).WithILInstruction(inst); } - TranslatedExpression GetDefaultValueExpression(IType type, ILInstruction inst = null) + ExpressionWithResolveResult GetDefaultValueExpression(IType type) { var expr = type.IsReferenceType == true ? (Expression)new NullReferenceExpression() : new DefaultValueExpression(ConvertType(type)); var constantType = type.IsReferenceType == true ? SpecialType.NullType : type; - if (inst == null) - return expr.WithoutILInstruction().WithRR(new ConstantResolveResult(constantType, null)); - return expr.WithILInstruction(inst).WithRR(new ConstantResolveResult(constantType, null)); + return expr.WithRR(new ConstantResolveResult(constantType, null)); } protected internal override TranslatedExpression VisitSizeOf(SizeOf inst, TranslationContext context) @@ -1154,7 +1152,7 @@ namespace ICSharpCode.Decompiler.CSharp if (j < arrayElements.Length) argumentsCopy.Add(new TranslatedExpression(arrayElements[j])); else - argumentsCopy.Add(GetDefaultValueExpression(elementType)); + argumentsCopy.Add(GetDefaultValueExpression(elementType).WithoutILInstruction()); } } if (IsUnambiguousCall(inst, target, method, Array.Empty(), argumentsCopy) == OverloadResolutionErrors.None) { From 0226b5a832e8db8790d5ab01b14979b68fce935e Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Wed, 20 Sep 2017 21:01:18 +0200 Subject: [PATCH 3/4] Only transform params if it's the last parameter + Refactoring of variable names. --- .../CSharp/ExpressionBuilder.cs | 18 +++++++++--------- .../Util/CollectionExtensions.cs | 1 - 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 0d55f7e1a..5126bf07d 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -1134,7 +1134,7 @@ namespace ICSharpCode.Decompiler.CSharp for (int i = 0; i < method.Parameters.Count; i++) { var parameter = expectedParameters[i]; var arg = Translate(inst.Arguments[firstParamIndex + i]); - if (parameter.IsParams) { + if (parameter.IsParams && i + 1 == method.Parameters.Count) { // Parameter is marked params // If the argument is an array creation, inline all elements into the call and add missing default values. // Otherwise handle it normally. @@ -1142,22 +1142,22 @@ namespace ICSharpCode.Decompiler.CSharp acrr.SizeArguments.Count == 1 && acrr.SizeArguments[0].IsCompileTimeConstant && acrr.SizeArguments[0].ConstantValue is int length) { - var expectedParametersCopy = expectedParameters.Take(expectedParameters.Count - 1).ToList(); - var argumentsCopy = new List(arguments); + var expandedParameters = expectedParameters.Take(expectedParameters.Count - 1).ToList(); + var expandedArguments = new List(arguments); if (length > 0) { var arrayElements = ((ArrayCreateExpression)arg.Expression).Initializer.Elements.ToArray(); var elementType = ((ArrayType)acrr.Type).ElementType; for (int j = 0; j < length; j++) { - expectedParametersCopy.Add(new DefaultParameter(elementType, parameter.Name + j)); + expandedParameters.Add(new DefaultParameter(elementType, parameter.Name + j)); if (j < arrayElements.Length) - argumentsCopy.Add(new TranslatedExpression(arrayElements[j])); + expandedArguments.Add(new TranslatedExpression(arrayElements[j])); else - argumentsCopy.Add(GetDefaultValueExpression(elementType).WithoutILInstruction()); + expandedArguments.Add(GetDefaultValueExpression(elementType).WithoutILInstruction()); } } - if (IsUnambiguousCall(inst, target, method, Array.Empty(), argumentsCopy) == OverloadResolutionErrors.None) { - expectedParameters = expectedParametersCopy; - arguments = argumentsCopy.SelectList(a => new TranslatedExpression(a.Expression.Detach())); + if (IsUnambiguousCall(inst, target, method, Array.Empty(), expandedArguments) == OverloadResolutionErrors.None) { + expectedParameters = expandedParameters; + arguments = expandedArguments.SelectList(a => new TranslatedExpression(a.Expression.Detach())); continue; } } diff --git a/ICSharpCode.Decompiler/Util/CollectionExtensions.cs b/ICSharpCode.Decompiler/Util/CollectionExtensions.cs index 3263bb735..0223d4f1e 100644 --- a/ICSharpCode.Decompiler/Util/CollectionExtensions.cs +++ b/ICSharpCode.Decompiler/Util/CollectionExtensions.cs @@ -74,7 +74,6 @@ namespace ICSharpCode.Decompiler.Util public static List SelectList(this ICollection collection, Func func) { List result = new List(collection.Count); - int index = 0; foreach (var element in collection) { result.Add(func(element)); } From 3098461b0e8759005103cec2fa09555af0cf54d0 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Wed, 20 Sep 2017 21:03:11 +0200 Subject: [PATCH 4/4] Add support for ctors to IsUnambiguousCall --- .../CSharp/ExpressionBuilder.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 5126bf07d..68eb7c4af 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -1267,15 +1267,20 @@ namespace ICSharpCode.Decompiler.CSharp OverloadResolutionErrors IsUnambiguousCall(ILInstruction inst, TranslatedExpression target, IMethod method, IType[] typeArguments, IList arguments) { - // TODO : MemberLookup does not support ctors. (target is null in that case! -> NullArgumentException is thrown) - if (inst.OpCode == OpCode.NewObj) - return OverloadResolutionErrors.None; var lookup = new MemberLookup(resolver.CurrentTypeDefinition, resolver.CurrentTypeDefinition.ParentAssembly); - var result = lookup.Lookup(target.ResolveResult, method.Name, EmptyList.Instance, true) as MethodGroupResolveResult; - if (result == null) - return OverloadResolutionErrors.AmbiguousMatch; var or = new OverloadResolution(resolver.Compilation, arguments.SelectArray(a => a.ResolveResult), typeArguments: typeArguments); - or.AddMethodLists(result.MethodsGroupedByDeclaringType.ToArray()); + if (inst is NewObj newObj) { + foreach (IMethod ctor in newObj.Method.DeclaringType.GetConstructors()) { + if (lookup.IsAccessible(ctor, allowProtectedAccess: resolver.CurrentTypeDefinition == newObj.Method.DeclaringTypeDefinition)) { + or.AddCandidate(ctor); + } + } + } else { + var result = lookup.Lookup(target.ResolveResult, method.Name, EmptyList.Instance, true) as MethodGroupResolveResult; + if (result == null) + return OverloadResolutionErrors.AmbiguousMatch; + or.AddMethodLists(result.MethodsGroupedByDeclaringType.ToArray()); + } if (or.BestCandidateErrors != OverloadResolutionErrors.None) return or.BestCandidateErrors; if (!IsAppropriateCallTarget(method, or.GetBestCandidateWithSubstitutedTypeArguments(), inst.OpCode == OpCode.CallVirt))