mirror of
https://github.com/icsharpcode/ILSpy.git
synced 2026-10-12 02:15:58 +00:00
Merge pull request #4072 from icsharpcode/fix/3704-static-type-target
Fix #3704: keep the static type of a dynamic call target
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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<T>(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<int>(d, (data != null) ? data.Length : 0);
|
||||
#else
|
||||
DynamicTests.M6<int>(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);
|
||||
|
||||
@@ -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<ILInstruction> 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)));
|
||||
|
||||
@@ -172,11 +172,22 @@ namespace ICSharpCode.Decompiler.IL
|
||||
public IReadOnlyList<IType> TypeArguments { get; }
|
||||
public IReadOnlyList<CSharpArgumentInfo> ArgumentInfo { get; }
|
||||
|
||||
public DynamicInvokeMemberInstruction(CSharpBinderFlags binderFlags, string name, IType[]? typeArguments, IType? context, CSharpArgumentInfo[] argumentInfo, ILInstruction[] arguments)
|
||||
/// <summary>
|
||||
/// 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 <see cref="Arguments"/>, but ArgumentInfo[0]
|
||||
/// still describes it.
|
||||
/// </summary>
|
||||
public readonly IType? StaticTargetType;
|
||||
|
||||
/// <summary>Offset from an index into <see cref="Arguments"/> to the matching ArgumentInfo entry.</summary>
|
||||
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<IType>.Array;
|
||||
StaticTargetType = staticTargetType;
|
||||
ArgumentInfo = argumentInfo;
|
||||
Arguments = new InstructionCollection<ILInstruction>(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<CSharpArgumentInfo> ArgumentInfo { get; }
|
||||
|
||||
public DynamicInvokeConstructorInstruction(CSharpBinderFlags binderFlags, IType? type, IType? context, CSharpArgumentInfo[] argumentInfo, ILInstruction[] arguments)
|
||||
/// <summary>
|
||||
/// The type being constructed. The target is never in <see cref="Arguments"/>, but ArgumentInfo[0] still
|
||||
/// describes it.
|
||||
/// </summary>
|
||||
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<ILInstruction>(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];
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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<IField, CallSiteInfo> 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));
|
||||
|
||||
Reference in New Issue
Block a user