From 2c0554f12914c35d9c0667d4e241ad9765247665 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Wed, 24 Jun 2020 02:28:28 +0200 Subject: [PATCH] Fix #1963: Support decompiling `calli` instructions into C# 9 function pointer syntax. --- .../ICSharpCode.Decompiler.Tests.csproj | 2 + .../ILPrettyTestRunner.cs | 6 ++ .../TestCases/ILPretty/CallIndirect.cs | 8 ++ .../TestCases/ILPretty/CallIndirect.il | 39 +++++++++ .../CSharp/ExpressionBuilder.cs | 21 +++++ .../OutputVisitor/CSharpOutputVisitor.cs | 13 +++ .../CSharp/Syntax/DepthFirstAstVisitor.cs | 15 ++++ .../CSharp/Syntax/FunctionPointerType.cs | 83 +++++++++++++++++++ .../CSharp/Syntax/IAstVisitor.cs | 3 + .../Transforms/IntroduceUnsafeModifier.cs | 7 +- .../ICSharpCode.Decompiler.csproj | 1 + ICSharpCode.Decompiler/IL/ILReader.cs | 4 +- .../IL/Instructions/CallIndirect.cs | 48 +++++------ 13 files changed, 218 insertions(+), 32 deletions(-) create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/ILPretty/CallIndirect.cs create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/ILPretty/CallIndirect.il create mode 100644 ICSharpCode.Decompiler/CSharp/Syntax/FunctionPointerType.cs diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index 546cd2ea7..d37764e45 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -61,6 +61,7 @@ + @@ -91,6 +92,7 @@ + diff --git a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs index 49c3bca2c..d9a9e2ede 100644 --- a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs @@ -196,6 +196,12 @@ namespace ICSharpCode.Decompiler.Tests Run(); } + [Test] + public void CallIndirect() + { + Run(); + } + [Test] public void FSharpLoops_Debug() { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/CallIndirect.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/CallIndirect.cs new file mode 100644 index 000000000..a87ff898e --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/CallIndirect.cs @@ -0,0 +1,8 @@ +using System; +internal class CallIndirect +{ + private unsafe void Test(IntPtr f) + { + ((delegate* stdcall)f)(42); + } +} \ No newline at end of file diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/CallIndirect.il b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/CallIndirect.il new file mode 100644 index 000000000..8ba3b409a --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/CallIndirect.il @@ -0,0 +1,39 @@ +#define CORE_ASSEMBLY "System.Runtime" + +.assembly extern CORE_ASSEMBLY +{ + .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....: + .ver 4:0:0:0 +} + +.class private auto ansi beforefieldinit CallIndirect + extends [System.Private.CoreLib]System.Object +{ + // Methods + .method private hidebysig + instance void Test ( + native int f + ) cil managed + { + .maxstack 8 + + ldc.i4.s 42 + ldarg.1 + calli unmanaged stdcall void(int32) + ret + } // end of method Example::Test + + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x206e + // Code size 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [System.Private.CoreLib]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method Example::.ctor + +} // end of class Example diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 9ae593ab0..a1ac312b9 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -3244,6 +3244,27 @@ namespace ICSharpCode.Decompiler.CSharp .WithILInstruction(inst); } + protected internal override TranslatedExpression VisitCallIndirect(CallIndirect inst, TranslationContext context) + { + if (inst.IsInstance) { + return ErrorExpression("calli with instance method signature not supportd"); + } + var ty = new FunctionPointerType(); + if (inst.CallingConvention != System.Reflection.Metadata.SignatureCallingConvention.Default) { + ty.CallingConvention = inst.CallingConvention.ToString().ToLowerInvariant(); + } + foreach (var parameterType in inst.ParameterTypes) { + ty.TypeArguments.Add(astBuilder.ConvertType(parameterType)); + } + ty.TypeArguments.Add(astBuilder.ConvertType(inst.ReturnType)); + var functionPointer = Translate(inst.FunctionPointer); + var invocation = new InvocationExpression(new CastExpression(ty, functionPointer)); + foreach (var (arg, paramType) in inst.Arguments.Zip(inst.ParameterTypes)) { + invocation.Arguments.Add(Translate(arg, typeHint: paramType).ConvertTo(paramType, this, allowImplicitConversion: true)); + } + return invocation.WithRR(new ResolveResult(inst.ReturnType)).WithILInstruction(inst); + } + protected internal override TranslatedExpression VisitInvalidBranch(InvalidBranch inst, TranslationContext context) { string message = "Error"; diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs index 1d3f99617..01c7a3d69 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -2335,6 +2335,19 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor EndNode(tupleTypeElement); } + public virtual void VisitFunctionPointerType(FunctionPointerType functionPointerType) + { + StartNode(functionPointerType); + WriteKeyword(Roles.DelegateKeyword); + WriteToken(FunctionPointerType.PointerRole); + if (!functionPointerType.CallingConventionIdentifier.IsNull) { + Space(); + WriteIdentifier(functionPointerType.CallingConventionIdentifier); + } + WriteTypeArguments(functionPointerType.TypeArguments); + EndNode(functionPointerType); + } + public virtual void VisitComposedType(ComposedType composedType) { StartNode(composedType); diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs b/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs index 3b3a0b216..73a4862db 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs @@ -126,6 +126,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax VisitChildren (tupleTypeElement); } + public virtual void VisitFunctionPointerType(FunctionPointerType functionPointerType) + { + VisitChildren(functionPointerType); + } + public virtual void VisitAttribute (Attribute attribute) { VisitChildren (attribute); @@ -778,6 +783,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax return VisitChildren (tupleTypeElement); } + public virtual T VisitFunctionPointerType(FunctionPointerType functionPointerType) + { + return VisitChildren (functionPointerType); + } + public virtual T VisitAttribute (Attribute attribute) { return VisitChildren (attribute); @@ -1430,6 +1440,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax return VisitChildren (tupleTypeElement, data); } + public virtual S VisitFunctionPointerType(FunctionPointerType functionPointerType, T data) + { + return VisitChildren (functionPointerType, data); + } + public virtual S VisitAttribute (Attribute attribute, T data) { return VisitChildren (attribute, data); diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/FunctionPointerType.cs b/ICSharpCode.Decompiler/CSharp/Syntax/FunctionPointerType.cs new file mode 100644 index 000000000..4352f7ffa --- /dev/null +++ b/ICSharpCode.Decompiler/CSharp/Syntax/FunctionPointerType.cs @@ -0,0 +1,83 @@ +// +// FullTypeName.cs +// +// Author: +// Mike Krüger +// +// Copyright (c) 2010 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Collections.Generic; +using ICSharpCode.Decompiler.CSharp.Resolver; +using ICSharpCode.Decompiler.CSharp.TypeSystem; +using ICSharpCode.Decompiler.IL; +using ICSharpCode.Decompiler.TypeSystem; + +namespace ICSharpCode.Decompiler.CSharp.Syntax +{ + public class FunctionPointerType : AstType + { + public static readonly TokenRole PointerRole = new TokenRole("*"); + public static readonly Role CallingConventionRole = new Role("Target", Identifier.Null); + + public string CallingConvention { + get { + return GetChildByRole (CallingConventionRole).Name; + } + set { + SetChildByRole (CallingConventionRole, Identifier.Create (value)); + } + } + + public Identifier CallingConventionIdentifier => GetChildByRole(CallingConventionRole); + + public AstNodeCollection TypeArguments { + get { return GetChildrenByRole (Roles.TypeArgument); } + } + + public override void AcceptVisitor (IAstVisitor visitor) + { + visitor.VisitFunctionPointerType(this); + } + + public override T AcceptVisitor (IAstVisitor visitor) + { + return visitor.VisitFunctionPointerType(this); + } + + public override S AcceptVisitor (IAstVisitor visitor, T data) + { + return visitor.VisitFunctionPointerType(this, data); + } + + protected internal override bool DoMatch(AstNode other, PatternMatching.Match match) + { + return other is FunctionPointerType o && MatchString(this.CallingConvention, o.CallingConvention) + && this.TypeArguments.DoMatch(o.TypeArguments, match); + } + + public override ITypeReference ToTypeReference(NameLookupMode lookupMode, InterningProvider interningProvider = null) + { + throw new NotImplementedException(); + } + } +} + diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs b/ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs index 9ed151865..f9c2976ad 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs @@ -137,6 +137,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax void VisitMemberType(MemberType memberType); void VisitTupleType(TupleAstType tupleType); void VisitTupleTypeElement(TupleTypeElement tupleTypeElement); + void VisitFunctionPointerType(FunctionPointerType functionPointerType); void VisitComposedType(ComposedType composedType); void VisitArraySpecifier(ArraySpecifier arraySpecifier); void VisitPrimitiveType(PrimitiveType primitiveType); @@ -279,6 +280,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax S VisitMemberType(MemberType memberType); S VisitTupleType(TupleAstType tupleType); S VisitTupleTypeElement(TupleTypeElement tupleTypeElement); + S VisitFunctionPointerType(FunctionPointerType functionPointerType); S VisitComposedType(ComposedType composedType); S VisitArraySpecifier(ArraySpecifier arraySpecifier); S VisitPrimitiveType(PrimitiveType primitiveType); @@ -421,6 +423,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax S VisitMemberType(MemberType memberType, T data); S VisitTupleType(TupleAstType tupleType, T data); S VisitTupleTypeElement(TupleTypeElement tupleTypeElement, T data); + S VisitFunctionPointerType(FunctionPointerType functionPointerType, T data); S VisitComposedType(ComposedType composedType, T data); S VisitArraySpecifier(ArraySpecifier arraySpecifier, T data); S VisitPrimitiveType(PrimitiveType primitiveType, T data); diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUnsafeModifier.cs b/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUnsafeModifier.cs index 03e08bba6..cd97b257b 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUnsafeModifier.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUnsafeModifier.cs @@ -73,7 +73,12 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms else return base.VisitComposedType(composedType); } - + + public override bool VisitFunctionPointerType(FunctionPointerType functionPointerType) + { + return true; + } + public override bool VisitUnaryOperatorExpression(UnaryOperatorExpression unaryOperatorExpression) { bool result = base.VisitUnaryOperatorExpression(unaryOperatorExpression); diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index b207c5f6d..e3af17f9b 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -70,6 +70,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/ILReader.cs b/ICSharpCode.Decompiler/IL/ILReader.cs index c5638eeb2..e86597bd9 100644 --- a/ICSharpCode.Decompiler/IL/ILReader.cs +++ b/ICSharpCode.Decompiler/IL/ILReader.cs @@ -1456,8 +1456,8 @@ namespace ICSharpCode.Decompiler.IL signature.Header.CallingConvention, signature.ReturnType, signature.ParameterTypes, - arguments, - functionPointer + functionPointer, + arguments ); if (call.ResultType != StackType.Void) return Push(call); diff --git a/ICSharpCode.Decompiler/IL/Instructions/CallIndirect.cs b/ICSharpCode.Decompiler/IL/Instructions/CallIndirect.cs index 20ac323a7..fb83552da 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/CallIndirect.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/CallIndirect.cs @@ -27,11 +27,13 @@ namespace ICSharpCode.Decompiler.IL { partial class CallIndirect { - public static readonly SlotInfo ArgumentSlot = new SlotInfo("Argument", canInlineInto: true, isCollection: true); + // Note: while in IL the arguments come first and the function pointer last; + // in the ILAst we're handling it as in C#: the function pointer is evaluated first, the arguments later. public static readonly SlotInfo FunctionPointerSlot = new SlotInfo("FunctionPointer", canInlineInto: true); + public static readonly SlotInfo ArgumentSlot = new SlotInfo("Argument", canInlineInto: true, isCollection: true); - public readonly InstructionCollection Arguments; ILInstruction functionPointer; + public readonly InstructionCollection Arguments; public bool IsInstance { get; } public bool HasExplicitThis { get; } public System.Reflection.Metadata.SignatureCallingConvention CallingConvention { get; } @@ -51,34 +53,27 @@ namespace ICSharpCode.Decompiler.IL } set { ValidateChild(value); - SetChildInstruction(ref functionPointer, value, Arguments.Count); + SetChildInstruction(ref functionPointer, value, 0); } } - protected internal override void InstructionCollectionUpdateComplete() - { - base.InstructionCollectionUpdateComplete(); - if (functionPointer?.Parent == this) - functionPointer.ChildIndex = Arguments.Count; - } - public CallIndirect(bool isInstance, bool hasExplicitThis, System.Reflection.Metadata.SignatureCallingConvention callingConvention, IType returnType, ImmutableArray parameterTypes, - IEnumerable arguments, ILInstruction functionPointer) : base(OpCode.CallIndirect) + ILInstruction functionPointer, IEnumerable arguments) : base(OpCode.CallIndirect) { this.IsInstance = isInstance; this.HasExplicitThis = hasExplicitThis; this.CallingConvention = callingConvention; this.ReturnType = returnType ?? throw new ArgumentNullException(nameof(returnType)); this.ParameterTypes = parameterTypes.ToImmutableArray(); - this.Arguments = new InstructionCollection(this, 0); - this.Arguments.AddRange(arguments); this.FunctionPointer = functionPointer; + this.Arguments = new InstructionCollection(this, 1); + this.Arguments.AddRange(arguments); } public override ILInstruction Clone() { return new CallIndirect(IsInstance, HasExplicitThis, CallingConvention, ReturnType, ParameterTypes, - this.Arguments.Select(inst => inst.Clone()), functionPointer.Clone() + functionPointer.Clone(), this.Arguments.Select(inst => inst.Clone()) ).WithILRange(this); } @@ -96,24 +91,19 @@ namespace ICSharpCode.Decompiler.IL output.Write("call.indirect "); ReturnType.WriteTo(output); output.Write('('); - bool first = true; + functionPointer.WriteTo(output, options); int firstArgument = IsInstance ? 1 : 0; if (firstArgument == 1) { + output.Write(", "); Arguments[0].WriteTo(output, options); - first = false; } - foreach (var (inst, type) in Arguments.Skip(firstArgument).Zip(ParameterTypes, (a,b) => (a,b))) { - if (first) - first = false; - else - output.Write(", "); + foreach (var (inst, type) in Arguments.Zip(ParameterTypes, (a,b) => (a,b))) { + output.Write(", "); inst.WriteTo(output, options); output.Write(" : "); type.WriteTo(output); } if (Arguments.Count > 0) - output.Write(", "); - functionPointer.WriteTo(output, options); output.Write(')'); } @@ -124,22 +114,22 @@ namespace ICSharpCode.Decompiler.IL protected override ILInstruction GetChild(int index) { - if (index == Arguments.Count) + if (index == 0) return functionPointer; - return Arguments[index]; + return Arguments[index - 1]; } protected override void SetChild(int index, ILInstruction value) { - if (index == Arguments.Count) + if (index == 0) FunctionPointer = value; else - Arguments[index] = value; + Arguments[index - 1] = value; } protected override SlotInfo GetChildSlot(int index) { - if (index == Arguments.Count) + if (index == 0) return FunctionPointerSlot; else return ArgumentSlot; @@ -148,10 +138,10 @@ namespace ICSharpCode.Decompiler.IL protected override InstructionFlags ComputeFlags() { var flags = this.DirectFlags; + flags |= functionPointer.Flags; foreach (var inst in Arguments) { flags |= inst.Flags; } - flags |= functionPointer.Flags; return flags; }