From bde782e4f5489248b9ab4750a1bcb1a205e2a871 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Thu, 27 Oct 2022 19:16:05 +0200 Subject: [PATCH] Fix #2806: Do not use implicitly typed out variables, if argument and parameter types do not match exactly. --- .../TestCases/Pretty/OutVariables.cs | 29 +++++++++++++++++++ ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 22 ++++++++++++-- .../Resolver/OverloadResolutionErrors.cs | 6 +++- .../IL/Transforms/LocalFunctionDecompiler.cs | 13 ++------- .../Semantics/OutVarResolveResult.cs | 7 +++-- .../TypeSystem/TypeSystemExtensions.cs | 9 ++++++ 6 files changed, 70 insertions(+), 16 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs index bdfc8cbfa..96d122322 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs @@ -43,5 +43,34 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } return null; } + + private bool TryGet(out T result) + { + result = default(T); + return true; + } + + public void M3() + { + TryGet>(out Dictionary data); + + Test(); + + int Test() + { + return data[0].A; + } + } + + public void GetObject(out object obj) + { + obj = null; + } + + public void M4() + { + GetObject(out dynamic obj); + obj.Method(); + } } } diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index 5213febe3..bd41044dc 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -77,8 +77,8 @@ namespace ICSharpCode.Decompiler.CSharp ResolveResult GetResolveResult(int index, TranslatedExpression expression) { var param = expectedParameters[index]; - if (useImplicitlyTypedOut && param.IsOut) - return OutVarResolveResult.Instance; + if (useImplicitlyTypedOut && param.IsOut && expression.Type is ByReferenceType brt) + return new OutVarResolveResult(brt.ElementType); return expression.ResolveResult; } } @@ -1017,6 +1017,10 @@ namespace ICSharpCode.Decompiler.CSharp { switch (errors) { + case OverloadResolutionErrors.OutVarTypeMismatch: + Debug.Assert(argumentList.UseImplicitlyTypedOut); + argumentList.UseImplicitlyTypedOut = false; + continue; case OverloadResolutionErrors.TypeInferenceFailed: if ((allowedTransforms & CallTransformation.RequireTypeArguments) != 0) { @@ -1318,6 +1322,20 @@ namespace ICSharpCode.Decompiler.CSharp foundMember = or.GetBestCandidateWithSubstitutedTypeArguments(); if (!IsAppropriateCallTarget(expectedTargetDetails, method, foundMember)) return OverloadResolutionErrors.AmbiguousMatch; + var map = or.GetArgumentToParameterMap(); + for (int i = 0; i < arguments.Length; i++) + { + ResolveResult arg = arguments[i]; + int parameterIndex = map[i]; + if (arg is OutVarResolveResult rr && parameterIndex >= 0) + { + var param = foundMember.Parameters[parameterIndex]; + var paramType = param.Type.UnwrapByRef(); + if (!paramType.Equals(rr.OriginalVariableType)) + return OverloadResolutionErrors.OutVarTypeMismatch; + } + } + return OverloadResolutionErrors.None; } diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolutionErrors.cs b/ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolutionErrors.cs index 4ef9a7b98..a8fb31416 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolutionErrors.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolutionErrors.cs @@ -82,6 +82,10 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver /// /// This error does not prevent a candidate from being applicable. /// - MethodConstraintsNotSatisfied = 0x0800 + MethodConstraintsNotSatisfied = 0x0800, + /// + /// Using 'out var' instead of 'out T' would result in loss of type information. + /// + OutVarTypeMismatch = 0x1000, } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs index 1a10d2c7a..ebc9a149a 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs @@ -493,7 +493,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms } if (targetMethod.TypeParameters.Count > 0) { - var lastParams = targetMethod.Parameters.Where(p => IsClosureParameter(p, this.resolveContext)).SelectMany(p => UnwrapByRef(p.Type).TypeArguments) + var lastParams = targetMethod.Parameters.Where(p => IsClosureParameter(p, this.resolveContext)).SelectMany(p => p.Type.UnwrapByRef().TypeArguments) .Select(pt => (int?)targetMethod.TypeParameters.IndexOf(pt)).DefaultIfEmpty().Max(); if (lastParams != null && lastParams.GetValueOrDefault() + 1 > skipCount) skipCount = lastParams.GetValueOrDefault() + 1; @@ -564,15 +564,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms && TransformDisplayClassUsage.IsPotentialClosure(context.CurrentTypeDefinition, type); } - static IType UnwrapByRef(IType type) - { - if (type is ByReferenceType byRef) - { - type = byRef.ElementType; - } - return type; - } - internal static ILInstruction GetStatement(ILInstruction inst) { while (inst.Parent != null) @@ -733,7 +724,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms ILInstruction GetClosureInitializer(ILVariable variable) { - var type = UnwrapByRef(variable.Type).GetDefinition(); + var type = variable.Type.UnwrapByRef().GetDefinition(); if (type == null) return null; if (variable.Kind == VariableKind.Parameter) diff --git a/ICSharpCode.Decompiler/Semantics/OutVarResolveResult.cs b/ICSharpCode.Decompiler/Semantics/OutVarResolveResult.cs index 214c994b5..d77c16096 100644 --- a/ICSharpCode.Decompiler/Semantics/OutVarResolveResult.cs +++ b/ICSharpCode.Decompiler/Semantics/OutVarResolveResult.cs @@ -26,8 +26,11 @@ namespace ICSharpCode.Decompiler.Semantics /// class OutVarResolveResult : ResolveResult { - public static readonly OutVarResolveResult Instance = new OutVarResolveResult(); + /// + /// Type of the variable originally used in IL. It will be used, if "out var" cannot be used. + /// + public readonly IType OriginalVariableType; - public OutVarResolveResult() : base(SpecialType.NoType) { } + public OutVarResolveResult(IType originalVariableType) : base(SpecialType.NoType) { OriginalVariableType = originalVariableType; } } } diff --git a/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs b/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs index c0e109de8..ba3958a0b 100644 --- a/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs +++ b/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs @@ -374,6 +374,15 @@ namespace ICSharpCode.Decompiler.TypeSystem return ty; } + public static IType UnwrapByRef(this IType type) + { + if (type is ByReferenceType byRef) + { + type = byRef.ElementType; + } + return type; + } + public static bool HasReadonlyModifier(this IMethod accessor) { return accessor.ThisIsRefReadOnly && accessor.DeclaringTypeDefinition?.IsReadOnly == false;