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.