Browse Source

Merge pull request #2150 from icsharpcode/fnptr

Support for C# 9 function pointers
pull/2160/head
Daniel Grunwald 6 years ago committed by GitHub
parent
commit
9d590e4c1e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  2. 6
      ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
  3. 20
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicTests.cs
  4. 140
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs
  5. 15
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.cs
  6. 8
      ICSharpCode.Decompiler.Tests/TestCases/Ugly/NoExtensionMethods.Expected.cs
  7. 10
      ICSharpCode.Decompiler/CSharp/CallBuilder.cs
  8. 64
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  9. 11
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
  10. 17
      ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs
  11. 31
      ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs
  12. 48
      ICSharpCode.Decompiler/CSharp/Resolver/TypeInference.cs
  13. 6
      ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs
  14. 20
      ICSharpCode.Decompiler/CSharp/Syntax/FunctionPointerAstType.cs
  15. 6
      ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs
  16. 45
      ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
  17. 28
      ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUnsafeModifier.cs
  18. 26
      ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs
  19. 22
      ICSharpCode.Decompiler/DecompilerSettings.cs
  20. 3
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  21. 18
      ICSharpCode.Decompiler/IL/ILReader.cs
  22. 4
      ICSharpCode.Decompiler/IL/ILTypeExtensions.cs
  23. 42
      ICSharpCode.Decompiler/IL/Instructions/CallIndirect.cs
  24. 4
      ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
  25. 39
      ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs
  26. 11
      ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs
  27. 185
      ICSharpCode.Decompiler/TypeSystem/FunctionPointerType.cs
  28. 6
      ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs
  29. 13
      ICSharpCode.Decompiler/TypeSystem/MetadataModule.cs
  30. 5
      ICSharpCode.Decompiler/TypeSystem/TypeKind.cs
  31. 5
      ICSharpCode.Decompiler/TypeSystem/TypeProvider.cs
  32. 11
      ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs
  33. 3
      ICSharpCode.Decompiler/TypeSystem/TypeUtils.cs
  34. 7
      ICSharpCode.Decompiler/TypeSystem/TypeVisitor.cs
  35. 15
      ICSharpCode.Decompiler/Util/CollectionExtensions.cs
  36. 13
      ILSpy/Analyzers/Builtin/TypeUsedByAnalyzer.cs
  37. 19
      ILSpy/Languages/Language.cs
  38. 9
      ILSpy/Properties/Resources.Designer.cs
  39. 3
      ILSpy/Properties/Resources.resx

1
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -95,6 +95,7 @@
<Compile Include="TestAssemblyResolver.cs" /> <Compile Include="TestAssemblyResolver.cs" />
<Compile Include="TestCases\Correctness\DeconstructionTests.cs" /> <Compile Include="TestCases\Correctness\DeconstructionTests.cs" />
<Compile Include="TestCases\Correctness\StringConcat.cs" /> <Compile Include="TestCases\Correctness\StringConcat.cs" />
<None Include="TestCases\Pretty\FunctionPointers.cs" />
<None Include="TestCases\Pretty\UsingVariables.cs" /> <None Include="TestCases\Pretty\UsingVariables.cs" />
<None Include="TestCases\Pretty\AsyncForeach.cs" /> <None Include="TestCases\Pretty\AsyncForeach.cs" />
<Compile Include="TestCases\Pretty\DeconstructionTests.cs" /> <Compile Include="TestCases\Pretty\DeconstructionTests.cs" />

6
ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

@ -372,6 +372,12 @@ namespace ICSharpCode.Decompiler.Tests
RunForLibrary(cscOptions: cscOptions | CompilerOptions.Preview); RunForLibrary(cscOptions: cscOptions | CompilerOptions.Preview);
} }
[Test]
public void FunctionPointers([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions)
{
RunForLibrary(cscOptions: cscOptions | CompilerOptions.Preview);
}
[Test] [Test]
public void NullPropagation([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions) public void NullPropagation([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions)
{ {

20
ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicTests.cs

@ -487,6 +487,26 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{ {
return (int)(dynamic)o; 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 internal static class Extension

140
ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs

@ -0,0 +1,140 @@
using System;
using System.Text;
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
public class FunctionPointerAddressOf
{
public static void Overloaded()
{
}
public static void Overloaded(int a)
{
}
public unsafe delegate*<void> GetAddress()
{
return &Overloaded;
}
public unsafe IntPtr GetAddressAsIntPtr()
{
return (IntPtr)(delegate*<void>)(&Overloaded);
}
public unsafe void* GetAddressAsVoidPtr()
{
return (delegate*<int, void>)(&Overloaded);
}
public static string VarianceTest(object o)
{
return null;
}
public unsafe delegate*<StringBuilder, object> Variance()
{
return (delegate*<object, string>)(&VarianceTest);
}
#if TODO
public unsafe delegate*<void> AddressOfLocalFunction()
{
return &LocalFunction;
static void LocalFunction()
{
}
}
#endif
}
internal class FunctionPointersWithDynamicTypes
{
public class D<T, U>
{
}
public class A<T>
{
public class B<U>
{
}
}
public unsafe delegate*<dynamic, dynamic, dynamic> F1;
public unsafe delegate*<object, object, dynamic> F2;
public unsafe delegate*<dynamic, object, object> F3;
public unsafe delegate*<object, dynamic, object> F4;
public unsafe delegate*<object, object, object> F5;
public unsafe delegate*<object, object, ref dynamic> F6;
public unsafe delegate*<ref dynamic, object, object> F7;
public unsafe delegate*<object, ref dynamic, object> F8;
public unsafe delegate*<ref object, ref object, dynamic> F9;
public unsafe delegate*<dynamic, ref object, ref object> F10;
public unsafe delegate*<ref object, dynamic, ref object> F11;
public unsafe delegate*<object, ref readonly dynamic> F12;
public unsafe delegate*<in dynamic, object> F13;
public unsafe delegate*<out dynamic, object> F14;
public unsafe D<delegate*<dynamic>[], dynamic> F15;
public unsafe delegate*<A<object>.B<dynamic>> F16;
}
internal class FunctionPointersWithNativeIntegerTypes
{
public unsafe delegate*<nint, nint, nint> F1;
public unsafe delegate*<IntPtr, IntPtr, nint> F2;
public unsafe delegate*<nint, IntPtr, IntPtr> F3;
public unsafe delegate*<IntPtr, nint, IntPtr> F4;
public unsafe delegate*<delegate*<IntPtr, IntPtr, IntPtr>, nint> F5;
public unsafe delegate*<nint, delegate*<IntPtr, IntPtr, IntPtr>> F6;
public unsafe delegate*<delegate*<IntPtr, IntPtr, nint>, IntPtr> F7;
public unsafe delegate*<IntPtr, delegate*<IntPtr, nint, IntPtr>> F8;
}
internal class FunctionPointersWithRefParams
{
public unsafe delegate*<in byte, ref char, out float, ref readonly int> F1;
public unsafe delegate*<ref char, out float, ref int> 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
//internal unsafe class FunctionPointersWithCallingConvention
//{
// public delegate*<void> managed;
// public delegate* unmanaged<void> unmanaged;
// public delegate* unmanaged[Cdecl]<void> cdecl;
//}
internal class FunctionPointerTypeInference
{
private static char Test(int i)
{
return (char)i;
}
public unsafe R GenericMethod<T, R>(delegate*<T, R> f, T arg)
{
return f(arg);
}
public unsafe void Call()
{
delegate*<int, char> f = &Test;
GenericMethod(f, 0);
GenericMethod((delegate*<int, char>)(&Test), 1);
GenericMethod<int, char>(null, 2);
}
}
}

15
ICSharpCode.Decompiler.Tests/TestCases/Pretty/UnsafeCode.cs

@ -509,5 +509,20 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
int num = value.Integers[index]; int num = value.Integers[index];
num.ToString(); 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;
}
} }
} }

8
ICSharpCode.Decompiler.Tests/TestCases/Ugly/NoExtensionMethods.Expected.cs

@ -9,9 +9,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Ugly
internal static class NoExtensionMethods internal static class NoExtensionMethods
{ {
[Extension] [Extension]
internal static Func<T> AsFunc<T>(T value) where T : class internal unsafe static Func<T> AsFunc<T>(T value) where T : class
{ {
return new Func<T>(value, __ldftn(Return)); return new Func<T>(value, (nint)(delegate*<T, T>)(&Return));
} }
[Extension] [Extension]
@ -25,9 +25,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Ugly
return Return; return Return;
} }
internal static Func<object> ExtensionMethodBoundToNull() internal unsafe static Func<object> ExtensionMethodBoundToNull()
{ {
return new Func<object>(null, __ldftn(Return)); return new Func<object>(null, (nint)(delegate*<object, object>)(&Return));
} }
} }
} }

10
ICSharpCode.Decompiler/CSharp/CallBuilder.cs

@ -1197,6 +1197,16 @@ namespace ICSharpCode.Decompiler.CSharp
{ {
IType argType = NullableType.GetUnderlyingType(arguments[0].Type); IType argType = NullableType.GetUnderlyingType(arguments[0].Type);
operatorCandidates = resolver.GetUserDefinedOperatorCandidates(argType, method.Name); 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<IParameterizedMember>(operatorCandidates);
IType targetType = NullableType.GetUnderlyingType(method.ReturnType);
hashSet.UnionWith(
resolver.GetUserDefinedOperatorCandidates(targetType, method.Name)
);
operatorCandidates = hashSet;
}
} }
else if (arguments.Length == 2) else if (arguments.Length == 2)
{ {

64
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 // 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 // software and associated documentation files (the "Software"), to deal in the Software
@ -20,6 +20,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Reflection.Metadata;
using System.Threading; using System.Threading;
using ICSharpCode.Decompiler.CSharp.Resolver; using ICSharpCode.Decompiler.CSharp.Resolver;
@ -3902,16 +3903,38 @@ namespace ICSharpCode.Decompiler.CSharp
protected internal override TranslatedExpression VisitLdFtn(LdFtn inst, TranslationContext context) protected internal override TranslatedExpression VisitLdFtn(LdFtn inst, TranslationContext context)
{ {
ExpressionWithResolveResult delegateRef = new CallBuilder(this, typeSystem, settings).BuildMethodReference(inst.Method, isVirtual: false); ExpressionWithResolveResult delegateRef = new CallBuilder(this, typeSystem, settings).BuildMethodReference(inst.Method, isVirtual: false);
if (!inst.Method.IsStatic)
{
// C# 9 function pointers don't support instance methods
return new InvocationExpression(new IdentifierExpression("__ldftn"), delegateRef) return new InvocationExpression(new IdentifierExpression("__ldftn"), delegateRef)
.WithRR(new ResolveResult(compilation.FindType(KnownTypeCode.IntPtr))) .WithRR(new ResolveResult(new PointerType(compilation.FindType(KnownTypeCode.Void))))
.WithILInstruction(inst); .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) 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); ExpressionWithResolveResult delegateRef = new CallBuilder(this, typeSystem, settings).BuildMethodReference(inst.Method, isVirtual: true);
return new InvocationExpression(new IdentifierExpression("__ldvirtftn"), delegateRef) 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); .WithILInstruction(inst);
} }
@ -3921,23 +3944,36 @@ namespace ICSharpCode.Decompiler.CSharp
{ {
return ErrorExpression("calli with instance method signature not supportd"); return ErrorExpression("calli with instance method signature not supportd");
} }
var ty = new FunctionPointerType();
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)))
{
var arg = Translate(argInst, typeHint: paramType).ConvertTo(paramType, this, allowImplicitConversion: true);
if (paramRefKind != ReferenceKind.None)
{ {
ty.CallingConvention = inst.CallingConvention.ToString().ToLowerInvariant(); arg = ChangeDirectionExpressionTo(arg, paramRefKind);
} }
foreach (var parameterType in inst.ParameterTypes) invocation.Arguments.Add(arg);
}
if (fpt.ReturnType.SkipModifiers() is ByReferenceType brt)
{ {
ty.TypeArguments.Add(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.TypeArguments.Add(astBuilder.ConvertType(inst.ReturnType)); else
var functionPointer = Translate(inst.FunctionPointer);
var invocation = new InvocationExpression(new CastExpression(ty, functionPointer));
foreach (var (arg, paramType) in inst.Arguments.Zip(inst.ParameterTypes))
{ {
invocation.Arguments.Add(Translate(arg, typeHint: paramType).ConvertTo(paramType, this, allowImplicitConversion: true)); return invocation.WithRR(new ResolveResult(fpt.ReturnType)).WithILInstruction(inst);
} }
return invocation.WithRR(new ResolveResult(inst.ReturnType)).WithILInstruction(inst);
} }
protected internal override TranslatedExpression VisitDeconstructInstruction(DeconstructInstruction inst, TranslationContext context) protected internal override TranslatedExpression VisitDeconstructInstruction(DeconstructInstruction inst, TranslationContext context)

11
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 // 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 // software and associated documentation files (the "Software"), to deal in the Software
@ -2600,17 +2600,20 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
EndNode(tupleTypeElement); EndNode(tupleTypeElement);
} }
public virtual void VisitFunctionPointerType(FunctionPointerType functionPointerType) public virtual void VisitFunctionPointerType(FunctionPointerAstType functionPointerType)
{ {
StartNode(functionPointerType); StartNode(functionPointerType);
WriteKeyword(Roles.DelegateKeyword); WriteKeyword(Roles.DelegateKeyword);
WriteToken(FunctionPointerType.PointerRole); WriteToken(FunctionPointerAstType.PointerRole);
if (!functionPointerType.CallingConventionIdentifier.IsNull) if (!functionPointerType.CallingConventionIdentifier.IsNull)
{ {
Space(); Space();
WriteIdentifier(functionPointerType.CallingConventionIdentifier); WriteIdentifier(functionPointerType.CallingConventionIdentifier);
} }
WriteTypeArguments(functionPointerType.TypeArguments); WriteToken(Roles.LChevron);
WriteCommaSeparatedList(
functionPointerType.Parameters.Concat<AstNode>(new[] { functionPointerType.ReturnType }));
WriteToken(Roles.RChevron);
EndNode(functionPointerType); EndNode(functionPointerType);
} }

17
ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs

@ -180,6 +180,13 @@ namespace ICSharpCode.Decompiler.CSharp
CollectNamespacesForTypeReference(elementType); CollectNamespacesForTypeReference(elementType);
} }
break; break;
case FunctionPointerType fnPtrType:
CollectNamespacesForTypeReference(fnPtrType.ReturnType);
foreach (var paramType in fnPtrType.ParameterTypes)
{
CollectNamespacesForTypeReference(paramType);
}
break;
default: default:
namespaces.Add(type.Namespace); namespaces.Add(type.Namespace);
break; break;
@ -341,20 +348,16 @@ namespace ICSharpCode.Decompiler.CSharp
} }
if (sig.GetKind() == StandaloneSignatureKind.Method) if (sig.GetKind() == StandaloneSignatureKind.Method)
{ {
MethodSignature<IType> methodSig; FunctionPointerType fpt;
try try
{ {
methodSig = module.DecodeMethodSignature((StandaloneSignatureHandle)handle, genericContext); (_, fpt) = module.DecodeMethodSignature((StandaloneSignatureHandle)handle, genericContext);
} }
catch (BadImageFormatException) catch (BadImageFormatException)
{ {
break; break;
} }
CollectNamespacesForTypeReference(methodSig.ReturnType); CollectNamespacesForTypeReference(fpt);
foreach (var paramType in methodSig.ParameterTypes)
{
CollectNamespacesForTypeReference(paramType);
}
} }
break; break;
} }

31
ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs

@ -870,23 +870,44 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
bool ImplicitPointerConversion(IType fromType, IType toType) bool ImplicitPointerConversion(IType fromType, IType toType)
{ {
// C# 4.0 spec: §18.4 Pointer conversions // 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; return true;
if (fromType.Kind == TypeKind.Null && toType is PointerType) if (fromType.Kind == TypeKind.Null && toType.Kind.IsAnyPointer())
return true; 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; return false;
} }
bool ExplicitPointerConversion(IType fromType, IType toType) bool ExplicitPointerConversion(IType fromType, IType toType)
{ {
// C# 4.0 spec: §18.4 Pointer conversions // 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 else
{ {
return toType.Kind == TypeKind.Pointer && IsIntegerType(fromType); return toType.Kind.IsAnyPointer() && IsIntegerType(fromType);
} }
} }

48
ICSharpCode.Decompiler/CSharp/Resolver/TypeInference.cs

@ -676,6 +676,22 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
MakeExactInference(pU.GetTypeArgument(i), pV.GetTypeArgument(i)); MakeExactInference(pU.GetTypeArgument(i), pV.GetTypeArgument(i));
} }
Log.Unindent(); 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(); 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(); 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 #endregion

6
ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs

@ -112,7 +112,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
VisitChildren(tupleTypeElement); VisitChildren(tupleTypeElement);
} }
public virtual void VisitFunctionPointerType(FunctionPointerType functionPointerType) public virtual void VisitFunctionPointerType(FunctionPointerAstType functionPointerType)
{ {
VisitChildren(functionPointerType); VisitChildren(functionPointerType);
} }
@ -780,7 +780,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return VisitChildren(tupleTypeElement); return VisitChildren(tupleTypeElement);
} }
public virtual T VisitFunctionPointerType(FunctionPointerType functionPointerType) public virtual T VisitFunctionPointerType(FunctionPointerAstType functionPointerType)
{ {
return VisitChildren(functionPointerType); return VisitChildren(functionPointerType);
} }
@ -1448,7 +1448,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return VisitChildren(tupleTypeElement, data); return VisitChildren(tupleTypeElement, data);
} }
public virtual S VisitFunctionPointerType(FunctionPointerType functionPointerType, T data) public virtual S VisitFunctionPointerType(FunctionPointerAstType functionPointerType, T data)
{ {
return VisitChildren(functionPointerType, data); return VisitChildren(functionPointerType, data);
} }

20
ICSharpCode.Decompiler/CSharp/Syntax/FunctionPointerType.cs → ICSharpCode.Decompiler/CSharp/Syntax/FunctionPointerAstType.cs

@ -25,16 +25,13 @@
// THE SOFTWARE. // THE SOFTWARE.
using System; using System;
using System.Collections.Generic;
using ICSharpCode.Decompiler.CSharp.Resolver; using ICSharpCode.Decompiler.CSharp.Resolver;
using ICSharpCode.Decompiler.CSharp.TypeSystem;
using ICSharpCode.Decompiler.IL;
using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem;
namespace ICSharpCode.Decompiler.CSharp.Syntax namespace ICSharpCode.Decompiler.CSharp.Syntax
{ {
public class FunctionPointerType : AstType public class FunctionPointerAstType : AstType
{ {
public static readonly TokenRole PointerRole = new TokenRole("*"); public static readonly TokenRole PointerRole = new TokenRole("*");
public static readonly Role<Identifier> CallingConventionRole = new Role<Identifier>("Target", Identifier.Null); public static readonly Role<Identifier> CallingConventionRole = new Role<Identifier>("Target", Identifier.Null);
@ -50,8 +47,13 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
public Identifier CallingConventionIdentifier => GetChildByRole(CallingConventionRole); public Identifier CallingConventionIdentifier => GetChildByRole(CallingConventionRole);
public AstNodeCollection<AstType> TypeArguments { public AstNodeCollection<ParameterDeclaration> Parameters {
get { return GetChildrenByRole(Roles.TypeArgument); } get { return GetChildrenByRole(Roles.Parameter); }
}
public AstType ReturnType {
get { return GetChildByRole(Roles.Type); }
set { SetChildByRole(Roles.Type, value); }
} }
public override void AcceptVisitor(IAstVisitor visitor) 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) protected internal override bool DoMatch(AstNode other, PatternMatching.Match match)
{ {
return other is FunctionPointerType o && MatchString(this.CallingConvention, o.CallingConvention) return other is FunctionPointerAstType o && MatchString(this.CallingConvention, o.CallingConvention)
&& this.TypeArguments.DoMatch(o.TypeArguments, match); && this.Parameters.DoMatch(o.Parameters, match)
&& this.ReturnType.DoMatch(o.ReturnType, match);
} }
public override ITypeReference ToTypeReference(NameLookupMode lookupMode, InterningProvider interningProvider = null) public override ITypeReference ToTypeReference(NameLookupMode lookupMode, InterningProvider interningProvider = null)
@ -81,4 +84,3 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
} }
} }
} }

6
ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs

@ -140,7 +140,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
void VisitMemberType(MemberType memberType); void VisitMemberType(MemberType memberType);
void VisitTupleType(TupleAstType tupleType); void VisitTupleType(TupleAstType tupleType);
void VisitTupleTypeElement(TupleTypeElement tupleTypeElement); void VisitTupleTypeElement(TupleTypeElement tupleTypeElement);
void VisitFunctionPointerType(FunctionPointerType functionPointerType); void VisitFunctionPointerType(FunctionPointerAstType functionPointerType);
void VisitComposedType(ComposedType composedType); void VisitComposedType(ComposedType composedType);
void VisitArraySpecifier(ArraySpecifier arraySpecifier); void VisitArraySpecifier(ArraySpecifier arraySpecifier);
void VisitPrimitiveType(PrimitiveType primitiveType); void VisitPrimitiveType(PrimitiveType primitiveType);
@ -286,7 +286,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
S VisitMemberType(MemberType memberType); S VisitMemberType(MemberType memberType);
S VisitTupleType(TupleAstType tupleType); S VisitTupleType(TupleAstType tupleType);
S VisitTupleTypeElement(TupleTypeElement tupleTypeElement); S VisitTupleTypeElement(TupleTypeElement tupleTypeElement);
S VisitFunctionPointerType(FunctionPointerType functionPointerType); S VisitFunctionPointerType(FunctionPointerAstType functionPointerType);
S VisitComposedType(ComposedType composedType); S VisitComposedType(ComposedType composedType);
S VisitArraySpecifier(ArraySpecifier arraySpecifier); S VisitArraySpecifier(ArraySpecifier arraySpecifier);
S VisitPrimitiveType(PrimitiveType primitiveType); S VisitPrimitiveType(PrimitiveType primitiveType);
@ -432,7 +432,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
S VisitMemberType(MemberType memberType, T data); S VisitMemberType(MemberType memberType, T data);
S VisitTupleType(TupleAstType tupleType, T data); S VisitTupleType(TupleAstType tupleType, T data);
S VisitTupleTypeElement(TupleTypeElement tupleTypeElement, 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 VisitComposedType(ComposedType composedType, T data);
S VisitArraySpecifier(ArraySpecifier arraySpecifier, T data); S VisitArraySpecifier(ArraySpecifier arraySpecifier, T data);
S VisitPrimitiveType(PrimitiveType primitiveType, T data); S VisitPrimitiveType(PrimitiveType primitiveType, T data);

45
ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs

@ -306,6 +306,51 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
} }
return astType; return astType;
} }
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();
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;
}
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 else
{ {
AstType astType; AstType astType;

28
ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUnsafeModifier.cs

@ -77,7 +77,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
return base.VisitComposedType(composedType); return base.VisitComposedType(composedType);
} }
public override bool VisitFunctionPointerType(FunctionPointerType functionPointerType) public override bool VisitFunctionPointerType(FunctionPointerAstType functionPointerType)
{ {
return true; return true;
} }
@ -130,12 +130,12 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
var rr = memberReferenceExpression.GetResolveResult(); var rr = memberReferenceExpression.GetResolveResult();
if (rr != null) if (rr != null)
{ {
if (rr.Type is PointerType) if (IsPointer(rr.Type))
return true; return true;
if (rr is MemberResolveResult mrr && mrr.Member.ReturnType.Kind == TypeKind.Delegate) if (rr is MemberResolveResult mrr && mrr.Member.ReturnType.Kind == TypeKind.Delegate)
{ {
var method = mrr.Member.ReturnType.GetDefinition()?.GetDelegateInvokeMethod(); 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; return true;
} }
} }
@ -149,12 +149,12 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
var rr = identifierExpression.GetResolveResult(); var rr = identifierExpression.GetResolveResult();
if (rr != null) if (rr != null)
{ {
if (rr.Type is PointerType) if (IsPointer(rr.Type))
return true; return true;
if (rr is MemberResolveResult mrr && mrr.Member.ReturnType.Kind == TypeKind.Delegate) if (rr is MemberResolveResult mrr && mrr.Member.ReturnType.Kind == TypeKind.Delegate)
{ {
var method = mrr.Member.ReturnType.GetDefinition()?.GetDelegateInvokeMethod(); 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; return true;
} }
} }
@ -166,7 +166,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
{ {
bool result = base.VisitStackAllocExpression(stackAllocExpression); bool result = base.VisitStackAllocExpression(stackAllocExpression);
var rr = stackAllocExpression.GetResolveResult(); var rr = stackAllocExpression.GetResolveResult();
if (rr?.Type is PointerType) if (IsPointer(rr?.Type))
return true; return true;
return result; return result;
} }
@ -175,7 +175,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
{ {
bool result = base.VisitInvocationExpression(invocationExpression); bool result = base.VisitInvocationExpression(invocationExpression);
var rr = invocationExpression.GetResolveResult(); var rr = invocationExpression.GetResolveResult();
if (rr != null && rr.Type is PointerType) if (IsPointer(rr?.Type))
return true; return true;
return result; return result;
} }
@ -185,5 +185,19 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
base.VisitFixedVariableInitializer(fixedVariableInitializer); base.VisitFixedVariableInitializer(fixedVariableInitializer);
return true; 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;
}
}
} }
} }

26
ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs

@ -328,7 +328,9 @@ namespace ICSharpCode.Decompiler.CSharp
// Direct cast only works correctly for IntPtr -> long. // Direct cast only works correctly for IntPtr -> long.
// IntPtr -> int works correctly only in checked context. // IntPtr -> int works correctly only in checked context.
// Everything else can be worked around by casting via long. // 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); var convertVia = expressionBuilder.settings.NativeIntegers ? SpecialType.NInt : compilation.FindType(KnownTypeCode.Int64);
return this.ConvertTo(convertVia, expressionBuilder, checkForOverflow) return this.ConvertTo(convertVia, expressionBuilder, checkForOverflow)
@ -340,7 +342,9 @@ namespace ICSharpCode.Decompiler.CSharp
// Direct cast only works correctly for UIntPtr -> ulong. // Direct cast only works correctly for UIntPtr -> ulong.
// UIntPtr -> uint works correctly only in checked context. // UIntPtr -> uint works correctly only in checked context.
// Everything else can be worked around by casting via ulong. // 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); var convertVia = expressionBuilder.settings.NativeIntegers ? SpecialType.NUInt : compilation.FindType(KnownTypeCode.UInt64);
return this.ConvertTo(convertVia, expressionBuilder, checkForOverflow) return this.ConvertTo(convertVia, expressionBuilder, checkForOverflow)
@ -370,7 +374,7 @@ namespace ICSharpCode.Decompiler.CSharp
.ConvertTo(targetType, expressionBuilder, checkForOverflow); .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 // 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. // without throwing an exception in 32-bit mode is to use a pointer type.
@ -380,7 +384,7 @@ namespace ICSharpCode.Decompiler.CSharp
} }
else if (targetUType.IsKnownType(KnownTypeCode.UIntPtr)) else if (targetUType.IsKnownType(KnownTypeCode.UIntPtr))
{ // Conversion to 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) // normal casts work for uint/nuint and pointers (both in checked and unchecked context)
} }
@ -409,22 +413,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 // enum to pointer: C# doesn't allow such casts
// -> convert via underlying type // -> convert via underlying type
return this.ConvertTo(type.GetEnumUnderlyingType(), expressionBuilder, checkForOverflow) return this.ConvertTo(type.GetEnumUnderlyingType(), expressionBuilder, checkForOverflow)
.ConvertTo(targetType, 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 // pointer to enum: C# doesn't allow such casts
// -> convert via underlying type // -> convert via underlying type
return this.ConvertTo(targetUType.GetEnumUnderlyingType(), expressionBuilder, checkForOverflow) return this.ConvertTo(targetUType.GetEnumUnderlyingType(), expressionBuilder, checkForOverflow)
.ConvertTo(targetType, expressionBuilder, checkForOverflow); .ConvertTo(targetType, expressionBuilder, checkForOverflow);
} }
if (targetType.Kind == TypeKind.Pointer && type.IsKnownType(KnownTypeCode.Char) if (targetType.Kind.IsAnyPointer() && type.IsKnownType(KnownTypeCode.Char)
|| targetUType.IsKnownType(KnownTypeCode.Char) && type.Kind == TypeKind.Pointer) || targetUType.IsKnownType(KnownTypeCode.Char) && type.Kind.IsAnyPointer())
{ {
// char <-> pointer: C# doesn't allow such casts // char <-> pointer: C# doesn't allow such casts
// -> convert via ushort // -> convert via ushort
@ -529,17 +533,17 @@ namespace ICSharpCode.Decompiler.CSharp
.ConvertTo(targetType, expressionBuilder, checkForOverflow, allowImplicitConversion); .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) if (allowImplicitConversion)
{ {
return new NullReferenceExpression() return new NullReferenceExpression()
.WithILInstruction(this.ILInstructions) .WithILInstruction(this.ILInstructions)
.WithRR(new ConstantResolveResult(targetType, null)); .WithRR(new ConstantResolveResult(SpecialType.NullType, null));
} }
return new CastExpression(expressionBuilder.ConvertType(targetType), new NullReferenceExpression()) return new CastExpression(expressionBuilder.ConvertType(targetType), new NullReferenceExpression())
.WithILInstruction(this.ILInstructions) .WithILInstruction(this.ILInstructions)
.WithRR(new ConstantResolveResult(targetType, null)); .WithRR(new ConstantResolveResult(SpecialType.NullType, null));
} }
if (allowImplicitConversion) if (allowImplicitConversion)
{ {

22
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -133,12 +133,13 @@ namespace ICSharpCode.Decompiler
{ {
nativeIntegers = false; nativeIntegers = false;
initAccessors = false; initAccessors = false;
functionPointers = false;
} }
} }
public CSharp.LanguageVersion GetMinimumRequiredVersion() public CSharp.LanguageVersion GetMinimumRequiredVersion()
{ {
if (nativeIntegers || initAccessors) if (nativeIntegers || initAccessors || functionPointers)
return CSharp.LanguageVersion.Preview; return CSharp.LanguageVersion.Preview;
if (nullableReferenceTypes || readOnlyMethods || asyncEnumerator || asyncUsingAndForEachStatement if (nullableReferenceTypes || readOnlyMethods || asyncEnumerator || asyncUsingAndForEachStatement
|| staticLocalFunctions || ranges || switchExpressions) || staticLocalFunctions || ranges || switchExpressions)
@ -204,6 +205,25 @@ namespace ICSharpCode.Decompiler
} }
} }
bool functionPointers = true;
/// <summary>
/// Use C# 9 <c>delegate* unmanaged</c> types.
/// If this option is disabled, function pointers will instead be decompiled with type `IntPtr`.
/// </summary>
[Category("C# 9.0 (experimental)")]
[Description("DecompilerSettings.FunctionPointers")]
public bool FunctionPointers {
get { return functionPointers; }
set {
if (functionPointers != value)
{
functionPointers = value;
OnPropertyChanged();
}
}
}
bool switchExpressions = true; bool switchExpressions = true;
/// <summary> /// <summary>

3
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -72,7 +72,7 @@
<Compile Include="CSharp\ProjectDecompiler\TargetServices.cs" /> <Compile Include="CSharp\ProjectDecompiler\TargetServices.cs" />
<Compile Include="CSharp\Syntax\Expressions\DeclarationExpression.cs" /> <Compile Include="CSharp\Syntax\Expressions\DeclarationExpression.cs" />
<Compile Include="CSharp\Syntax\Expressions\SwitchExpression.cs" /> <Compile Include="CSharp\Syntax\Expressions\SwitchExpression.cs" />
<Compile Include="CSharp\Syntax\FunctionPointerType.cs" /> <Compile Include="CSharp\Syntax\FunctionPointerAstType.cs" />
<Compile Include="CSharp\Syntax\VariableDesignation.cs" /> <Compile Include="CSharp\Syntax\VariableDesignation.cs" />
<Compile Include="IL\Transforms\DeconstructionTransform.cs" /> <Compile Include="IL\Transforms\DeconstructionTransform.cs" />
<Compile Include="IL\Transforms\FixLoneIsInst.cs" /> <Compile Include="IL\Transforms\FixLoneIsInst.cs" />
@ -250,6 +250,7 @@
<Compile Include="CSharp\Resolver\OverloadResolutionErrors.cs" /> <Compile Include="CSharp\Resolver\OverloadResolutionErrors.cs" />
<Compile Include="IL\Transforms\FixRemainingIncrements.cs" /> <Compile Include="IL\Transforms\FixRemainingIncrements.cs" />
<Compile Include="IL\Transforms\ILExtraction.cs" /> <Compile Include="IL\Transforms\ILExtraction.cs" />
<Compile Include="TypeSystem\FunctionPointerType.cs" />
<Compile Include="TypeSystem\Implementation\LocalFunctionMethod.cs" /> <Compile Include="TypeSystem\Implementation\LocalFunctionMethod.cs" />
<Compile Include="CSharp\Resolver\TypeInference.cs" /> <Compile Include="CSharp\Resolver\TypeInference.cs" />
<Compile Include="CSharp\Transforms\CombineQueryExpressions.cs" /> <Compile Include="CSharp\Transforms\CombineQueryExpressions.cs" />

18
ICSharpCode.Decompiler/IL/ILReader.cs

@ -1566,24 +1566,22 @@ namespace ICSharpCode.Decompiler.IL
ILInstruction DecodeCallIndirect() ILInstruction DecodeCallIndirect()
{ {
var signatureHandle = (StandaloneSignatureHandle)ReadAndDecodeMetadataToken(); var signatureHandle = (StandaloneSignatureHandle)ReadAndDecodeMetadataToken();
var signature = module.DecodeMethodSignature(signatureHandle, genericContext); var (header, fpt) = module.DecodeMethodSignature(signatureHandle, genericContext);
var functionPointer = Pop(StackType.I); var functionPointer = Pop(StackType.I);
int firstArgument = signature.Header.IsInstance ? 1 : 0; int firstArgument = header.IsInstance ? 1 : 0;
var arguments = new ILInstruction[firstArgument + signature.ParameterTypes.Length]; var arguments = new ILInstruction[firstArgument + fpt.ParameterTypes.Length];
for (int i = signature.ParameterTypes.Length - 1; i >= 0; i--) 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) if (firstArgument == 1)
{ {
arguments[0] = Pop(); arguments[0] = Pop();
} }
var call = new CallIndirect( var call = new CallIndirect(
signature.Header.IsInstance, header.IsInstance,
signature.Header.HasExplicitThis, header.HasExplicitThis,
signature.Header.CallingConvention, fpt,
signature.ReturnType,
signature.ParameterTypes,
functionPointer, functionPointer,
arguments arguments
); );

4
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 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System.Reflection.Metadata;
using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem;
namespace ICSharpCode.Decompiler.IL namespace ICSharpCode.Decompiler.IL
@ -163,7 +161,7 @@ namespace ICSharpCode.Decompiler.IL
case CallVirt callVirt: case CallVirt callVirt:
return callVirt.Method.ReturnType; return callVirt.Method.ReturnType;
case CallIndirect calli: case CallIndirect calli:
return calli.ReturnType; return calli.FunctionPointerType.ReturnType;
case UserDefinedLogicOperator logicOp: case UserDefinedLogicOperator logicOp:
return logicOp.Method.ReturnType; return logicOp.Method.ReturnType;
case LdObj ldobj: case LdObj ldobj:

42
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 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
@ -37,17 +35,8 @@ namespace ICSharpCode.Decompiler.IL
public readonly InstructionCollection<ILInstruction> Arguments; public readonly InstructionCollection<ILInstruction> Arguments;
public bool IsInstance { get; } public bool IsInstance { get; }
public bool HasExplicitThis { get; } public bool HasExplicitThis { get; }
public System.Reflection.Metadata.SignatureCallingConvention CallingConvention { get; } public FunctionPointerType FunctionPointerType { get; }
public IType ReturnType { get; }
public ImmutableArray<IType> ParameterTypes { get; }
/// <summary>
/// A 'final instruction' that gets executed after the <c>Instructions</c> collection.
/// Provides the return value for the block.
/// </summary>
/// <remarks>
/// Blocks in containers must have 'Nop' as a final instruction.
/// </remarks>
public ILInstruction FunctionPointer { public ILInstruction FunctionPointer {
get { get {
return functionPointer; return functionPointer;
@ -58,14 +47,12 @@ namespace ICSharpCode.Decompiler.IL
} }
} }
public CallIndirect(bool isInstance, bool hasExplicitThis, System.Reflection.Metadata.SignatureCallingConvention callingConvention, IType returnType, ImmutableArray<IType> parameterTypes, public CallIndirect(bool isInstance, bool hasExplicitThis, FunctionPointerType functionPointerType,
ILInstruction functionPointer, IEnumerable<ILInstruction> arguments) : base(OpCode.CallIndirect) ILInstruction functionPointer, IEnumerable<ILInstruction> arguments) : base(OpCode.CallIndirect)
{ {
this.IsInstance = isInstance; this.IsInstance = isInstance;
this.HasExplicitThis = hasExplicitThis; this.HasExplicitThis = hasExplicitThis;
this.CallingConvention = callingConvention; this.FunctionPointerType = functionPointerType;
this.ReturnType = returnType ?? throw new ArgumentNullException(nameof(returnType));
this.ParameterTypes = parameterTypes.ToImmutableArray();
this.FunctionPointer = functionPointer; this.FunctionPointer = functionPointer;
this.Arguments = new InstructionCollection<ILInstruction>(this, 1); this.Arguments = new InstructionCollection<ILInstruction>(this, 1);
this.Arguments.AddRange(arguments); this.Arguments.AddRange(arguments);
@ -73,24 +60,24 @@ namespace ICSharpCode.Decompiler.IL
public override ILInstruction Clone() 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()) functionPointer.Clone(), this.Arguments.Select(inst => inst.Clone())
).WithILRange(this); ).WithILRange(this);
} }
public override StackType ResultType => ReturnType.GetStackType(); public override StackType ResultType => FunctionPointerType.ReturnType.GetStackType();
internal override void CheckInvariant(ILPhase phase) internal override void CheckInvariant(ILPhase phase)
{ {
base.CheckInvariant(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) public override void WriteTo(ITextOutput output, ILAstWritingOptions options)
{ {
WriteILRange(output, options); WriteILRange(output, options);
output.Write("call.indirect "); output.Write("call.indirect ");
ReturnType.WriteTo(output); FunctionPointerType.ReturnType.WriteTo(output);
output.Write('('); output.Write('(');
functionPointer.WriteTo(output, options); functionPointer.WriteTo(output, options);
int firstArgument = IsInstance ? 1 : 0; int firstArgument = IsInstance ? 1 : 0;
@ -99,7 +86,7 @@ namespace ICSharpCode.Decompiler.IL
output.Write(", "); output.Write(", ");
Arguments[0].WriteTo(output, options); 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(", "); output.Write(", ");
inst.WriteTo(output, options); inst.WriteTo(output, options);
@ -161,16 +148,7 @@ namespace ICSharpCode.Decompiler.IL
return false; return false;
if (HasExplicitThis != other.HasExplicitThis) if (HasExplicitThis != other.HasExplicitThis)
return false; return false;
if (CallingConvention != other.CallingConvention) return FunctionPointerType.Equals(other.FunctionPointerType);
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);
} }
} }
} }

4
ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs

@ -459,6 +459,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return ldloc.Variable.IsRefReadOnly; return ldloc.Variable.IsRefReadOnly;
case Call call: case Call call:
return call.Method.ReturnTypeIsRefReadOnly; return call.Method.ReturnTypeIsRefReadOnly;
case CallIndirect calli:
return calli.FunctionPointerType.ReturnIsRefReadOnly;
case AddressOf _: case AddressOf _:
// C# doesn't allow mutation of value-type temporaries // C# doesn't allow mutation of value-type temporaries
return true; return true;
@ -557,6 +559,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true; return true;
} }
break; break;
case OpCode.CallIndirect when loadInst.SlotInfo == CallIndirect.FunctionPointerSlot:
return true;
case OpCode.LdElema: case OpCode.LdElema:
if (((LdElema)parent).WithSystemIndex) if (((LdElema)parent).WithSystemIndex)
{ {

39
ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs

@ -40,7 +40,8 @@ namespace ICSharpCode.Decompiler.TypeSystem
SRM.MetadataReader metadata, SRM.MetadataReader metadata,
TypeSystemOptions options, TypeSystemOptions options,
Nullability nullableContext, Nullability nullableContext,
bool typeChildrenOnly = false) bool typeChildrenOnly = false,
bool isSignatureReturnType = false)
{ {
bool hasDynamicAttribute = false; bool hasDynamicAttribute = false;
bool[] dynamicAttributeData = null; bool[] dynamicAttributeData = null;
@ -133,6 +134,14 @@ namespace ICSharpCode.Decompiler.TypeSystem
options, tupleElementNames, options, tupleElementNames,
nullability, nullableAttributeData 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) if (typeChildrenOnly)
{ {
return inputType.VisitChildren(visitor); return inputType.VisitChildren(visitor);
@ -309,6 +318,34 @@ namespace ICSharpCode.Decompiler.TypeSystem
return new ParameterizedType(genericType, arguments); 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 => 1,
ReferenceKind.Out => 2, // in/out also count the modreq
ReferenceKind.In => 2,
_ => throw new NotSupportedException()
};
parameters[i] = type.ParameterTypes[i].AcceptVisitor(this);
changed = changed || parameters[i] != type.ParameterTypes[i];
}
if (!changed)
return type;
return type.WithSignature(returnType, parameters.ToImmutableArray());
}
public override IType VisitTypeDefinition(ITypeDefinition type) public override IType VisitTypeDefinition(ITypeDefinition type)
{ {
IType newType = type; IType newType = type;

11
ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs

@ -18,8 +18,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq; using System.Linq;
using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Metadata;
@ -116,11 +114,16 @@ namespace ICSharpCode.Decompiler.TypeSystem
/// </summary> /// </summary>
NativeIntegers = 0x1000, NativeIntegers = 0x1000,
/// <summary> /// <summary>
/// Allow function pointer types. If this option is not enabled, function pointers are
/// replaced with the 'IntPtr' type.
/// </summary>
FunctionPointers = 0x2000,
/// <summary>
/// Default settings: typical options for the decompiler, with all C# languages features enabled. /// Default settings: typical options for the decompiler, with all C# languages features enabled.
/// </summary> /// </summary>
Default = Dynamic | Tuple | ExtensionMethods | DecimalConstants | ReadOnlyStructsAndParameters Default = Dynamic | Tuple | ExtensionMethods | DecimalConstants | ReadOnlyStructsAndParameters
| RefStructs | UnmanagedConstraints | NullabilityAnnotations | ReadOnlyMethods | RefStructs | UnmanagedConstraints | NullabilityAnnotations | ReadOnlyMethods
| NativeIntegers | NativeIntegers | FunctionPointers
} }
/// <summary> /// <summary>
@ -154,6 +157,8 @@ namespace ICSharpCode.Decompiler.TypeSystem
typeSystemOptions |= TypeSystemOptions.ReadOnlyMethods; typeSystemOptions |= TypeSystemOptions.ReadOnlyMethods;
if (settings.NativeIntegers) if (settings.NativeIntegers)
typeSystemOptions |= TypeSystemOptions.NativeIntegers; typeSystemOptions |= TypeSystemOptions.NativeIntegers;
if (settings.FunctionPointers)
typeSystemOptions |= TypeSystemOptions.FunctionPointers;
return typeSystemOptions; return typeSystemOptions;
} }

185
ICSharpCode.Decompiler/TypeSystem/FunctionPointerType.cs

@ -0,0 +1,185 @@
// 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;
using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.Decompiler.TypeSystem
{
public class FunctionPointerType : AbstractType
{
public static FunctionPointerType FromSignature(MethodSignature<IType> 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<IType>(signature.ParameterTypes.Length);
var parameterReferenceKinds = ImmutableArray.CreateBuilder<ReferenceKind>(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<IType> ParameterTypes;
public readonly ImmutableArray<ReferenceKind> ParameterReferenceKinds;
public FunctionPointerType(MetadataModule module, SignatureCallingConvention callingConvention,
IType returnType, bool returnIsRefReadOnly,
ImmutableArray<IType> parameterTypes, ImmutableArray<ReferenceKind> parameterReferenceKinds)
{
this.module = module;
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 => ((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)
{
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(
module, CallingConvention,
r, ReturnIsRefReadOnly,
pt != null ? pt.ToImmutableArray() : ParameterTypes,
ParameterReferenceKinds);
}
public override bool Equals(IType other)
{
return other is FunctionPointerType fpt
&& CallingConvention == fpt.CallingConvention
&& 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, k) in ParameterTypes.Zip(ParameterReferenceKinds))
{
hash ^= p.GetHashCode() ^ k.GetHashCode();
hash *= 8310859;
}
return hash;
}
}
internal IType WithSignature(IType returnType, ImmutableArray<IType> parameterTypes)
{
return new FunctionPointerType(this.module, this.CallingConvention, returnType,
this.ReturnIsRefReadOnly, parameterTypes, this.ParameterReferenceKinds);
}
}
}

6
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 nullableContext = methodDef.GetCustomAttributes().GetNullableContext(module.metadata) ?? DeclaringTypeDefinition.NullableContext;
var signature = methodDef.DecodeSignature(module.TypeProvider, genericContext); 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) catch (BadImageFormatException)
{ {
@ -263,7 +264,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
} }
Debug.Assert(i == parameters.Length); Debug.Assert(i == parameters.Length);
var returnType = ApplyAttributeTypeVisitor.ApplyAttributesToType(signature.ReturnType, 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); return (returnType, parameters, signature.ReturnType as ModifiedType);
} }
#endregion #endregion

13
ICSharpCode.Decompiler/TypeSystem/MetadataModule.cs

@ -689,21 +689,14 @@ namespace ICSharpCode.Decompiler.TypeSystem
#endregion #endregion
#region Decode Standalone Signature #region Decode Standalone Signature
public MethodSignature<IType> DecodeMethodSignature(StandaloneSignatureHandle handle, GenericContext genericContext) public (SignatureHeader, FunctionPointerType) DecodeMethodSignature(StandaloneSignatureHandle handle, GenericContext genericContext)
{ {
var standaloneSignature = metadata.GetStandaloneSignature(handle); var standaloneSignature = metadata.GetStandaloneSignature(handle);
if (standaloneSignature.GetKind() != StandaloneSignatureKind.Method) if (standaloneSignature.GetKind() != StandaloneSignatureKind.Method)
throw new BadImageFormatException("Expected Method signature"); throw new BadImageFormatException("Expected Method signature");
var sig = standaloneSignature.DecodeMethodSignature(TypeProvider, genericContext); var sig = standaloneSignature.DecodeMethodSignature(TypeProvider, genericContext);
return new MethodSignature<IType>( var fpt = FunctionPointerType.FromSignature(sig, this);
sig.Header, return (sig.Header, (FunctionPointerType)IntroduceTupleTypes(fpt));
IntroduceTupleTypes(sig.ReturnType),
sig.RequiredParameterCount,
sig.GenericParameterCount,
ImmutableArray.CreateRange(
sig.ParameterTypes, IntroduceTupleTypes
)
);
} }
public ImmutableArray<IType> DecodeLocalSignature(StandaloneSignatureHandle handle, GenericContext genericContext) public ImmutableArray<IType> DecodeLocalSignature(StandaloneSignatureHandle handle, GenericContext genericContext)

5
ICSharpCode.Decompiler/TypeSystem/TypeKind.cs

@ -103,5 +103,10 @@ namespace ICSharpCode.Decompiler.TypeSystem
/// C# 9 <c>nuint</c> /// C# 9 <c>nuint</c>
/// </summary> /// </summary>
NUInt, NUInt,
/// <summary>
/// C# 9 <c>delegate*</c>
/// </summary>
FunctionPointer,
} }
} }

5
ICSharpCode.Decompiler/TypeSystem/TypeProvider.cs

@ -62,8 +62,13 @@ namespace ICSharpCode.Decompiler.TypeSystem
public IType GetFunctionPointerType(SRM.MethodSignature<IType> signature) public IType GetFunctionPointerType(SRM.MethodSignature<IType> signature)
{ {
if (signature.Header.IsInstance)
{
// pointers to member functions are not supported even in C# 9
return compilation.FindType(KnownTypeCode.IntPtr); return compilation.FindType(KnownTypeCode.IntPtr);
} }
return FunctionPointerType.FromSignature(signature, module);
}
public IType GetGenericInstantiation(IType genericType, ImmutableArray<IType> typeArguments) public IType GetGenericInstantiation(IType genericType, ImmutableArray<IType> typeArguments)
{ {

11
ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs

@ -20,7 +20,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using ICSharpCode.Decompiler.CSharp.Syntax;
using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Semantics; using ICSharpCode.Decompiler.Semantics;
using ICSharpCode.Decompiler.TypeSystem.Implementation; using ICSharpCode.Decompiler.TypeSystem.Implementation;
@ -294,6 +293,16 @@ namespace ICSharpCode.Decompiler.TypeSystem
return accessor.ThisIsRefReadOnly && accessor.DeclaringTypeDefinition?.IsReadOnly == false; 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 #region GetType/Member
/// <summary> /// <summary>
/// Gets all type definitions in the compilation. /// Gets all type definitions in the compilation.

3
ICSharpCode.Decompiler/TypeSystem/TypeUtils.cs

@ -273,6 +273,7 @@ namespace ICSharpCode.Decompiler.TypeSystem
case TypeKind.Pointer: case TypeKind.Pointer:
case TypeKind.NInt: case TypeKind.NInt:
case TypeKind.NUInt: case TypeKind.NUInt:
case TypeKind.FunctionPointer:
return StackType.I; return StackType.I;
case TypeKind.TypeParameter: case TypeKind.TypeParameter:
// Type parameters are always considered StackType.O, even // Type parameters are always considered StackType.O, even
@ -340,6 +341,7 @@ namespace ICSharpCode.Decompiler.TypeSystem
{ {
case TypeKind.Pointer: case TypeKind.Pointer:
case TypeKind.NUInt: case TypeKind.NUInt:
case TypeKind.FunctionPointer:
return Sign.Unsigned; return Sign.Unsigned;
case TypeKind.NInt: case TypeKind.NInt:
return Sign.Signed; return Sign.Signed;
@ -421,6 +423,7 @@ namespace ICSharpCode.Decompiler.TypeSystem
case TypeKind.ByReference: case TypeKind.ByReference:
return PrimitiveType.Ref; return PrimitiveType.Ref;
case TypeKind.NInt: case TypeKind.NInt:
case TypeKind.FunctionPointer:
return PrimitiveType.I; return PrimitiveType.I;
case TypeKind.NUInt: case TypeKind.NUInt:
return PrimitiveType.U; return PrimitiveType.U;

7
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 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System;
using ICSharpCode.Decompiler.TypeSystem.Implementation; using ICSharpCode.Decompiler.TypeSystem.Implementation;
namespace ICSharpCode.Decompiler.TypeSystem namespace ICSharpCode.Decompiler.TypeSystem
@ -81,5 +79,10 @@ namespace ICSharpCode.Decompiler.TypeSystem
{ {
return type.VisitChildren(this); return type.VisitChildren(this);
} }
public virtual IType VisitFunctionPointerType(FunctionPointerType type)
{
return type.VisitChildren(this);
}
} }
} }

15
ICSharpCode.Decompiler/Util/CollectionExtensions.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq; using System.Linq;
namespace ICSharpCode.Decompiler.Util namespace ICSharpCode.Decompiler.Util
@ -137,6 +138,20 @@ namespace ICSharpCode.Decompiler.Util
return result; return result;
} }
/// <summary>
/// Equivalent to <code>collection.Select(func).ToImmutableArray()</code>, but more efficient as it makes
/// use of the input collection's known size.
/// </summary>
public static ImmutableArray<U> SelectImmutableArray<T, U>(this IReadOnlyCollection<T> collection, Func<T, U> func)
{
var builder = ImmutableArray.CreateBuilder<U>(collection.Count);
foreach (var element in collection)
{
builder.Add(func(element));
}
return builder.MoveToImmutable();
}
/// <summary> /// <summary>
/// Equivalent to <code>collection.Select(func).ToArray()</code>, but more efficient as it makes /// Equivalent to <code>collection.Select(func).ToArray()</code>, but more efficient as it makes
/// use of the input collection's known size. /// use of the input collection's known size.

13
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 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
using System.Reflection.Metadata; using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using ICSharpCode.Decompiler; using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Disassembler; using ICSharpCode.Decompiler.Disassembler;
@ -266,13 +262,8 @@ namespace ICSharpCode.ILSpy.Analyzers.Builtin
break; break;
case HandleKind.StandaloneSignature: case HandleKind.StandaloneSignature:
var signature = module.DecodeMethodSignature((StandaloneSignatureHandle)member, genericContext); var (_, fpt) = module.DecodeMethodSignature((StandaloneSignatureHandle)member, genericContext);
foreach (var type in signature.ParameterTypes) fpt.AcceptVisitor(visitor);
{
type.AcceptVisitor(visitor);
}
signature.ReturnType.AcceptVisitor(visitor);
if (visitor.Found) if (visitor.Found)
return; return;

19
ILSpy/Languages/Language.cs

@ -254,6 +254,25 @@ namespace ICSharpCode.ILSpy
return type; 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) public override IType VisitOtherType(IType type)
{ {
WriteType(type); WriteType(type);

9
ILSpy/Properties/Resources.Designer.cs generated

@ -909,6 +909,15 @@ namespace ICSharpCode.ILSpy.Properties {
} }
} }
/// <summary>
/// Looks up a localized string similar to Function pointers.
/// </summary>
public static string DecompilerSettings_FunctionPointers {
get {
return ResourceManager.GetString("DecompilerSettings.FunctionPointers", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Include XML documentation comments in the decompiled code. /// Looks up a localized string similar to Include XML documentation comments in the decompiled code.
/// </summary> /// </summary>

3
ILSpy/Properties/Resources.resx

@ -333,6 +333,9 @@ Are you sure you want to continue?</value>
<data name="DecompilerSettings.ForStatement" xml:space="preserve"> <data name="DecompilerSettings.ForStatement" xml:space="preserve">
<value>Transform to for, if possible</value> <value>Transform to for, if possible</value>
</data> </data>
<data name="DecompilerSettings.FunctionPointers" xml:space="preserve">
<value>Function pointers</value>
</data>
<data name="DecompilerSettings.IncludeXMLDocumentationCommentsInTheDecompiledCode" xml:space="preserve"> <data name="DecompilerSettings.IncludeXMLDocumentationCommentsInTheDecompiledCode" xml:space="preserve">
<value>Include XML documentation comments in the decompiled code</value> <value>Include XML documentation comments in the decompiled code</value>
</data> </data>

Loading…
Cancel
Save