Skip to content

Commit 55d8404

Browse files
Merge pull request #3620 from ds5678/unmanaged-function-pointer-improvements
Improve decompilation of unmanaged function pointers
2 parents f54955a + 83df0ab commit 55d8404

File tree

5 files changed

+228
-8
lines changed

5 files changed

+228
-8
lines changed

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

Lines changed: 156 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
#if !(CS110 && NET70)
21
using System;
3-
#endif
2+
using System.Runtime.CompilerServices;
3+
using System.Runtime.InteropServices;
44
using System.Text;
55

66
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
@@ -46,15 +46,168 @@ public static string VarianceTest(object o)
4646
return (delegate*<object, string>)(&VarianceTest);
4747
}
4848

49-
public unsafe delegate*<void> AddressOfLocalFunction()
49+
public unsafe delegate*<void> AddressOfLocalFunction_Managed()
50+
{
51+
return &LocalFunction;
52+
53+
static void LocalFunction()
54+
{
55+
}
56+
}
57+
58+
public unsafe delegate* unmanaged<void> AddressOfLocalFunction_Unmanaged()
59+
{
60+
return &LocalFunction;
61+
62+
[UnmanagedCallersOnly]
63+
static void LocalFunction()
64+
{
65+
}
66+
}
67+
68+
public unsafe delegate* unmanaged[Cdecl]<void> AddressOfLocalFunction_CDecl()
69+
{
70+
return &LocalFunction;
71+
72+
[UnmanagedCallersOnly(CallConvs = new Type[] { typeof(CallConvCdecl) })]
73+
static void LocalFunction()
74+
{
75+
}
76+
}
77+
78+
public unsafe delegate* unmanaged[Fastcall]<void> AddressOfLocalFunction_Fastcall()
79+
{
80+
return &LocalFunction;
81+
82+
[UnmanagedCallersOnly(CallConvs = new Type[] { typeof(CallConvFastcall) })]
83+
static void LocalFunction()
84+
{
85+
}
86+
}
87+
88+
#if NET60
89+
public unsafe delegate* unmanaged[MemberFunction]<void> AddressOfLocalFunction_MemberFunction()
90+
{
91+
return &LocalFunction;
92+
93+
[UnmanagedCallersOnly(CallConvs = new Type[] { typeof(CallConvMemberFunction) })]
94+
static void LocalFunction()
95+
{
96+
}
97+
}
98+
#endif
99+
100+
public unsafe delegate* unmanaged[Stdcall]<void> AddressOfLocalFunction_Stdcall()
101+
{
102+
return &LocalFunction;
103+
104+
[UnmanagedCallersOnly(CallConvs = new Type[] { typeof(CallConvStdcall) })]
105+
static void LocalFunction()
106+
{
107+
}
108+
}
109+
110+
#if NET60
111+
public unsafe delegate* unmanaged[SuppressGCTransition]<void> AddressOfLocalFunction_SuppressGCTransition()
112+
{
113+
return &LocalFunction;
114+
115+
[UnmanagedCallersOnly(CallConvs = new Type[] { typeof(CallConvSuppressGCTransition) })]
116+
static void LocalFunction()
117+
{
118+
}
119+
}
120+
#endif
121+
122+
public unsafe delegate* unmanaged[Thiscall]<void> AddressOfLocalFunction_Thiscall()
123+
{
124+
return &LocalFunction;
125+
126+
[UnmanagedCallersOnly(CallConvs = new Type[] { typeof(CallConvThiscall) })]
127+
static void LocalFunction()
128+
{
129+
}
130+
}
131+
132+
public unsafe delegate* unmanaged[Cdecl, Fastcall]<void> AddressOfLocalFunction_CDeclAndFastcall()
133+
{
134+
return &LocalFunction;
135+
136+
[UnmanagedCallersOnly(CallConvs = new Type[] {
137+
typeof(CallConvCdecl),
138+
typeof(CallConvFastcall)
139+
})]
140+
static void LocalFunction()
141+
{
142+
}
143+
}
144+
145+
public unsafe delegate* unmanaged[Fastcall, Cdecl]<void> AddressOfLocalFunction_FastcallAndCDecl()
146+
{
147+
return &LocalFunction;
148+
149+
[UnmanagedCallersOnly(CallConvs = new Type[] {
150+
typeof(CallConvFastcall),
151+
typeof(CallConvCdecl)
152+
})]
153+
static void LocalFunction()
154+
{
155+
}
156+
}
157+
158+
#if NET60
159+
public unsafe delegate* unmanaged[Cdecl, SuppressGCTransition]<void> AddressOfLocalFunction_CDeclAndSuppressGCTransition()
50160
{
51161
return &LocalFunction;
52162

163+
[UnmanagedCallersOnly(CallConvs = new Type[] {
164+
typeof(CallConvCdecl),
165+
typeof(CallConvSuppressGCTransition)
166+
})]
53167
static void LocalFunction()
54168
{
169+
}
170+
}
171+
172+
public unsafe delegate* unmanaged[Fastcall, SuppressGCTransition]<void> AddressOfLocalFunction_FastcallAndSuppressGCTransition()
173+
{
174+
return &LocalFunction;
55175

176+
[UnmanagedCallersOnly(CallConvs = new Type[] {
177+
typeof(CallConvFastcall),
178+
typeof(CallConvSuppressGCTransition)
179+
})]
180+
static void LocalFunction()
181+
{
56182
}
57183
}
184+
185+
public unsafe delegate* unmanaged[Stdcall, SuppressGCTransition]<void> AddressOfLocalFunction_StdcallAndSuppressGCTransition()
186+
{
187+
return &LocalFunction;
188+
189+
[UnmanagedCallersOnly(CallConvs = new Type[] {
190+
typeof(CallConvStdcall),
191+
typeof(CallConvSuppressGCTransition)
192+
})]
193+
static void LocalFunction()
194+
{
195+
}
196+
}
197+
198+
public unsafe delegate* unmanaged[Thiscall, SuppressGCTransition]<void> AddressOfLocalFunction_ThiscallAndSuppressGCTransition()
199+
{
200+
return &LocalFunction;
201+
202+
[UnmanagedCallersOnly(CallConvs = new Type[] {
203+
typeof(CallConvThiscall),
204+
typeof(CallConvSuppressGCTransition)
205+
})]
206+
static void LocalFunction()
207+
{
208+
}
209+
}
210+
#endif
58211
}
59212

60213
internal class FunctionPointersWithCallingConvention

ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4598,10 +4598,50 @@ protected internal override TranslatedExpression VisitLdFtn(LdFtn inst, Translat
45984598
.WithILInstruction(inst);
45994599
}
46004600
// C# 9 function pointer
4601+
SignatureCallingConvention callingConvention = SignatureCallingConvention.Default;
4602+
ImmutableArray<IType> customCallingConventions;
4603+
var unmanagedCallersOnlyAttribute = inst.Method.GetAttributes().FirstOrDefault(m => m.AttributeType.IsKnownType(KnownAttribute.UnmanagedCallersOnly));
4604+
if (unmanagedCallersOnlyAttribute != null)
4605+
{
4606+
callingConvention = SignatureCallingConvention.Unmanaged;
4607+
4608+
var callingConventionsArgument = unmanagedCallersOnlyAttribute.NamedArguments.FirstOrDefault(a => a.Name == "CallConvs");
4609+
if (callingConventionsArgument.Value is ImmutableArray<CustomAttributeTypedArgument<IType>> array)
4610+
{
4611+
var builder = ImmutableArray.CreateBuilder<IType>(array.Length);
4612+
foreach (var type in array.Select(a => a.Value).OfType<IType>())
4613+
{
4614+
SignatureCallingConvention? foundCallingConvention = type.Namespace is not "System.Runtime.CompilerServices" || callingConvention != SignatureCallingConvention.Unmanaged ? null : type.Name switch {
4615+
"CallConvCdecl" => SignatureCallingConvention.CDecl,
4616+
"CallConvFastcall" => SignatureCallingConvention.FastCall,
4617+
"CallConvStdcall" => SignatureCallingConvention.StdCall,
4618+
"CallConvThiscall" => SignatureCallingConvention.ThisCall,
4619+
_ => null,
4620+
};
4621+
if (foundCallingConvention is not null)
4622+
{
4623+
callingConvention = foundCallingConvention.Value;
4624+
}
4625+
else
4626+
{
4627+
builder.Add(type);
4628+
}
4629+
}
4630+
customCallingConventions = builder.ToImmutable();
4631+
}
4632+
else
4633+
{
4634+
customCallingConventions = [];
4635+
}
4636+
}
4637+
else
4638+
{
4639+
callingConvention = SignatureCallingConvention.Default;
4640+
customCallingConventions = [];
4641+
}
46014642
var ftp = new FunctionPointerType(
46024643
typeSystem.MainModule,
4603-
// TODO: calling convention
4604-
SignatureCallingConvention.Default, ImmutableArray.Create<IType>(),
4644+
callingConvention, customCallingConventions,
46054645
inst.Method.ReturnType, inst.Method.ReturnTypeIsRefReadOnly,
46064646
inst.Method.Parameters.SelectImmutableArray(p => p.Type),
46074647
inst.Method.Parameters.SelectImmutableArray(p => p.ReferenceKind)

ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ AstType ConvertTypeHelper(IType type)
368368
foreach (var customCallConv in fpt.CustomCallingConventions)
369369
{
370370
AstType callConvSyntax;
371-
if (customCallConv.Name.StartsWith("CallConv", StringComparison.Ordinal) && customCallConv.Name.Length > 8)
371+
if (customCallConv.Namespace == "System.Runtime.CompilerServices" && customCallConv.Name.StartsWith("CallConv", StringComparison.Ordinal) && customCallConv.Name.Length > 8)
372372
{
373373
callConvSyntax = new PrimitiveType(customCallConv.Name.Substring(8));
374374
if (AddResolveResultAnnotations)

ICSharpCode.Decompiler/TypeSystem/FunctionPointerType.cs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public static FunctionPointerType FromSignature(MethodSignature<IType> signature
3333
{
3434
IType returnType = signature.ReturnType;
3535
bool returnIsRefReadOnly = false;
36+
SignatureCallingConvention callingConvention = signature.Header.CallingConvention;
3637
var customCallConvs = ImmutableArray.CreateBuilder<IType>();
3738
while (returnType is ModifiedType modReturn)
3839
{
@@ -45,7 +46,31 @@ public static FunctionPointerType FromSignature(MethodSignature<IType> signature
4546
&& modReturn.Modifier.Namespace == "System.Runtime.CompilerServices")
4647
{
4748
returnType = modReturn.ElementType;
48-
customCallConvs.Add(modReturn.Modifier);
49+
if (callingConvention == SignatureCallingConvention.Unmanaged)
50+
{
51+
switch (modReturn.Modifier.Name)
52+
{
53+
case "CallConvCdecl":
54+
callingConvention = SignatureCallingConvention.CDecl;
55+
break;
56+
case "CallConvFastcall":
57+
callingConvention = SignatureCallingConvention.FastCall;
58+
break;
59+
case "CallConvStdcall":
60+
callingConvention = SignatureCallingConvention.StdCall;
61+
break;
62+
case "CallConvThiscall":
63+
callingConvention = SignatureCallingConvention.ThisCall;
64+
break;
65+
default:
66+
customCallConvs.Add(modReturn.Modifier);
67+
break;
68+
}
69+
}
70+
else
71+
{
72+
customCallConvs.Add(modReturn.Modifier);
73+
}
4974
}
5075
else
5176
{
@@ -89,7 +114,7 @@ public static FunctionPointerType FromSignature(MethodSignature<IType> signature
89114
parameterReferenceKinds.Add(kind);
90115
}
91116
return new FunctionPointerType(
92-
module, signature.Header.CallingConvention, customCallConvs.ToImmutable(),
117+
module, callingConvention, customCallConvs.ToImmutable(),
93118
returnType, returnIsRefReadOnly,
94119
parameterTypes.MoveToImmutable(), parameterReferenceKinds.MoveToImmutable());
95120
}

ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ public enum KnownAttribute
110110
// C# 9 attributes:
111111
NativeInteger,
112112
PreserveBaseOverrides,
113+
UnmanagedCallersOnly,
113114

114115
// C# 11 attributes:
115116
Required,
@@ -192,6 +193,7 @@ public static class KnownAttributes
192193
// C# 9 attributes:
193194
new TopLevelTypeName("System.Runtime.CompilerServices", "NativeIntegerAttribute"),
194195
new TopLevelTypeName("System.Runtime.CompilerServices", "PreserveBaseOverridesAttribute"),
196+
new TopLevelTypeName("System.Runtime.InteropServices", "UnmanagedCallersOnlyAttribute"),
195197
// C# 11 attributes:
196198
new TopLevelTypeName("System.Runtime.CompilerServices", "RequiredMemberAttribute"),
197199
// C# 12 attributes:

0 commit comments

Comments
 (0)