diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicAwait.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicAwait.cs index 3e091148d..ec020e2d4 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicAwait.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicAwait.cs @@ -162,6 +162,17 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty return (string)(await d.RunAsync()); } + // --- static-type call target spilled across an await --- + + private static void StaticMember(dynamic d, int value) + { + } + + private static async Task StaticTargetWithAwaitedArgument(object o) + { + DynamicAwait.StaticMember((dynamic)o, await GetIntAsync(null)); + } + // --- fully dynamic awaiter (the awaitable itself is dynamic) --- private static async Task AwaitDynamicValue(dynamic d) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicTests.cs index 5de9db12a..9fb7bc2b3 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicTests.cs @@ -23,6 +23,13 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } + private class CtorTarget + { + public CtorTarget(dynamic d, int i) + { + } + } + private struct MyValueType { private readonly dynamic _getOnlyProperty; @@ -368,6 +375,69 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { } + private static byte[] GetData() + { + return null; + } + + private static void M4(dynamic d, int i) + { + } + + private static dynamic M5(dynamic d, int i) + { + return null; + } + + private static void M6(dynamic d, int i) + { + } + +#if CS60 + // #3704: the call target of a dynamic member access on a static type is typeof(DynamicTests). + // The array is loaded into a temporary that survives as its own statement, which strands the + // typeof between the two, so it cannot reach the call site by ordinary inlining. + private static void StaticTargetBehindSurvivingTemporary(dynamic d) + { +#if EXPECTED_OUTPUT + byte[] data = GetData(); + DynamicTests.M4(d, (data != null) ? data.Length : 0); +#else + DynamicTests.M4(d, GetData()?.Length ?? 0); +#endif + } + + private static dynamic StaticTargetResultUsed(dynamic d) + { +#if EXPECTED_OUTPUT + byte[] data = GetData(); + return DynamicTests.M5(d, (data != null) ? data.Length : 0); +#else + return DynamicTests.M5(d, GetData()?.Length ?? 0); +#endif + } + + private static void StaticTargetWithTypeArguments(dynamic d) + { +#if EXPECTED_OUTPUT + byte[] data = GetData(); + DynamicTests.M6(d, (data != null) ? data.Length : 0); +#else + DynamicTests.M6(d, GetData()?.Length ?? 0); +#endif + } + + private static void StaticCtorBehindSurvivingTemporary(dynamic d) + { +#if EXPECTED_OUTPUT + byte[] data = GetData(); + new CtorTarget(d, (data != null) ? data.Length : 0); +#else + new CtorTarget(d, GetData()?.Length ?? 0); +#endif + } +#endif + private static void NotDynamicDispatch(dynamic d) { DynamicTests.M(d); diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 5a3c6b2cc..ec1be0aa4 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -4573,9 +4573,8 @@ namespace ICSharpCode.Decompiler.CSharp protected internal override TranslatedExpression VisitDynamicInvokeConstructorInstruction(DynamicInvokeConstructorInstruction inst, TranslationContext context) { - if (!(inst.ArgumentInfo[0].HasFlag(CSharpArgumentInfoFlags.IsStaticType) && IL.Transforms.TransformExpressionTrees.MatchGetTypeFromHandle(inst.Arguments[0], out var constructorType))) - return ErrorExpression("Could not detect static type for DynamicInvokeConstructorInstruction"); - var arguments = TranslateDynamicArguments(inst.Arguments.Skip(1), inst.ArgumentInfo.Skip(1)).ToList(); + var constructorType = inst.Type; + var arguments = TranslateDynamicArguments(inst.Arguments, inst.ArgumentInfo.Skip(1)).ToList(); var constructor = CreateDynamicConstructorSymbol(constructorType, inst.ArgumentInfo.Skip(1).ToArray()); return new ObjectCreateExpression(ConvertType(constructorType), arguments.Select(a => a.Expression)) .WithILInstruction(inst) @@ -4585,7 +4584,11 @@ namespace ICSharpCode.Decompiler.CSharp protected internal override TranslatedExpression VisitDynamicInvokeMemberInstruction(DynamicInvokeMemberInstruction inst, TranslationContext context) { Expression targetExpr; - var target = TranslateDynamicTarget(inst.Arguments[0], inst.ArgumentInfo[0]); + var target = inst.StaticTargetType is IType staticTargetType + ? new TypeReferenceExpression(ConvertType(staticTargetType)) + .WithoutILInstruction() + .WithRR(new TypeResolveResult(staticTargetType)) + : TranslateDynamicTarget(inst.Arguments[0], inst.ArgumentInfo[0]); if (inst.BinderFlags.HasFlag(CSharpBinderFlags.InvokeSimpleName) && target.Expression is ThisReferenceExpression) { targetExpr = new IdentifierExpression(inst.Name); @@ -4595,7 +4598,8 @@ namespace ICSharpCode.Decompiler.CSharp { targetExpr = new MemberReferenceExpression(target, inst.Name, inst.TypeArguments.Select(ConvertType)); } - var arguments = TranslateDynamicArguments(inst.Arguments.Skip(1), inst.ArgumentInfo.Skip(1)).ToList(); + IEnumerable argumentValues = inst.StaticTargetType != null ? inst.Arguments : inst.Arguments.Skip(1); + var arguments = TranslateDynamicArguments(argumentValues, inst.ArgumentInfo.Skip(1)).ToList(); return new InvocationExpression(targetExpr, arguments.Select(a => a.Expression)) .WithILInstruction(inst) .WithRR(new DynamicInvocationResolveResult(target.ResolveResult, DynamicInvocationType.Invocation, arguments.Select(a => a.ResolveResult).ToArray(), symbol: CreateDynamicInvokeMemberSymbol(inst.Name, inst.ArgumentInfo[0], inst.ArgumentInfo.Skip(1).ToArray(), inst.TypeArguments))); diff --git a/ICSharpCode.Decompiler/IL/Instructions/DynamicInstructions.cs b/ICSharpCode.Decompiler/IL/Instructions/DynamicInstructions.cs index 3e037a336..2d938d72e 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/DynamicInstructions.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/DynamicInstructions.cs @@ -172,11 +172,22 @@ namespace ICSharpCode.Decompiler.IL public IReadOnlyList TypeArguments { get; } public IReadOnlyList ArgumentInfo { get; } - public DynamicInvokeMemberInstruction(CSharpBinderFlags binderFlags, string name, IType[]? typeArguments, IType? context, CSharpArgumentInfo[] argumentInfo, ILInstruction[] arguments) + /// + /// The type the member is invoked on, set when the target carries CSharpArgumentInfoFlags.IsStaticType; + /// null for a dynamic receiver. When set, the target is not in , but ArgumentInfo[0] + /// still describes it. + /// + public readonly IType? StaticTargetType; + + /// Offset from an index into to the matching ArgumentInfo entry. + int ArgumentInfoOffset => StaticTargetType != null ? 1 : 0; + + public DynamicInvokeMemberInstruction(CSharpBinderFlags binderFlags, string name, IType[]? typeArguments, IType? context, IType? staticTargetType, CSharpArgumentInfo[] argumentInfo, ILInstruction[] arguments) : base(OpCode.DynamicInvokeMemberInstruction, binderFlags, context) { Name = name; TypeArguments = typeArguments ?? Empty.Array; + StaticTargetType = staticTargetType; ArgumentInfo = argumentInfo; Arguments = new InstructionCollection(this, 0); Arguments.AddRange(arguments); @@ -188,6 +199,11 @@ namespace ICSharpCode.Decompiler.IL output.Write(OpCode); WriteBinderFlags(output, options); output.Write(' '); + if (StaticTargetType != null) + { + StaticTargetType.WriteTo(output); + output.Write('.'); + } output.Write(Name); if (TypeArguments.Count > 0) { @@ -202,13 +218,14 @@ namespace ICSharpCode.Decompiler.IL } output.Write('>'); } - WriteArgumentList(output, options, Arguments.Zip(ArgumentInfo)); + WriteArgumentList(output, options, Arguments.Zip(ArgumentInfo.Skip(ArgumentInfoOffset))); } public override StackType ResultType => StackType.O; public override CSharpArgumentInfo GetArgumentInfoOfChild(int index) { + index += ArgumentInfoOffset; if (index < 0 || index >= ArgumentInfo.Count) throw new ArgumentOutOfRangeException(nameof(index)); return ArgumentInfo[index]; @@ -356,17 +373,21 @@ namespace ICSharpCode.Decompiler.IL partial class DynamicInvokeConstructorInstruction { - readonly IType? resultType; - public IReadOnlyList ArgumentInfo { get; } - public DynamicInvokeConstructorInstruction(CSharpBinderFlags binderFlags, IType? type, IType? context, CSharpArgumentInfo[] argumentInfo, ILInstruction[] arguments) + /// + /// The type being constructed. The target is never in , but ArgumentInfo[0] still + /// describes it. + /// + public readonly IType Type; + + public DynamicInvokeConstructorInstruction(CSharpBinderFlags binderFlags, IType type, IType? context, CSharpArgumentInfo[] argumentInfo, ILInstruction[] arguments) : base(OpCode.DynamicInvokeConstructorInstruction, binderFlags, context) { + Type = type; ArgumentInfo = argumentInfo; Arguments = new InstructionCollection(this, 0); Arguments.AddRange(arguments); - this.resultType = type; } protected override void WriteToCore(ITextOutput output, ILAstWritingOptions options) @@ -375,15 +396,17 @@ namespace ICSharpCode.Decompiler.IL output.Write(OpCode); WriteBinderFlags(output, options); output.Write(' '); - resultType?.WriteTo(output); + Type.WriteTo(output); output.Write(".ctor"); - WriteArgumentList(output, options, Arguments.Zip(ArgumentInfo)); + WriteArgumentList(output, options, Arguments.Zip(ArgumentInfo.Skip(1))); } - public override StackType ResultType => resultType?.GetStackType() ?? StackType.Unknown; + public override StackType ResultType => Type.GetStackType(); public override CSharpArgumentInfo GetArgumentInfoOfChild(int index) { + // ArgumentInfo[0] describes the constructed type, which is not an argument. + index += 1; if (index < 0 || index >= ArgumentInfo.Count) throw new ArgumentOutOfRangeException(nameof(index)); return ArgumentInfo[index]; diff --git a/ICSharpCode.Decompiler/IL/Transforms/DynamicCallSiteTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/DynamicCallSiteTransform.cs index 69f858bd6..450186aaa 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/DynamicCallSiteTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/DynamicCallSiteTransform.cs @@ -120,6 +120,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms storesToRemove.Add(stLoc); if (value.MatchLdFld(out cacheFieldLoad, out var targetFieldCopy) && cacheFieldLoad.MatchLdsFld(out cacheFieldCopy) && cacheField.Equals(cacheFieldCopy) && targetField.Equals(targetFieldCopy)) storesToRemove.Add(stLoc); + if (TransformExpressionTrees.MatchGetTypeFromHandle(value, out _)) + storesToRemove.Add(stLoc); } } } @@ -149,6 +151,21 @@ namespace ICSharpCode.Decompiler.IL.Transforms || (variable.Kind == VariableKind.Local && variable.StateMachineField != null); } + /// + /// Matches a GetTypeFromHandle call, or a load of a variable that is assigned one exactly once. The + /// latter form occurs when control flow in a later argument forces the value off the evaluation stack. + /// Any variable kind qualifies: only the type is read here. + /// + static bool MatchTypeOf(ILInstruction inst, out IType type) + { + if (TransformExpressionTrees.MatchGetTypeFromHandle(inst, out type)) + return true; + return inst.MatchLdLoc(out var variable) + && variable.IsSingleDefinition + && variable.StoreInstructions.FirstOrDefault() is StLoc initStore + && TransformExpressionTrees.MatchGetTypeFromHandle(initStore.Value, out type); + } + static void FindDynamicCallSitesInBlock(Block block, Dictionary callsites, ILTransformContext context) { if (block.Instructions.Count < 2) @@ -243,33 +260,35 @@ namespace ICSharpCode.Decompiler.IL.Transforms ); case BinderMethodKind.InvokeConstructor: var arguments = targetInvokeCall.Arguments.Skip(2).ToArray(); - // Extract type information from targetInvokeCall: - // Must either be an inlined type or - // a reference to a variable that is initialized with a type. - if (!TransformExpressionTrees.MatchGetTypeFromHandle(arguments[0], out var type)) - { - if (!(arguments[0].MatchLdLoc(out var temp) && temp.IsSingleDefinition && temp.StoreInstructions.FirstOrDefault() is StLoc initStore)) - return null; - if (!TransformExpressionTrees.MatchGetTypeFromHandle(initStore.Value, out type)) - return null; - } - deadArguments.AddRange(targetInvokeCall.Arguments.Take(2)); + // ArgumentInfos[0] is always IsStaticType here: an object creation names the type it constructs. + if (!MatchTypeOf(arguments[0], out var type)) + return null; + deadArguments.AddRange(targetInvokeCall.Arguments.Take(3)); return new DynamicInvokeConstructorInstruction( binderFlags: callsite.Flags, type: type ?? SpecialType.UnknownType, context: callsite.Context, argumentInfo: callsite.ArgumentInfos, - arguments: arguments + arguments: arguments.Skip(1).ToArray() ); case BinderMethodKind.InvokeMember: + var memberArguments = targetInvokeCall.Arguments.Skip(2).ToArray(); + IType staticTargetType = null; + if (callsite.ArgumentInfos[0].HasFlag(CSharpArgumentInfoFlags.IsStaticType) + && MatchTypeOf(memberArguments[0], out staticTargetType)) + { + deadArguments.Add(memberArguments[0]); + memberArguments = memberArguments.Skip(1).ToArray(); + } deadArguments.AddRange(targetInvokeCall.Arguments.Take(2)); return new DynamicInvokeMemberInstruction( binderFlags: callsite.Flags, name: callsite.MemberName, typeArguments: callsite.TypeArguments, context: callsite.Context, + staticTargetType: staticTargetType, argumentInfo: callsite.ArgumentInfos, - arguments: targetInvokeCall.Arguments.Skip(2).ToArray() + arguments: memberArguments ); case BinderMethodKind.IsEvent: deadArguments.AddRange(targetInvokeCall.Arguments.Take(2));