From cabb02b5fd649a918992685ac38a53c354ca2b77 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 5 Sep 2020 11:30:51 +0200 Subject: [PATCH 1/5] Add support for decoding function pointer types in signatures. --- .../ICSharpCode.Decompiler.Tests.csproj | 1 + .../PrettyTestRunner.cs | 6 + .../TestCases/Pretty/DynamicTests.cs | 20 ++++ .../TestCases/Pretty/FunctionPointers.cs | 59 +++++++++ .../CSharp/ExpressionBuilder.cs | 10 +- .../OutputVisitor/CSharpOutputVisitor.cs | 11 +- .../CSharp/Syntax/DepthFirstAstVisitor.cs | 6 +- ...interType.cs => FunctionPointerAstType.cs} | 20 ++-- .../CSharp/Syntax/IAstVisitor.cs | 6 +- .../CSharp/Syntax/TypeSystemAstBuilder.cs | 23 ++++ .../Transforms/IntroduceUnsafeModifier.cs | 2 +- ICSharpCode.Decompiler/DecompilerSettings.cs | 22 +++- .../ICSharpCode.Decompiler.csproj | 3 +- .../TypeSystem/ApplyAttributeTypeVisitor.cs | 41 ++++++- .../TypeSystem/DecompilerTypeSystem.cs | 11 +- .../TypeSystem/FunctionPointerType.cs | 113 ++++++++++++++++++ .../Implementation/MetadataMethod.cs | 6 +- ICSharpCode.Decompiler/TypeSystem/TypeKind.cs | 5 + .../TypeSystem/TypeProvider.cs | 50 +++++++- .../TypeSystem/TypeVisitor.cs | 7 +- ILSpy/Languages/Language.cs | 19 +++ ILSpy/Properties/Resources.Designer.cs | 9 ++ ILSpy/Properties/Resources.resx | 3 + 23 files changed, 418 insertions(+), 35 deletions(-) create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs rename ICSharpCode.Decompiler/CSharp/Syntax/{FunctionPointerType.cs => FunctionPointerAstType.cs} (83%) create mode 100644 ICSharpCode.Decompiler/TypeSystem/FunctionPointerType.cs diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index 2a03078ca..379fa9104 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -95,6 +95,7 @@ + diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index 03897a4c1..617a7f2a0 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -372,6 +372,12 @@ namespace ICSharpCode.Decompiler.Tests RunForLibrary(cscOptions: cscOptions | CompilerOptions.Preview); } + [Test] + public void FunctionPointers([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions) + { + RunForLibrary(cscOptions: cscOptions | CompilerOptions.Preview); + } + [Test] public void NullPropagation([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions) { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicTests.cs index 586adacc7..748dde07d 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicTests.cs @@ -487,6 +487,26 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { return (int)(dynamic)o; } + +#if CS72 + public void RefParams(ref object a, ref dynamic b, ref dynamic c) + { + } + public void RefParams2(in object a, ref dynamic b, out dynamic c) + { + c = null; + } + + public ref dynamic RefReturn(ref object o) + { + return ref o; + } + + public ref readonly dynamic RefReadonlyReturn(in object o) + { + return ref o; + } +#endif } internal static class Extension diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs new file mode 100644 index 000000000..a84c50cc7 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs @@ -0,0 +1,59 @@ +using System; + +namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty +{ + internal class FunctionPointersWithDynamicTypes + { + public class D + { + } + public class A + { + public class B + { + } + } + + public unsafe delegate* F1; + public unsafe delegate* F2; + public unsafe delegate* F3; + public unsafe delegate* F4; + public unsafe delegate* F5; + public unsafe delegate* F6; + public unsafe delegate* F7; + public unsafe delegate* F8; + public unsafe delegate* F9; + public unsafe delegate* F10; + public unsafe delegate* F11; + public unsafe delegate* F12; + public unsafe delegate* F13; + public unsafe delegate* F14; + public unsafe D[], dynamic> F15; + public unsafe delegate*.B> F16; + } + + internal class FunctionPointersWithNativeIntegerTypes + { + public unsafe delegate* F1; + public unsafe delegate* F2; + public unsafe delegate* F3; + public unsafe delegate* F4; + public unsafe delegate*, nint> F5; + public unsafe delegate*> F6; + public unsafe delegate*, IntPtr> F7; + public unsafe delegate*> F8; + } + + internal class FunctionPointersWithRefParams + { + public unsafe delegate* F1; + } + + // TODO: the new calling convention syntax isn't yet available in the released Roslyn version + //internal unsafe class FunctionPointersWithCallingConvention + //{ + // public delegate* managed; + // public delegate* unmanaged unmanaged; + // public delegate* unmanaged[Cdecl] cdecl; + //} +} diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 7d2ea9324..c227aa2c2 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2014 Daniel Grunwald +// Copyright (c) 2014-2020 Daniel Grunwald // // 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 @@ -3888,16 +3888,18 @@ namespace ICSharpCode.Decompiler.CSharp { return ErrorExpression("calli with instance method signature not supportd"); } - var ty = new FunctionPointerType(); + var ty = new FunctionPointerAstType(); 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.Parameters.Add(new ParameterDeclaration { + Type = astBuilder.ConvertType(parameterType) + }); } - ty.TypeArguments.Add(astBuilder.ConvertType(inst.ReturnType)); + ty.ReturnType = 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)) diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs index 79be4dedb..b6cbd2e32 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team +// Copyright (c) 2010-2020 AlphaSierraPapa for the SharpDevelop Team // // 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 @@ -2600,17 +2600,20 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor EndNode(tupleTypeElement); } - public virtual void VisitFunctionPointerType(FunctionPointerType functionPointerType) + public virtual void VisitFunctionPointerType(FunctionPointerAstType functionPointerType) { StartNode(functionPointerType); WriteKeyword(Roles.DelegateKeyword); - WriteToken(FunctionPointerType.PointerRole); + WriteToken(FunctionPointerAstType.PointerRole); if (!functionPointerType.CallingConventionIdentifier.IsNull) { Space(); WriteIdentifier(functionPointerType.CallingConventionIdentifier); } - WriteTypeArguments(functionPointerType.TypeArguments); + WriteToken(Roles.LChevron); + WriteCommaSeparatedList( + functionPointerType.Parameters.Concat(new[] { functionPointerType.ReturnType })); + WriteToken(Roles.RChevron); EndNode(functionPointerType); } diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs b/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs index 75909133d..8133f6bed 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs @@ -112,7 +112,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax VisitChildren(tupleTypeElement); } - public virtual void VisitFunctionPointerType(FunctionPointerType functionPointerType) + public virtual void VisitFunctionPointerType(FunctionPointerAstType functionPointerType) { VisitChildren(functionPointerType); } @@ -780,7 +780,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax return VisitChildren(tupleTypeElement); } - public virtual T VisitFunctionPointerType(FunctionPointerType functionPointerType) + public virtual T VisitFunctionPointerType(FunctionPointerAstType functionPointerType) { return VisitChildren(functionPointerType); } @@ -1448,7 +1448,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax return VisitChildren(tupleTypeElement, data); } - public virtual S VisitFunctionPointerType(FunctionPointerType functionPointerType, T data) + public virtual S VisitFunctionPointerType(FunctionPointerAstType functionPointerType, T data) { return VisitChildren(functionPointerType, data); } diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/FunctionPointerType.cs b/ICSharpCode.Decompiler/CSharp/Syntax/FunctionPointerAstType.cs similarity index 83% rename from ICSharpCode.Decompiler/CSharp/Syntax/FunctionPointerType.cs rename to ICSharpCode.Decompiler/CSharp/Syntax/FunctionPointerAstType.cs index 4551dbdee..d5f243aaa 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/FunctionPointerType.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/FunctionPointerAstType.cs @@ -25,16 +25,13 @@ // 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 class FunctionPointerAstType : AstType { public static readonly TokenRole PointerRole = new TokenRole("*"); public static readonly Role CallingConventionRole = new Role("Target", Identifier.Null); @@ -50,8 +47,13 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax public Identifier CallingConventionIdentifier => GetChildByRole(CallingConventionRole); - public AstNodeCollection TypeArguments { - get { return GetChildrenByRole(Roles.TypeArgument); } + public AstNodeCollection Parameters { + get { return GetChildrenByRole(Roles.Parameter); } + } + + public AstType ReturnType { + get { return GetChildByRole(Roles.Type); } + set { SetChildByRole(Roles.Type, value); } } public override void AcceptVisitor(IAstVisitor visitor) @@ -71,8 +73,9 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax 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); + return other is FunctionPointerAstType o && MatchString(this.CallingConvention, o.CallingConvention) + && this.Parameters.DoMatch(o.Parameters, match) + && this.ReturnType.DoMatch(o.ReturnType, match); } public override ITypeReference ToTypeReference(NameLookupMode lookupMode, InterningProvider interningProvider = null) @@ -81,4 +84,3 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax } } } - diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs b/ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs index 1e87d6755..3d3846a49 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs @@ -140,7 +140,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax void VisitMemberType(MemberType memberType); void VisitTupleType(TupleAstType tupleType); void VisitTupleTypeElement(TupleTypeElement tupleTypeElement); - void VisitFunctionPointerType(FunctionPointerType functionPointerType); + void VisitFunctionPointerType(FunctionPointerAstType functionPointerType); void VisitComposedType(ComposedType composedType); void VisitArraySpecifier(ArraySpecifier arraySpecifier); void VisitPrimitiveType(PrimitiveType primitiveType); @@ -286,7 +286,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax S VisitMemberType(MemberType memberType); S VisitTupleType(TupleAstType tupleType); S VisitTupleTypeElement(TupleTypeElement tupleTypeElement); - S VisitFunctionPointerType(FunctionPointerType functionPointerType); + S VisitFunctionPointerType(FunctionPointerAstType functionPointerType); S VisitComposedType(ComposedType composedType); S VisitArraySpecifier(ArraySpecifier arraySpecifier); S VisitPrimitiveType(PrimitiveType primitiveType); @@ -432,7 +432,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 VisitFunctionPointerType(FunctionPointerAstType 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/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs index 09af3f02c..52ee0cf71 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs @@ -306,6 +306,29 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax } return astType; } + else if (type is FunctionPointerType fpt) + { + var astType = new FunctionPointerAstType(); + for (int i = 0; i < fpt.ParameterTypes.Length; i++) + { + astType.Parameters.Add(new ParameterDeclaration { + ParameterModifier = fpt.ParameterReferenceKinds[i] switch + { + ReferenceKind.In => ParameterModifier.In, + ReferenceKind.Ref => ParameterModifier.Ref, + ReferenceKind.Out => ParameterModifier.Out, + _ => ParameterModifier.None, + }, + Type = ConvertType(fpt.ParameterTypes[i]) + }); + } + astType.ReturnType = ConvertType(fpt.ReturnType); + if (fpt.ReturnIsRefReadOnly && astType.ReturnType is ComposedType ct && ct.HasRefSpecifier) + { + ct.HasReadOnlySpecifier = true; + } + return astType; + } else { AstType astType; diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUnsafeModifier.cs b/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUnsafeModifier.cs index bd5d69e7c..a94eee76e 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUnsafeModifier.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUnsafeModifier.cs @@ -77,7 +77,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms return base.VisitComposedType(composedType); } - public override bool VisitFunctionPointerType(FunctionPointerType functionPointerType) + public override bool VisitFunctionPointerType(FunctionPointerAstType functionPointerType) { return true; } diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index f969dd7b1..b36e4196e 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -133,12 +133,13 @@ namespace ICSharpCode.Decompiler { nativeIntegers = false; initAccessors = false; + functionPointers = false; } } public CSharp.LanguageVersion GetMinimumRequiredVersion() { - if (nativeIntegers || initAccessors) + if (nativeIntegers || initAccessors || functionPointers) return CSharp.LanguageVersion.Preview; if (nullableReferenceTypes || readOnlyMethods || asyncEnumerator || asyncUsingAndForEachStatement || staticLocalFunctions || ranges || switchExpressions) @@ -204,6 +205,25 @@ namespace ICSharpCode.Decompiler } } + bool functionPointers = true; + + /// + /// Use C# 9 delegate* unmanaged types. + /// If this option is disabled, function pointers will instead be decompiled with type `IntPtr`. + /// + [Category("C# 9.0 (experimental)")] + [Description("DecompilerSettings.FunctionPointers")] + public bool FunctionPointers { + get { return functionPointers; } + set { + if (functionPointers != value) + { + functionPointers = value; + OnPropertyChanged(); + } + } + } + bool switchExpressions = true; /// diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index d4628b0eb..d64c6b693 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -72,7 +72,7 @@ - + @@ -250,6 +250,7 @@ + diff --git a/ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs b/ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs index f058d103e..cbf8771cf 100644 --- a/ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs +++ b/ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs @@ -40,7 +40,8 @@ namespace ICSharpCode.Decompiler.TypeSystem SRM.MetadataReader metadata, TypeSystemOptions options, Nullability nullableContext, - bool typeChildrenOnly = false) + bool typeChildrenOnly = false, + bool isSignatureReturnType = false) { bool hasDynamicAttribute = false; bool[] dynamicAttributeData = null; @@ -133,6 +134,14 @@ namespace ICSharpCode.Decompiler.TypeSystem options, tupleElementNames, nullability, nullableAttributeData ); + if (isSignatureReturnType && hasDynamicAttribute + && inputType.SkipModifiers().Kind == TypeKind.ByReference + && attributes.Value.HasKnownAttribute(metadata, KnownAttribute.IsReadOnly)) + { + // crazy special case: `ref readonly` return takes one dynamic index more than + // a non-readonly `ref` return. + visitor.dynamicTypeIndex++; + } if (typeChildrenOnly) { return inputType.VisitChildren(visitor); @@ -309,6 +318,36 @@ namespace ICSharpCode.Decompiler.TypeSystem return new ParameterizedType(genericType, arguments); } + public override IType VisitFunctionPointerType(FunctionPointerType type) + { + dynamicTypeIndex++; + if (type.ReturnIsRefReadOnly) + { + dynamicTypeIndex++; + } + var returnType = type.ReturnType.AcceptVisitor(this); + bool changed = type.ReturnType != returnType; + var parameters = new IType[type.ParameterTypes.Length]; + for (int i = 0; i < parameters.Length; i++) + { + dynamicTypeIndex += type.ParameterReferenceKinds[i] switch + { + ReferenceKind.None => 1, + ReferenceKind.Ref => 2, + ReferenceKind.Out => 3, + ReferenceKind.In => 3, + _ => throw new NotSupportedException() + }; + parameters[i] = type.ParameterTypes[i].AcceptVisitor(this); + changed = changed || parameters[i] != type.ParameterTypes[i]; + } + if (!changed) + return type; + return new FunctionPointerType(type.CallingConvention, + returnType, type.ReturnIsRefReadOnly, + parameters.ToImmutableArray(), type.ParameterReferenceKinds); + } + public override IType VisitTypeDefinition(ITypeDefinition type) { IType newType = type; diff --git a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs index 58915a02e..c36b530cc 100644 --- a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs +++ b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs @@ -18,8 +18,6 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; using System.Linq; using ICSharpCode.Decompiler.Metadata; @@ -116,11 +114,16 @@ namespace ICSharpCode.Decompiler.TypeSystem /// NativeIntegers = 0x1000, /// + /// Allow function pointer types. If this option is not enabled, function pointers are + /// replaced with the 'IntPtr' type. + /// + FunctionPointers = 0x2000, + /// /// Default settings: typical options for the decompiler, with all C# languages features enabled. /// Default = Dynamic | Tuple | ExtensionMethods | DecimalConstants | ReadOnlyStructsAndParameters | RefStructs | UnmanagedConstraints | NullabilityAnnotations | ReadOnlyMethods - | NativeIntegers + | NativeIntegers | FunctionPointers } /// @@ -154,6 +157,8 @@ namespace ICSharpCode.Decompiler.TypeSystem typeSystemOptions |= TypeSystemOptions.ReadOnlyMethods; if (settings.NativeIntegers) typeSystemOptions |= TypeSystemOptions.NativeIntegers; + if (settings.FunctionPointers) + typeSystemOptions |= TypeSystemOptions.FunctionPointers; return typeSystemOptions; } diff --git a/ICSharpCode.Decompiler/TypeSystem/FunctionPointerType.cs b/ICSharpCode.Decompiler/TypeSystem/FunctionPointerType.cs new file mode 100644 index 000000000..866d81202 --- /dev/null +++ b/ICSharpCode.Decompiler/TypeSystem/FunctionPointerType.cs @@ -0,0 +1,113 @@ +// Copyright (c) 2020 Daniel Grunwald +// +// 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.Immutable; +using System.Diagnostics; +using System.Linq; +using System.Reflection.Metadata; + +using ICSharpCode.Decompiler.TypeSystem.Implementation; + +namespace ICSharpCode.Decompiler.TypeSystem +{ + public class FunctionPointerType : AbstractType + { + public readonly SignatureCallingConvention CallingConvention; + public readonly IType ReturnType; + public readonly bool ReturnIsRefReadOnly; + public readonly ImmutableArray ParameterTypes; + public readonly ImmutableArray ParameterReferenceKinds; + + public FunctionPointerType(SignatureCallingConvention callingConvention, + IType returnType, bool returnIsRefReadOnly, + ImmutableArray parameterTypes, ImmutableArray parameterReferenceKinds) + { + this.CallingConvention = callingConvention; + this.ReturnType = returnType; + this.ReturnIsRefReadOnly = returnIsRefReadOnly; + this.ParameterTypes = parameterTypes; + this.ParameterReferenceKinds = parameterReferenceKinds; + Debug.Assert(parameterTypes.Length == parameterReferenceKinds.Length); + } + + public override string Name => "delegate*"; + + public override bool? IsReferenceType => false; + + public override TypeKind Kind => TypeKind.FunctionPointer; + + public override IType AcceptVisitor(TypeVisitor visitor) + { + return visitor.VisitFunctionPointerType(this); + } + + public override IType VisitChildren(TypeVisitor visitor) + { + IType r = ReturnType.AcceptVisitor(visitor); + // Keep ta == null as long as no elements changed, allocate the array only if necessary. + IType[] pt = (r != ReturnType) ? new IType[ParameterTypes.Length] : null; + for (int i = 0; i < ParameterTypes.Length; i++) + { + IType p = ParameterTypes[i].AcceptVisitor(visitor); + if (p == null) + throw new NullReferenceException("TypeVisitor.Visit-method returned null"); + if (pt == null && p != ParameterTypes[i]) + { + // we found a difference, so we need to allocate the array + pt = new IType[ParameterTypes.Length]; + for (int j = 0; j < i; j++) + { + pt[j] = ParameterTypes[j]; + } + } + if (pt != null) + pt[i] = p; + } + if (pt == null) + return this; + else + return new FunctionPointerType(CallingConvention, + r, ReturnIsRefReadOnly, + pt != null ? pt.ToImmutableArray() : ParameterTypes, + ParameterReferenceKinds); + } + + public override bool Equals(IType other) + { + return other is FunctionPointerType fpt && ReturnType.Equals(fpt.ReturnType) + && ReturnIsRefReadOnly == fpt.ReturnIsRefReadOnly + && ParameterTypes.SequenceEqual(fpt.ParameterTypes) + && ParameterReferenceKinds.SequenceEqual(fpt.ParameterReferenceKinds); + } + + public override int GetHashCode() + { + unchecked + { + int hash = ReturnType.GetHashCode(); + foreach (var p in ParameterTypes) + { + hash ^= p.GetHashCode(); + hash *= 8310859; + } + return hash; + } + } + } +} diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs index 442a851d1..67b845bdb 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs @@ -185,7 +185,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation { var nullableContext = methodDef.GetCustomAttributes().GetNullableContext(module.metadata) ?? DeclaringTypeDefinition.NullableContext; var signature = methodDef.DecodeSignature(module.TypeProvider, genericContext); - (returnType, parameters, mod) = DecodeSignature(module, this, signature, methodDef.GetParameters(), nullableContext, module.OptionsForEntity(this)); + (returnType, parameters, mod) = DecodeSignature(module, this, signature, + methodDef.GetParameters(), nullableContext, module.OptionsForEntity(this)); } catch (BadImageFormatException) { @@ -263,7 +264,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation } Debug.Assert(i == parameters.Length); var returnType = ApplyAttributeTypeVisitor.ApplyAttributesToType(signature.ReturnType, - module.Compilation, returnTypeAttributes, metadata, typeSystemOptions, nullableContext); + module.Compilation, returnTypeAttributes, metadata, typeSystemOptions, nullableContext, + isSignatureReturnType: true); return (returnType, parameters, signature.ReturnType as ModifiedType); } #endregion diff --git a/ICSharpCode.Decompiler/TypeSystem/TypeKind.cs b/ICSharpCode.Decompiler/TypeSystem/TypeKind.cs index 4d813cfed..2e4c36045 100644 --- a/ICSharpCode.Decompiler/TypeSystem/TypeKind.cs +++ b/ICSharpCode.Decompiler/TypeSystem/TypeKind.cs @@ -103,5 +103,10 @@ namespace ICSharpCode.Decompiler.TypeSystem /// C# 9 nuint /// NUInt, + + /// + /// C# 9 delegate* + /// + FunctionPointer, } } diff --git a/ICSharpCode.Decompiler/TypeSystem/TypeProvider.cs b/ICSharpCode.Decompiler/TypeSystem/TypeProvider.cs index 95cdb6d0a..27ba4bfca 100644 --- a/ICSharpCode.Decompiler/TypeSystem/TypeProvider.cs +++ b/ICSharpCode.Decompiler/TypeSystem/TypeProvider.cs @@ -62,7 +62,55 @@ namespace ICSharpCode.Decompiler.TypeSystem public IType GetFunctionPointerType(SRM.MethodSignature signature) { - return compilation.FindType(KnownTypeCode.IntPtr); + if ((module.TypeSystemOptions & TypeSystemOptions.FunctionPointers) == 0) + return compilation.FindType(KnownTypeCode.IntPtr); + if (signature.Header.IsInstance) + { + // pointers to member functions are not supported even in C# 9 + return compilation.FindType(KnownTypeCode.IntPtr); + } + IType returnType = signature.ReturnType; + bool returnIsRefReadOnly = false; + if (returnType is ModifiedType modReturn && modReturn.Modifier.IsKnownType(KnownAttribute.In)) + { + returnType = modReturn.ElementType; + returnIsRefReadOnly = true; + } + var parameterTypes = ImmutableArray.CreateBuilder(signature.ParameterTypes.Length); + var parameterReferenceKinds = ImmutableArray.CreateBuilder(signature.ParameterTypes.Length); + foreach (var p in signature.ParameterTypes) + { + IType paramType = p; + ReferenceKind kind = ReferenceKind.None; + if (p is ModifiedType modreq) + { + if (modreq.Modifier.IsKnownType(KnownAttribute.In)) + { + kind = ReferenceKind.In; + paramType = modreq.ElementType; + } + else if (modreq.Modifier.IsKnownType(KnownAttribute.Out)) + { + kind = ReferenceKind.Out; + paramType = modreq.ElementType; + } + } + if (paramType is ByReferenceType brt) + { + paramType = brt.ElementType; + if (kind == ReferenceKind.None) + kind = ReferenceKind.Ref; + } + else + { + kind = ReferenceKind.None; + } + parameterTypes.Add(paramType); + parameterReferenceKinds.Add(kind); + } + return new FunctionPointerType(signature.Header.CallingConvention, + returnType, returnIsRefReadOnly, + parameterTypes.MoveToImmutable(), parameterReferenceKinds.MoveToImmutable()); } public IType GetGenericInstantiation(IType genericType, ImmutableArray typeArguments) diff --git a/ICSharpCode.Decompiler/TypeSystem/TypeVisitor.cs b/ICSharpCode.Decompiler/TypeSystem/TypeVisitor.cs index e934f0ac0..da76087c7 100644 --- a/ICSharpCode.Decompiler/TypeSystem/TypeVisitor.cs +++ b/ICSharpCode.Decompiler/TypeSystem/TypeVisitor.cs @@ -16,8 +16,6 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; - using ICSharpCode.Decompiler.TypeSystem.Implementation; namespace ICSharpCode.Decompiler.TypeSystem @@ -81,5 +79,10 @@ namespace ICSharpCode.Decompiler.TypeSystem { return type.VisitChildren(this); } + + public virtual IType VisitFunctionPointerType(FunctionPointerType type) + { + return type.VisitChildren(this); + } } } diff --git a/ILSpy/Languages/Language.cs b/ILSpy/Languages/Language.cs index 7b9e0c446..1db986d1e 100644 --- a/ILSpy/Languages/Language.cs +++ b/ILSpy/Languages/Language.cs @@ -254,6 +254,25 @@ namespace ICSharpCode.ILSpy return type; } + public override IType VisitFunctionPointerType(FunctionPointerType type) + { + builder.Append("method "); + type.ReturnType.AcceptVisitor(this); + builder.Append(" *("); + bool first = true; + foreach (var p in type.ParameterTypes) + { + if (first) + first = false; + else + builder.Append(", "); + + p.AcceptVisitor(this); + } + builder.Append(')'); + return type; + } + public override IType VisitOtherType(IType type) { WriteType(type); diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs index 1b3abffd1..618957f6b 100644 --- a/ILSpy/Properties/Resources.Designer.cs +++ b/ILSpy/Properties/Resources.Designer.cs @@ -909,6 +909,15 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// + /// Looks up a localized string similar to Function pointers. + /// + public static string DecompilerSettings_FunctionPointers { + get { + return ResourceManager.GetString("DecompilerSettings.FunctionPointers", resourceCulture); + } + } + /// /// Looks up a localized string similar to Include XML documentation comments in the decompiled code. /// diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx index 77fd81994..6bf69acf8 100644 --- a/ILSpy/Properties/Resources.resx +++ b/ILSpy/Properties/Resources.resx @@ -333,6 +333,9 @@ Are you sure you want to continue? Transform to for, if possible + + Function pointers + Include XML documentation comments in the decompiled code From 3831b42197a8782421df8b80e7a59f0b0ff4a068 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 5 Sep 2020 13:31:58 +0200 Subject: [PATCH 2/5] Properly support `calli` instruction and its interaction with function pointer types. --- .../TestCases/Pretty/FunctionPointers.cs | 12 +++ .../CSharp/ExpressionBuilder.cs | 37 +++++--- .../CSharp/RequiredNamespaceCollector.cs | 10 +-- .../CSharp/Syntax/TypeSystemAstBuilder.cs | 40 ++++++--- .../Transforms/IntroduceUnsafeModifier.cs | 26 ++++-- ICSharpCode.Decompiler/IL/ILReader.cs | 18 ++-- ICSharpCode.Decompiler/IL/ILTypeExtensions.cs | 4 +- .../IL/Instructions/CallIndirect.cs | 42 +++------- .../IL/Transforms/ILInlining.cs | 4 + .../TypeSystem/ApplyAttributeTypeVisitor.cs | 10 +-- .../TypeSystem/FunctionPointerType.cs | 84 +++++++++++++++++-- .../TypeSystem/MetadataModule.cs | 13 +-- .../TypeSystem/TypeProvider.cs | 45 +--------- .../TypeSystem/TypeUtils.cs | 3 + ILSpy/Analyzers/Builtin/TypeUsedByAnalyzer.cs | 13 +-- 15 files changed, 202 insertions(+), 159 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs index a84c50cc7..39e03712a 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs @@ -47,6 +47,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty internal class FunctionPointersWithRefParams { public unsafe delegate* F1; + public unsafe delegate* F2; + + // TODO: re-enable test after https://github.com/dotnet/roslyn/issues/47487 is fixed + //public unsafe int CallF1(byte b, char c, out float f) + //{ + // return F1(1, ref c, out f); + //} + + public unsafe void CallF2(byte b, char c, out float f) + { + F2(ref c, out f) = b; + } } // TODO: the new calling convention syntax isn't yet available in the released Roslyn version diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index c227aa2c2..899757466 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -3888,25 +3888,36 @@ namespace ICSharpCode.Decompiler.CSharp { return ErrorExpression("calli with instance method signature not supportd"); } - var ty = new FunctionPointerAstType(); - if (inst.CallingConvention != System.Reflection.Metadata.SignatureCallingConvention.Default) + + var functionPointer = Translate(inst.FunctionPointer, typeHint: inst.FunctionPointerType); + if (!NormalizeTypeVisitor.TypeErasure.EquivalentTypes(functionPointer.Type, inst.FunctionPointerType)) + { + functionPointer = functionPointer.ConvertTo(inst.FunctionPointerType, this); + } + var fpt = (FunctionPointerType)functionPointer.Type.SkipModifiers(); + var invocation = new InvocationExpression(); + invocation.Target = functionPointer; + foreach (var (argInst, (paramType, paramRefKind)) in inst.Arguments.Zip(fpt.ParameterTypes.Zip(fpt.ParameterReferenceKinds))) { - ty.CallingConvention = inst.CallingConvention.ToString().ToLowerInvariant(); + var arg = Translate(argInst, typeHint: paramType).ConvertTo(paramType, this, allowImplicitConversion: true); + if (paramRefKind != ReferenceKind.None) + { + arg = ChangeDirectionExpressionTo(arg, paramRefKind); + } + invocation.Arguments.Add(arg); } - foreach (var parameterType in inst.ParameterTypes) + if (fpt.ReturnType.SkipModifiers() is ByReferenceType brt) { - ty.Parameters.Add(new ParameterDeclaration { - Type = astBuilder.ConvertType(parameterType) - }); + var rr = new ResolveResult(brt.ElementType); + return new DirectionExpression( + FieldDirection.Ref, + invocation.WithRR(rr).WithILInstruction(inst) + ).WithRR(new ByReferenceResolveResult(rr, ReferenceKind.Ref)).WithoutILInstruction(); } - ty.ReturnType = 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)) + else { - invocation.Arguments.Add(Translate(arg, typeHint: paramType).ConvertTo(paramType, this, allowImplicitConversion: true)); + return invocation.WithRR(new ResolveResult(fpt.ReturnType)).WithILInstruction(inst); } - return invocation.WithRR(new ResolveResult(inst.ReturnType)).WithILInstruction(inst); } protected internal override TranslatedExpression VisitDeconstructInstruction(DeconstructInstruction inst, TranslationContext context) diff --git a/ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs b/ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs index 3f3a363de..b8428fa7f 100644 --- a/ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs +++ b/ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs @@ -341,20 +341,16 @@ namespace ICSharpCode.Decompiler.CSharp } if (sig.GetKind() == StandaloneSignatureKind.Method) { - MethodSignature methodSig; + FunctionPointerType fpt; try { - methodSig = module.DecodeMethodSignature((StandaloneSignatureHandle)handle, genericContext); + (_, fpt) = module.DecodeMethodSignature((StandaloneSignatureHandle)handle, genericContext); } catch (BadImageFormatException) { break; } - CollectNamespacesForTypeReference(methodSig.ReturnType); - foreach (var paramType in methodSig.ParameterTypes) - { - CollectNamespacesForTypeReference(paramType); - } + CollectNamespacesForTypeReference(fpt); } break; } diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs index 52ee0cf71..66c3df0cb 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs @@ -311,23 +311,41 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax var astType = new FunctionPointerAstType(); for (int i = 0; i < fpt.ParameterTypes.Length; i++) { - astType.Parameters.Add(new ParameterDeclaration { - ParameterModifier = fpt.ParameterReferenceKinds[i] switch - { - ReferenceKind.In => ParameterModifier.In, - ReferenceKind.Ref => ParameterModifier.Ref, - ReferenceKind.Out => ParameterModifier.Out, - _ => ParameterModifier.None, - }, - Type = ConvertType(fpt.ParameterTypes[i]) - }); + var paramDecl = new ParameterDeclaration(); + paramDecl.ParameterModifier = fpt.ParameterReferenceKinds[i] switch + { + ReferenceKind.In => ParameterModifier.In, + ReferenceKind.Ref => ParameterModifier.Ref, + ReferenceKind.Out => ParameterModifier.Out, + _ => ParameterModifier.None, + }; + IType parameterType = fpt.ParameterTypes[i]; + if (paramDecl.ParameterModifier != ParameterModifier.None && parameterType is ByReferenceType brt) + { + paramDecl.Type = ConvertType(brt.ElementType); + } + else + { + paramDecl.Type = ConvertType(parameterType); + } + astType.Parameters.Add(paramDecl); } astType.ReturnType = ConvertType(fpt.ReturnType); if (fpt.ReturnIsRefReadOnly && astType.ReturnType is ComposedType ct && ct.HasRefSpecifier) { ct.HasReadOnlySpecifier = true; } - return astType; + ITypeDefinition treatedAs = fpt.GetDefinition(); + if (treatedAs != null) + { + var result = ConvertTypeHelper(treatedAs); + result.AddChild(new Comment(astType.ToString(), CommentType.MultiLine), Roles.Comment); + return result; + } + else + { + return astType; + } } else { diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUnsafeModifier.cs b/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUnsafeModifier.cs index a94eee76e..d814221f2 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUnsafeModifier.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUnsafeModifier.cs @@ -130,12 +130,12 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms var rr = memberReferenceExpression.GetResolveResult(); if (rr != null) { - if (rr.Type is PointerType) + if (IsPointer(rr.Type)) return true; if (rr is MemberResolveResult mrr && mrr.Member.ReturnType.Kind == TypeKind.Delegate) { var method = mrr.Member.ReturnType.GetDefinition()?.GetDelegateInvokeMethod(); - if (method != null && (method.ReturnType is PointerType || method.Parameters.Any(p => p.Type is PointerType))) + if (method != null && (IsPointer(method.ReturnType) || method.Parameters.Any(p => IsPointer(p.Type)))) return true; } } @@ -149,12 +149,12 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms var rr = identifierExpression.GetResolveResult(); if (rr != null) { - if (rr.Type is PointerType) + if (IsPointer(rr.Type)) return true; if (rr is MemberResolveResult mrr && mrr.Member.ReturnType.Kind == TypeKind.Delegate) { var method = mrr.Member.ReturnType.GetDefinition()?.GetDelegateInvokeMethod(); - if (method != null && (method.ReturnType is PointerType || method.Parameters.Any(p => p.Type is PointerType))) + if (method != null && (IsPointer(method.ReturnType) || method.Parameters.Any(p => IsPointer(p.Type)))) return true; } } @@ -166,7 +166,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms { bool result = base.VisitStackAllocExpression(stackAllocExpression); var rr = stackAllocExpression.GetResolveResult(); - if (rr?.Type is PointerType) + if (IsPointer(rr?.Type)) return true; return result; } @@ -175,7 +175,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms { bool result = base.VisitInvocationExpression(invocationExpression); var rr = invocationExpression.GetResolveResult(); - if (rr != null && rr.Type is PointerType) + if (IsPointer(rr?.Type)) return true; return result; } @@ -185,5 +185,19 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms base.VisitFixedVariableInitializer(fixedVariableInitializer); return true; } + + private bool IsPointer(IType type) + { + switch (type?.Kind) + { + case TypeKind.Pointer: + case TypeKind.FunctionPointer: + return true; + case TypeKind.ByReference: + return IsPointer(((ByReferenceType)type).ElementType); + default: + return false; + } + } } } diff --git a/ICSharpCode.Decompiler/IL/ILReader.cs b/ICSharpCode.Decompiler/IL/ILReader.cs index 2f015c77e..d4d2498a8 100644 --- a/ICSharpCode.Decompiler/IL/ILReader.cs +++ b/ICSharpCode.Decompiler/IL/ILReader.cs @@ -1566,24 +1566,22 @@ namespace ICSharpCode.Decompiler.IL ILInstruction DecodeCallIndirect() { var signatureHandle = (StandaloneSignatureHandle)ReadAndDecodeMetadataToken(); - var signature = module.DecodeMethodSignature(signatureHandle, genericContext); + var (header, fpt) = module.DecodeMethodSignature(signatureHandle, genericContext); var functionPointer = Pop(StackType.I); - int firstArgument = signature.Header.IsInstance ? 1 : 0; - var arguments = new ILInstruction[firstArgument + signature.ParameterTypes.Length]; - for (int i = signature.ParameterTypes.Length - 1; i >= 0; i--) + int firstArgument = header.IsInstance ? 1 : 0; + var arguments = new ILInstruction[firstArgument + fpt.ParameterTypes.Length]; + for (int i = fpt.ParameterTypes.Length - 1; i >= 0; i--) { - arguments[firstArgument + i] = Pop(signature.ParameterTypes[i].GetStackType()); + arguments[firstArgument + i] = Pop(fpt.ParameterTypes[i].GetStackType()); } if (firstArgument == 1) { arguments[0] = Pop(); } var call = new CallIndirect( - signature.Header.IsInstance, - signature.Header.HasExplicitThis, - signature.Header.CallingConvention, - signature.ReturnType, - signature.ParameterTypes, + header.IsInstance, + header.HasExplicitThis, + fpt, functionPointer, arguments ); diff --git a/ICSharpCode.Decompiler/IL/ILTypeExtensions.cs b/ICSharpCode.Decompiler/IL/ILTypeExtensions.cs index fef012cac..feac446f4 100644 --- a/ICSharpCode.Decompiler/IL/ILTypeExtensions.cs +++ b/ICSharpCode.Decompiler/IL/ILTypeExtensions.cs @@ -16,8 +16,6 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System.Reflection.Metadata; - using ICSharpCode.Decompiler.TypeSystem; namespace ICSharpCode.Decompiler.IL @@ -163,7 +161,7 @@ namespace ICSharpCode.Decompiler.IL case CallVirt callVirt: return callVirt.Method.ReturnType; case CallIndirect calli: - return calli.ReturnType; + return calli.FunctionPointerType.ReturnType; case UserDefinedLogicOperator logicOp: return logicOp.Method.ReturnType; case LdObj ldobj: diff --git a/ICSharpCode.Decompiler/IL/Instructions/CallIndirect.cs b/ICSharpCode.Decompiler/IL/Instructions/CallIndirect.cs index c4da53a0c..a12592d06 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/CallIndirect.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/CallIndirect.cs @@ -16,9 +16,7 @@ // 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 System.Collections.Immutable; using System.Diagnostics; using System.Linq; @@ -37,17 +35,8 @@ namespace ICSharpCode.Decompiler.IL public readonly InstructionCollection Arguments; public bool IsInstance { get; } public bool HasExplicitThis { get; } - public System.Reflection.Metadata.SignatureCallingConvention CallingConvention { get; } - public IType ReturnType { get; } - public ImmutableArray ParameterTypes { get; } - - /// - /// A 'final instruction' that gets executed after the Instructions collection. - /// Provides the return value for the block. - /// - /// - /// Blocks in containers must have 'Nop' as a final instruction. - /// + public FunctionPointerType FunctionPointerType { get; } + public ILInstruction FunctionPointer { get { return functionPointer; @@ -58,14 +47,12 @@ namespace ICSharpCode.Decompiler.IL } } - public CallIndirect(bool isInstance, bool hasExplicitThis, System.Reflection.Metadata.SignatureCallingConvention callingConvention, IType returnType, ImmutableArray parameterTypes, + public CallIndirect(bool isInstance, bool hasExplicitThis, FunctionPointerType functionPointerType, 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.FunctionPointerType = functionPointerType; this.FunctionPointer = functionPointer; this.Arguments = new InstructionCollection(this, 1); this.Arguments.AddRange(arguments); @@ -73,24 +60,24 @@ namespace ICSharpCode.Decompiler.IL public override ILInstruction Clone() { - return new CallIndirect(IsInstance, HasExplicitThis, CallingConvention, ReturnType, ParameterTypes, + return new CallIndirect(IsInstance, HasExplicitThis, FunctionPointerType, functionPointer.Clone(), this.Arguments.Select(inst => inst.Clone()) ).WithILRange(this); } - public override StackType ResultType => ReturnType.GetStackType(); + public override StackType ResultType => FunctionPointerType.ReturnType.GetStackType(); internal override void CheckInvariant(ILPhase phase) { base.CheckInvariant(phase); - Debug.Assert(Arguments.Count == ParameterTypes.Length + (IsInstance ? 1 : 0)); + Debug.Assert(Arguments.Count == FunctionPointerType.ParameterTypes.Length + (IsInstance ? 1 : 0)); } public override void WriteTo(ITextOutput output, ILAstWritingOptions options) { WriteILRange(output, options); output.Write("call.indirect "); - ReturnType.WriteTo(output); + FunctionPointerType.ReturnType.WriteTo(output); output.Write('('); functionPointer.WriteTo(output, options); int firstArgument = IsInstance ? 1 : 0; @@ -99,7 +86,7 @@ namespace ICSharpCode.Decompiler.IL output.Write(", "); Arguments[0].WriteTo(output, options); } - foreach (var (inst, type) in Arguments.Zip(ParameterTypes, (a, b) => (a, b))) + foreach (var (inst, type) in Arguments.Zip(FunctionPointerType.ParameterTypes, (a, b) => (a, b))) { output.Write(", "); inst.WriteTo(output, options); @@ -161,16 +148,7 @@ namespace ICSharpCode.Decompiler.IL return false; if (HasExplicitThis != other.HasExplicitThis) return false; - if (CallingConvention != other.CallingConvention) - return false; - if (ParameterTypes.Length != other.ParameterTypes.Length) - return false; - for (int i = 0; i < ParameterTypes.Length; i++) - { - if (!ParameterTypes[i].Equals(other.ParameterTypes[i])) - return false; - } - return ReturnType.Equals(other.ReturnType); + return FunctionPointerType.Equals(other.FunctionPointerType); } } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs index f505a04d3..ca144927e 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs @@ -459,6 +459,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms return ldloc.Variable.IsRefReadOnly; case Call call: return call.Method.ReturnTypeIsRefReadOnly; + case CallIndirect calli: + return calli.FunctionPointerType.ReturnIsRefReadOnly; case AddressOf _: // C# doesn't allow mutation of value-type temporaries return true; @@ -557,6 +559,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms return true; } break; + case OpCode.CallIndirect when loadInst.SlotInfo == CallIndirect.FunctionPointerSlot: + return true; case OpCode.LdElema: if (((LdElema)parent).WithSystemIndex) { diff --git a/ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs b/ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs index cbf8771cf..fee4a6d2d 100644 --- a/ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs +++ b/ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs @@ -333,9 +333,9 @@ namespace ICSharpCode.Decompiler.TypeSystem dynamicTypeIndex += type.ParameterReferenceKinds[i] switch { ReferenceKind.None => 1, - ReferenceKind.Ref => 2, - ReferenceKind.Out => 3, - ReferenceKind.In => 3, + ReferenceKind.Ref => 1, + ReferenceKind.Out => 2, // in/out also count the modreq + ReferenceKind.In => 2, _ => throw new NotSupportedException() }; parameters[i] = type.ParameterTypes[i].AcceptVisitor(this); @@ -343,9 +343,7 @@ namespace ICSharpCode.Decompiler.TypeSystem } if (!changed) return type; - return new FunctionPointerType(type.CallingConvention, - returnType, type.ReturnIsRefReadOnly, - parameters.ToImmutableArray(), type.ParameterReferenceKinds); + return type.WithSignature(returnType, parameters.ToImmutableArray()); } public override IType VisitTypeDefinition(ITypeDefinition type) diff --git a/ICSharpCode.Decompiler/TypeSystem/FunctionPointerType.cs b/ICSharpCode.Decompiler/TypeSystem/FunctionPointerType.cs index 866d81202..36ba05bd7 100644 --- a/ICSharpCode.Decompiler/TypeSystem/FunctionPointerType.cs +++ b/ICSharpCode.Decompiler/TypeSystem/FunctionPointerType.cs @@ -23,21 +23,70 @@ using System.Linq; using System.Reflection.Metadata; using ICSharpCode.Decompiler.TypeSystem.Implementation; +using ICSharpCode.Decompiler.Util; namespace ICSharpCode.Decompiler.TypeSystem { public class FunctionPointerType : AbstractType { + public static FunctionPointerType FromSignature(MethodSignature signature, MetadataModule module) + { + IType returnType = signature.ReturnType; + bool returnIsRefReadOnly = false; + if (returnType is ModifiedType modReturn && modReturn.Modifier.IsKnownType(KnownAttribute.In)) + { + returnType = modReturn.ElementType; + returnIsRefReadOnly = true; + } + var parameterTypes = ImmutableArray.CreateBuilder(signature.ParameterTypes.Length); + var parameterReferenceKinds = ImmutableArray.CreateBuilder(signature.ParameterTypes.Length); + foreach (var p in signature.ParameterTypes) + { + IType paramType = p; + ReferenceKind kind = ReferenceKind.None; + if (p is ModifiedType modreq) + { + if (modreq.Modifier.IsKnownType(KnownAttribute.In)) + { + kind = ReferenceKind.In; + paramType = modreq.ElementType; + } + else if (modreq.Modifier.IsKnownType(KnownAttribute.Out)) + { + kind = ReferenceKind.Out; + paramType = modreq.ElementType; + } + } + if (paramType.Kind == TypeKind.ByReference) + { + if (kind == ReferenceKind.None) + kind = ReferenceKind.Ref; + } + else + { + kind = ReferenceKind.None; + } + parameterTypes.Add(paramType); + parameterReferenceKinds.Add(kind); + } + return new FunctionPointerType( + module, signature.Header.CallingConvention, + returnType, returnIsRefReadOnly, + parameterTypes.MoveToImmutable(), parameterReferenceKinds.MoveToImmutable()); + } + + private readonly MetadataModule module; public readonly SignatureCallingConvention CallingConvention; public readonly IType ReturnType; public readonly bool ReturnIsRefReadOnly; public readonly ImmutableArray ParameterTypes; public readonly ImmutableArray ParameterReferenceKinds; - public FunctionPointerType(SignatureCallingConvention callingConvention, + public FunctionPointerType(MetadataModule module, SignatureCallingConvention callingConvention, IType returnType, bool returnIsRefReadOnly, ImmutableArray parameterTypes, ImmutableArray parameterReferenceKinds) { + this.module = module; this.CallingConvention = callingConvention; this.ReturnType = returnType; this.ReturnIsRefReadOnly = returnIsRefReadOnly; @@ -50,7 +99,21 @@ namespace ICSharpCode.Decompiler.TypeSystem public override bool? IsReferenceType => false; - public override TypeKind Kind => TypeKind.FunctionPointer; + public override TypeKind Kind => ((module.TypeSystemOptions & TypeSystemOptions.FunctionPointers) != 0) ? TypeKind.FunctionPointer : TypeKind.Struct; + + public override ITypeDefinition GetDefinition() + { + if ((module.TypeSystemOptions & TypeSystemOptions.FunctionPointers) != 0) + { + return null; + } + else + { + // If FunctionPointers are not enabled in the TS, we still use FunctionPointerType instances; + // but have them act as if they were aliases for UIntPtr. + return module.Compilation.FindType(KnownTypeCode.UIntPtr).GetDefinition(); + } + } public override IType AcceptVisitor(TypeVisitor visitor) { @@ -82,7 +145,8 @@ namespace ICSharpCode.Decompiler.TypeSystem if (pt == null) return this; else - return new FunctionPointerType(CallingConvention, + return new FunctionPointerType( + module, CallingConvention, r, ReturnIsRefReadOnly, pt != null ? pt.ToImmutableArray() : ParameterTypes, ParameterReferenceKinds); @@ -90,7 +154,9 @@ namespace ICSharpCode.Decompiler.TypeSystem public override bool Equals(IType other) { - return other is FunctionPointerType fpt && ReturnType.Equals(fpt.ReturnType) + return other is FunctionPointerType fpt + && CallingConvention == fpt.CallingConvention + && ReturnType.Equals(fpt.ReturnType) && ReturnIsRefReadOnly == fpt.ReturnIsRefReadOnly && ParameterTypes.SequenceEqual(fpt.ParameterTypes) && ParameterReferenceKinds.SequenceEqual(fpt.ParameterReferenceKinds); @@ -101,13 +167,19 @@ namespace ICSharpCode.Decompiler.TypeSystem unchecked { int hash = ReturnType.GetHashCode(); - foreach (var p in ParameterTypes) + foreach (var (p, k) in ParameterTypes.Zip(ParameterReferenceKinds)) { - hash ^= p.GetHashCode(); + hash ^= p.GetHashCode() ^ k.GetHashCode(); hash *= 8310859; } return hash; } } + + internal IType WithSignature(IType returnType, ImmutableArray parameterTypes) + { + return new FunctionPointerType(this.module, this.CallingConvention, returnType, + this.ReturnIsRefReadOnly, parameterTypes, this.ParameterReferenceKinds); + } } } diff --git a/ICSharpCode.Decompiler/TypeSystem/MetadataModule.cs b/ICSharpCode.Decompiler/TypeSystem/MetadataModule.cs index 313d12a14..e02d19f2c 100644 --- a/ICSharpCode.Decompiler/TypeSystem/MetadataModule.cs +++ b/ICSharpCode.Decompiler/TypeSystem/MetadataModule.cs @@ -689,21 +689,14 @@ namespace ICSharpCode.Decompiler.TypeSystem #endregion #region Decode Standalone Signature - public MethodSignature DecodeMethodSignature(StandaloneSignatureHandle handle, GenericContext genericContext) + public (SignatureHeader, FunctionPointerType) DecodeMethodSignature(StandaloneSignatureHandle handle, GenericContext genericContext) { var standaloneSignature = metadata.GetStandaloneSignature(handle); if (standaloneSignature.GetKind() != StandaloneSignatureKind.Method) throw new BadImageFormatException("Expected Method signature"); var sig = standaloneSignature.DecodeMethodSignature(TypeProvider, genericContext); - return new MethodSignature( - sig.Header, - IntroduceTupleTypes(sig.ReturnType), - sig.RequiredParameterCount, - sig.GenericParameterCount, - ImmutableArray.CreateRange( - sig.ParameterTypes, IntroduceTupleTypes - ) - ); + var fpt = FunctionPointerType.FromSignature(sig, this); + return (sig.Header, (FunctionPointerType)IntroduceTupleTypes(fpt)); } public ImmutableArray DecodeLocalSignature(StandaloneSignatureHandle handle, GenericContext genericContext) diff --git a/ICSharpCode.Decompiler/TypeSystem/TypeProvider.cs b/ICSharpCode.Decompiler/TypeSystem/TypeProvider.cs index 27ba4bfca..5ac4b1c50 100644 --- a/ICSharpCode.Decompiler/TypeSystem/TypeProvider.cs +++ b/ICSharpCode.Decompiler/TypeSystem/TypeProvider.cs @@ -62,55 +62,12 @@ namespace ICSharpCode.Decompiler.TypeSystem public IType GetFunctionPointerType(SRM.MethodSignature signature) { - if ((module.TypeSystemOptions & TypeSystemOptions.FunctionPointers) == 0) - return compilation.FindType(KnownTypeCode.IntPtr); if (signature.Header.IsInstance) { // pointers to member functions are not supported even in C# 9 return compilation.FindType(KnownTypeCode.IntPtr); } - IType returnType = signature.ReturnType; - bool returnIsRefReadOnly = false; - if (returnType is ModifiedType modReturn && modReturn.Modifier.IsKnownType(KnownAttribute.In)) - { - returnType = modReturn.ElementType; - returnIsRefReadOnly = true; - } - var parameterTypes = ImmutableArray.CreateBuilder(signature.ParameterTypes.Length); - var parameterReferenceKinds = ImmutableArray.CreateBuilder(signature.ParameterTypes.Length); - foreach (var p in signature.ParameterTypes) - { - IType paramType = p; - ReferenceKind kind = ReferenceKind.None; - if (p is ModifiedType modreq) - { - if (modreq.Modifier.IsKnownType(KnownAttribute.In)) - { - kind = ReferenceKind.In; - paramType = modreq.ElementType; - } - else if (modreq.Modifier.IsKnownType(KnownAttribute.Out)) - { - kind = ReferenceKind.Out; - paramType = modreq.ElementType; - } - } - if (paramType is ByReferenceType brt) - { - paramType = brt.ElementType; - if (kind == ReferenceKind.None) - kind = ReferenceKind.Ref; - } - else - { - kind = ReferenceKind.None; - } - parameterTypes.Add(paramType); - parameterReferenceKinds.Add(kind); - } - return new FunctionPointerType(signature.Header.CallingConvention, - returnType, returnIsRefReadOnly, - parameterTypes.MoveToImmutable(), parameterReferenceKinds.MoveToImmutable()); + return FunctionPointerType.FromSignature(signature, module); } public IType GetGenericInstantiation(IType genericType, ImmutableArray typeArguments) diff --git a/ICSharpCode.Decompiler/TypeSystem/TypeUtils.cs b/ICSharpCode.Decompiler/TypeSystem/TypeUtils.cs index 3d2856c34..d2176704a 100644 --- a/ICSharpCode.Decompiler/TypeSystem/TypeUtils.cs +++ b/ICSharpCode.Decompiler/TypeSystem/TypeUtils.cs @@ -273,6 +273,7 @@ namespace ICSharpCode.Decompiler.TypeSystem case TypeKind.Pointer: case TypeKind.NInt: case TypeKind.NUInt: + case TypeKind.FunctionPointer: return StackType.I; case TypeKind.TypeParameter: // Type parameters are always considered StackType.O, even @@ -340,6 +341,7 @@ namespace ICSharpCode.Decompiler.TypeSystem { case TypeKind.Pointer: case TypeKind.NUInt: + case TypeKind.FunctionPointer: return Sign.Unsigned; case TypeKind.NInt: return Sign.Signed; @@ -421,6 +423,7 @@ namespace ICSharpCode.Decompiler.TypeSystem case TypeKind.ByReference: return PrimitiveType.Ref; case TypeKind.NInt: + case TypeKind.FunctionPointer: return PrimitiveType.I; case TypeKind.NUInt: return PrimitiveType.U; diff --git a/ILSpy/Analyzers/Builtin/TypeUsedByAnalyzer.cs b/ILSpy/Analyzers/Builtin/TypeUsedByAnalyzer.cs index c710921e2..66ae80887 100644 --- a/ILSpy/Analyzers/Builtin/TypeUsedByAnalyzer.cs +++ b/ILSpy/Analyzers/Builtin/TypeUsedByAnalyzer.cs @@ -16,13 +16,9 @@ // 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 System.ComponentModel.Composition; using System.Diagnostics; -using System.Linq; using System.Reflection.Metadata; -using System.Reflection.Metadata.Ecma335; using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Disassembler; @@ -266,13 +262,8 @@ namespace ICSharpCode.ILSpy.Analyzers.Builtin break; case HandleKind.StandaloneSignature: - var signature = module.DecodeMethodSignature((StandaloneSignatureHandle)member, genericContext); - foreach (var type in signature.ParameterTypes) - { - type.AcceptVisitor(visitor); - } - - signature.ReturnType.AcceptVisitor(visitor); + var (_, fpt) = module.DecodeMethodSignature((StandaloneSignatureHandle)member, genericContext); + fpt.AcceptVisitor(visitor); if (visitor.Found) return; From d13a8bb64d0927d4a82dd6a5803ec4e595e37979 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 6 Sep 2020 18:28:54 +0200 Subject: [PATCH 3/5] Support `ldftn` in the context of a function pointer. --- .../TestCases/Pretty/FunctionPointers.cs | 26 ++++++++++++++++ ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 12 ++++++- .../CSharp/ExpressionBuilder.cs | 31 ++++++++++++++++--- .../CSharp/Resolver/CSharpConversions.cs | 10 +++--- .../CSharp/Syntax/TypeSystemAstBuilder.cs | 4 +++ .../CSharp/TranslatedExpression.cs | 14 ++++----- .../TypeSystem/TypeSystemExtensions.cs | 11 ++++++- .../Util/CollectionExtensions.cs | 15 +++++++++ 8 files changed, 105 insertions(+), 18 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs index 39e03712a..c3f05a953 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs @@ -2,6 +2,31 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { + public class FunctionPointerAddressOf + { + public static void Overloaded() + { + } + public static void Overloaded(int a) + { + } + + public unsafe delegate* GetAddress() + { + return &Overloaded; + } + + public unsafe IntPtr GetAddressAsIntPtr() + { + return (IntPtr)(delegate*)(&Overloaded); + } + + public unsafe void* GetAddressAsVoidPtr() + { + return (delegate*)(&Overloaded); + } + } + internal class FunctionPointersWithDynamicTypes { public class D @@ -68,4 +93,5 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty // public delegate* unmanaged unmanaged; // public delegate* unmanaged[Cdecl] cdecl; //} + } diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index 625e3be01..21fd71c98 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -1197,6 +1197,16 @@ namespace ICSharpCode.Decompiler.CSharp { IType argType = NullableType.GetUnderlyingType(arguments[0].Type); operatorCandidates = resolver.GetUserDefinedOperatorCandidates(argType, method.Name); + if (method.Name == "op_Explicit") + { + // For casts, also consider candidates from the target type we are casting to. + var hashSet = new HashSet(operatorCandidates); + IType targetType = NullableType.GetUnderlyingType(method.ReturnType); + hashSet.UnionWith( + resolver.GetUserDefinedOperatorCandidates(targetType, method.Name) + ); + operatorCandidates = hashSet; + } } else if (arguments.Length == 2) { @@ -1825,4 +1835,4 @@ namespace ICSharpCode.Decompiler.CSharp return false; } } -} \ No newline at end of file +} diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 899757466..53b9f84de 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -20,6 +20,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Reflection.Metadata; using System.Threading; using ICSharpCode.Decompiler.CSharp.Resolver; @@ -3869,16 +3870,38 @@ namespace ICSharpCode.Decompiler.CSharp protected internal override TranslatedExpression VisitLdFtn(LdFtn inst, TranslationContext context) { ExpressionWithResolveResult delegateRef = new CallBuilder(this, typeSystem, settings).BuildMethodReference(inst.Method, isVirtual: false); - return new InvocationExpression(new IdentifierExpression("__ldftn"), delegateRef) - .WithRR(new ResolveResult(compilation.FindType(KnownTypeCode.IntPtr))) - .WithILInstruction(inst); + if (!inst.Method.IsStatic) + { + // C# 9 function pointers don't support instance methods + return new InvocationExpression(new IdentifierExpression("__ldftn"), delegateRef) + .WithRR(new ResolveResult(new PointerType(compilation.FindType(KnownTypeCode.Void)))) + .WithILInstruction(inst); + } + // C# 9 function pointer + var ftp = new FunctionPointerType( + typeSystem.MainModule, + SignatureCallingConvention.Default, // TODO + inst.Method.ReturnType, inst.Method.ReturnTypeIsRefReadOnly, + inst.Method.Parameters.SelectImmutableArray(p => p.Type), + inst.Method.Parameters.SelectImmutableArray(p => p.ReferenceKind) + ); + ExpressionWithResolveResult addressOf = new UnaryOperatorExpression( + UnaryOperatorType.AddressOf, + delegateRef + ).WithRR(new ResolveResult(SpecialType.NoType)).WithILInstruction(inst); + var conversion = Conversion.MethodGroupConversion( + inst.Method, isVirtualMethodLookup: false, delegateCapturesFirstArgument: false); + return new CastExpression(ConvertType(ftp), addressOf) + .WithRR(new ConversionResolveResult(ftp, addressOf.ResolveResult, conversion)) + .WithoutILInstruction(); } protected internal override TranslatedExpression VisitLdVirtFtn(LdVirtFtn inst, TranslationContext context) { + // C# 9 function pointers don't support instance methods ExpressionWithResolveResult delegateRef = new CallBuilder(this, typeSystem, settings).BuildMethodReference(inst.Method, isVirtual: true); return new InvocationExpression(new IdentifierExpression("__ldvirtftn"), delegateRef) - .WithRR(new ResolveResult(compilation.FindType(KnownTypeCode.IntPtr))) + .WithRR(new ResolveResult(new PointerType(compilation.FindType(KnownTypeCode.Void)))) .WithILInstruction(inst); } diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs index e1555a96d..403fb08e7 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs @@ -870,9 +870,9 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver bool ImplicitPointerConversion(IType fromType, IType toType) { // C# 4.0 spec: §18.4 Pointer conversions - if (fromType is PointerType && toType is PointerType && toType.ReflectionName == "System.Void*") + if (fromType.Kind.IsAnyPointer() && toType is PointerType && toType.ReflectionName == "System.Void*") return true; - if (fromType.Kind == TypeKind.Null && toType is PointerType) + if (fromType.Kind == TypeKind.Null && toType.Kind.IsAnyPointer()) return true; return false; } @@ -880,13 +880,13 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver bool ExplicitPointerConversion(IType fromType, IType toType) { // C# 4.0 spec: §18.4 Pointer conversions - if (fromType.Kind == TypeKind.Pointer) + if (fromType.Kind.IsAnyPointer()) { - return toType.Kind == TypeKind.Pointer || IsIntegerType(toType); + return toType.Kind.IsAnyPointer() || IsIntegerType(toType); } else { - return toType.Kind == TypeKind.Pointer && IsIntegerType(fromType); + return toType.Kind.IsAnyPointer() && IsIntegerType(fromType); } } diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs index 66c3df0cb..c64572a04 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs @@ -309,6 +309,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax else if (type is FunctionPointerType fpt) { var astType = new FunctionPointerAstType(); + if (fpt.CallingConvention != System.Reflection.Metadata.SignatureCallingConvention.Default) + { + astType.CallingConvention = fpt.CallingConvention.ToString().ToLowerInvariant(); + } for (int i = 0; i < fpt.ParameterTypes.Length; i++) { var paramDecl = new ParameterDeclaration(); diff --git a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs index 9ccda1dde..6413eeb0b 100644 --- a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs @@ -370,7 +370,7 @@ namespace ICSharpCode.Decompiler.CSharp .ConvertTo(targetType, expressionBuilder, checkForOverflow); } } - else if (type.Kind != TypeKind.Pointer) + else if (!type.Kind.IsAnyPointer()) { // If overflow-checking is disabled, the only way to truncate to native size // without throwing an exception in 32-bit mode is to use a pointer type. @@ -380,7 +380,7 @@ namespace ICSharpCode.Decompiler.CSharp } else if (targetUType.IsKnownType(KnownTypeCode.UIntPtr)) { // Conversion to UIntPtr - if (type.IsKnownType(KnownTypeCode.UInt32) || type.Kind == TypeKind.Pointer || type.Kind == TypeKind.NUInt) + if (type.IsKnownType(KnownTypeCode.UInt32) || type.Kind.IsAnyPointer() || type.Kind == TypeKind.NUInt) { // normal casts work for uint/nuint and pointers (both in checked and unchecked context) } @@ -409,22 +409,22 @@ namespace ICSharpCode.Decompiler.CSharp } } - if (targetType.Kind == TypeKind.Pointer && type.Kind == TypeKind.Enum) + if (targetType.Kind.IsAnyPointer() && type.Kind == TypeKind.Enum) { // enum to pointer: C# doesn't allow such casts // -> convert via underlying type return this.ConvertTo(type.GetEnumUnderlyingType(), expressionBuilder, checkForOverflow) .ConvertTo(targetType, expressionBuilder, checkForOverflow); } - else if (targetUType.Kind == TypeKind.Enum && type.Kind == TypeKind.Pointer) + else if (targetUType.Kind == TypeKind.Enum && type.Kind.IsAnyPointer()) { // pointer to enum: C# doesn't allow such casts // -> convert via underlying type return this.ConvertTo(targetUType.GetEnumUnderlyingType(), expressionBuilder, checkForOverflow) .ConvertTo(targetType, expressionBuilder, checkForOverflow); } - if (targetType.Kind == TypeKind.Pointer && type.IsKnownType(KnownTypeCode.Char) - || targetUType.IsKnownType(KnownTypeCode.Char) && type.Kind == TypeKind.Pointer) + if (targetType.Kind.IsAnyPointer() && type.IsKnownType(KnownTypeCode.Char) + || targetUType.IsKnownType(KnownTypeCode.Char) && type.Kind.IsAnyPointer()) { // char <-> pointer: C# doesn't allow such casts // -> convert via ushort @@ -529,7 +529,7 @@ namespace ICSharpCode.Decompiler.CSharp .ConvertTo(targetType, expressionBuilder, checkForOverflow, allowImplicitConversion); } } - if (targetType.Kind == TypeKind.Pointer && (0.Equals(ResolveResult.ConstantValue) || 0u.Equals(ResolveResult.ConstantValue))) + if (targetType.Kind.IsAnyPointer() && (0.Equals(ResolveResult.ConstantValue) || 0u.Equals(ResolveResult.ConstantValue))) { if (allowImplicitConversion) { diff --git a/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs b/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs index af5bfb903..bf25900b6 100644 --- a/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs +++ b/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs @@ -20,7 +20,6 @@ using System; using System.Collections.Generic; using System.Linq; -using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Semantics; using ICSharpCode.Decompiler.TypeSystem.Implementation; @@ -294,6 +293,16 @@ namespace ICSharpCode.Decompiler.TypeSystem return accessor.ThisIsRefReadOnly && accessor.DeclaringTypeDefinition?.IsReadOnly == false; } + public static bool IsAnyPointer(this TypeKind typeKind) + { + return typeKind switch + { + TypeKind.Pointer => true, + TypeKind.FunctionPointer => true, + _ => false + }; + } + #region GetType/Member /// /// Gets all type definitions in the compilation. diff --git a/ICSharpCode.Decompiler/Util/CollectionExtensions.cs b/ICSharpCode.Decompiler/Util/CollectionExtensions.cs index a4a4123ac..2b54131a9 100644 --- a/ICSharpCode.Decompiler/Util/CollectionExtensions.cs +++ b/ICSharpCode.Decompiler/Util/CollectionExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; namespace ICSharpCode.Decompiler.Util @@ -137,6 +138,20 @@ namespace ICSharpCode.Decompiler.Util return result; } + /// + /// Equivalent to collection.Select(func).ToImmutableArray(), but more efficient as it makes + /// use of the input collection's known size. + /// + public static ImmutableArray SelectImmutableArray(this IReadOnlyCollection collection, Func func) + { + var builder = ImmutableArray.CreateBuilder(collection.Count); + foreach (var element in collection) + { + builder.Add(func(element)); + } + return builder.MoveToImmutable(); + } + /// /// Equivalent to collection.Select(func).ToArray(), but more efficient as it makes /// use of the input collection's known size. From b8796908f621af975f0cd00a2b2d7be7c92c59e8 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 6 Sep 2020 18:58:53 +0200 Subject: [PATCH 4/5] Fix some type inference issues with pointer types. --- .../TestCases/Pretty/FunctionPointers.cs | 43 +++++++++++++++++ .../CSharp/RequiredNamespaceCollector.cs | 7 +++ .../CSharp/Resolver/CSharpConversions.cs | 21 ++++++++ .../CSharp/Resolver/TypeInference.cs | 48 +++++++++++++++++++ .../CSharp/TranslatedExpression.cs | 4 +- 5 files changed, 121 insertions(+), 2 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs index c3f05a953..16eee5f60 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs @@ -1,4 +1,5 @@ using System; +using System.Text; namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { @@ -25,6 +26,28 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { return (delegate*)(&Overloaded); } + + public static string VarianceTest(object o) + { + return null; + } + + public unsafe delegate* Variance() + { + return (delegate*)(&VarianceTest); + } + +#if TODO + public unsafe delegate* AddressOfLocalFunction() + { + return &LocalFunction; + + static void LocalFunction() + { + + } + } +#endif } internal class FunctionPointersWithDynamicTypes @@ -94,4 +117,24 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty // public delegate* unmanaged[Cdecl] cdecl; //} + internal class FunctionPointerTypeInference + { + private static char Test(int i) + { + return (char)i; + } + + public unsafe R GenericMethod(delegate* f, T arg) + { + return f(arg); + } + + public unsafe void Call() + { + delegate* f = &Test; + GenericMethod(f, 0); + GenericMethod((delegate*)(&Test), 1); + GenericMethod(null, 2); + } + } } diff --git a/ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs b/ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs index b8428fa7f..6c4aa4acd 100644 --- a/ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs +++ b/ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs @@ -180,6 +180,13 @@ namespace ICSharpCode.Decompiler.CSharp CollectNamespacesForTypeReference(elementType); } break; + case FunctionPointerType fnPtrType: + CollectNamespacesForTypeReference(fnPtrType.ReturnType); + foreach (var paramType in fnPtrType.ParameterTypes) + { + CollectNamespacesForTypeReference(paramType); + } + break; default: namespaces.Add(type.Namespace); break; diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs index 403fb08e7..fd099d0e2 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs @@ -874,6 +874,27 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver return true; if (fromType.Kind == TypeKind.Null && toType.Kind.IsAnyPointer()) return true; + if (fromType is FunctionPointerType fromFnPtr && toType is FunctionPointerType toFnPtr + && fromFnPtr.CallingConvention == toFnPtr.CallingConvention + && fromFnPtr.ParameterTypes.Length == toFnPtr.ParameterTypes.Length) + { + // Variance applies to function pointer types + const int nestingDepth = 0; + if (!(IdentityConversion(fromFnPtr.ReturnType, toFnPtr.ReturnType) + || ImplicitReferenceConversion(fromFnPtr.ReturnType, toFnPtr.ReturnType, nestingDepth))) + { + return false; + } + foreach (var (fromPT, toPT) in fromFnPtr.ParameterTypes.Zip(toFnPtr.ParameterTypes)) + { + if (!(IdentityConversion(toPT, fromPT) + || ImplicitReferenceConversion(toPT, fromPT, nestingDepth))) + { + return false; + } + } + return true; + } return false; } diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/TypeInference.cs b/ICSharpCode.Decompiler/CSharp/Resolver/TypeInference.cs index 28afb46a5..5af195db5 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/TypeInference.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/TypeInference.cs @@ -676,6 +676,22 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver MakeExactInference(pU.GetTypeArgument(i), pV.GetTypeArgument(i)); } Log.Unindent(); + return; + } + // Handle pointer types: + if (U is PointerType ptrU && V is PointerType ptrV) + { + MakeExactInference(ptrU.ElementType, ptrV.ElementType); + return; + } + if (U is FunctionPointerType fnPtrU && V is FunctionPointerType fnPtrV) + { + MakeExactInference(fnPtrU.ReturnType, fnPtrV.ReturnType); + foreach (var (ptU, ptV) in fnPtrU.ParameterTypes.Zip(fnPtrV.ParameterTypes)) + { + MakeExactInference(ptU, ptV); + } + return; } } @@ -792,6 +808,22 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver } } Log.Unindent(); + return; + } + // Handle pointer types: + if (U is PointerType ptrU && V is PointerType ptrV) + { + MakeExactInference(ptrU.ElementType, ptrV.ElementType); + return; + } + if (U is FunctionPointerType fnPtrU && V is FunctionPointerType fnPtrV) + { + MakeLowerBoundInference(fnPtrU.ReturnType, fnPtrV.ReturnType); + foreach (var (ptU, ptV) in fnPtrU.ParameterTypes.Zip(fnPtrV.ParameterTypes)) + { + MakeUpperBoundInference(ptU, ptV); + } + return; } } @@ -897,6 +929,22 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver } } Log.Unindent(); + return; + } + // Handle pointer types: + if (U is PointerType ptrU && V is PointerType ptrV) + { + MakeExactInference(ptrU.ElementType, ptrV.ElementType); + return; + } + if (U is FunctionPointerType fnPtrU && V is FunctionPointerType fnPtrV) + { + MakeUpperBoundInference(fnPtrU.ReturnType, fnPtrV.ReturnType); + foreach (var (ptU, ptV) in fnPtrU.ParameterTypes.Zip(fnPtrV.ParameterTypes)) + { + MakeLowerBoundInference(ptU, ptV); + } + return; } } #endregion diff --git a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs index 6413eeb0b..24c7f2bcd 100644 --- a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs @@ -535,11 +535,11 @@ namespace ICSharpCode.Decompiler.CSharp { return new NullReferenceExpression() .WithILInstruction(this.ILInstructions) - .WithRR(new ConstantResolveResult(targetType, null)); + .WithRR(new ConstantResolveResult(SpecialType.NullType, null)); } return new CastExpression(expressionBuilder.ConvertType(targetType), new NullReferenceExpression()) .WithILInstruction(this.ILInstructions) - .WithRR(new ConstantResolveResult(targetType, null)); + .WithRR(new ConstantResolveResult(SpecialType.NullType, null)); } if (allowImplicitConversion) { From 5c6b9897c103bc052cd15fbae3e8ce74e3b57ffd Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 6 Sep 2020 20:40:02 +0200 Subject: [PATCH 5/5] Avoid redundant casts + adjust NoExtensionMethods ugly test. --- .../TestCases/Pretty/UnsafeCode.cs | 15 +++++++++++++++ .../TestCases/Ugly/NoExtensionMethods.Expected.cs | 8 ++++---- .../CSharp/TranslatedExpression.cs | 8 ++++++-- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.cs index e432bbf6a..37ce5c345 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.cs @@ -509,5 +509,20 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty int num = value.Integers[index]; num.ToString(); } + + private unsafe static void* CastToVoidPtr(IntPtr intptr) + { + return (void*)intptr; + } + + private unsafe static void* CastToVoidPtr(UIntPtr intptr) + { + return (void*)intptr; + } + + private unsafe static void* CastToVoidPtr(int* intptr) + { + return intptr; + } } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Ugly/NoExtensionMethods.Expected.cs b/ICSharpCode.Decompiler.Tests/TestCases/Ugly/NoExtensionMethods.Expected.cs index 72d6c3b3e..8d74f61ea 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Ugly/NoExtensionMethods.Expected.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Ugly/NoExtensionMethods.Expected.cs @@ -9,9 +9,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Ugly internal static class NoExtensionMethods { [Extension] - internal static Func AsFunc(T value) where T : class + internal unsafe static Func AsFunc(T value) where T : class { - return new Func(value, __ldftn(Return)); + return new Func(value, (nint)(delegate*)(&Return)); } [Extension] @@ -25,9 +25,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Ugly return Return; } - internal static Func ExtensionMethodBoundToNull() + internal unsafe static Func ExtensionMethodBoundToNull() { - return new Func(null, __ldftn(Return)); + return new Func(null, (nint)(delegate*)(&Return)); } } } diff --git a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs index 24c7f2bcd..41900b0e6 100644 --- a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs @@ -328,7 +328,9 @@ namespace ICSharpCode.Decompiler.CSharp // Direct cast only works correctly for IntPtr -> long. // IntPtr -> int works correctly only in checked context. // Everything else can be worked around by casting via long. - if (!(targetType.IsKnownType(KnownTypeCode.Int64) || targetType.Kind == TypeKind.NInt || checkForOverflow && targetType.IsKnownType(KnownTypeCode.Int32))) + if (!(targetType.IsKnownType(KnownTypeCode.Int64) || targetType.Kind == TypeKind.NInt + || (checkForOverflow && targetType.IsKnownType(KnownTypeCode.Int32)) + || targetType.Kind.IsAnyPointer())) { var convertVia = expressionBuilder.settings.NativeIntegers ? SpecialType.NInt : compilation.FindType(KnownTypeCode.Int64); return this.ConvertTo(convertVia, expressionBuilder, checkForOverflow) @@ -340,7 +342,9 @@ namespace ICSharpCode.Decompiler.CSharp // Direct cast only works correctly for UIntPtr -> ulong. // UIntPtr -> uint works correctly only in checked context. // Everything else can be worked around by casting via ulong. - if (!(targetType.IsKnownType(KnownTypeCode.UInt64) || targetType.Kind == TypeKind.NUInt || checkForOverflow && targetType.IsKnownType(KnownTypeCode.UInt32))) + if (!(targetType.IsKnownType(KnownTypeCode.UInt64) || targetType.Kind == TypeKind.NUInt + || (checkForOverflow && targetType.IsKnownType(KnownTypeCode.UInt32)) + || targetType.Kind.IsAnyPointer())) { var convertVia = expressionBuilder.settings.NativeIntegers ? SpecialType.NUInt : compilation.FindType(KnownTypeCode.UInt64); return this.ConvertTo(convertVia, expressionBuilder, checkForOverflow)