From cabb02b5fd649a918992685ac38a53c354ca2b77 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 5 Sep 2020 11:30:51 +0200 Subject: [PATCH] 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