Skip to content

Commit a04c0e4

Browse files
committed
Improve decompilation of unmanaged function pointers
This improves how function pointers are decompiled. * ExpressionBuilder::VisitLdFtn now properly constructs the calling conventions. * FunctionPointerType::FromSignature now checks whether a modopt type affects the calling convention.
1 parent f54955a commit a04c0e4

File tree

5 files changed

+195
-8
lines changed

5 files changed

+195
-8
lines changed

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

Lines changed: 130 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,142 @@ 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+
#if NET60
133+
public unsafe delegate* unmanaged[Cdecl, SuppressGCTransition]<void> AddressOfLocalFunction_CDeclAndSuppressGCTransition()
134+
{
135+
return &LocalFunction;
136+
137+
[UnmanagedCallersOnly(CallConvs = new Type[] {
138+
typeof(CallConvCdecl),
139+
typeof(CallConvSuppressGCTransition)
140+
})]
141+
static void LocalFunction()
142+
{
143+
}
144+
}
145+
146+
public unsafe delegate* unmanaged[Fastcall, SuppressGCTransition]<void> AddressOfLocalFunction_FastcallAndSuppressGCTransition()
50147
{
51148
return &LocalFunction;
52149

150+
[UnmanagedCallersOnly(CallConvs = new Type[] {
151+
typeof(CallConvFastcall),
152+
typeof(CallConvSuppressGCTransition)
153+
})]
53154
static void LocalFunction()
54155
{
156+
}
157+
}
158+
159+
public unsafe delegate* unmanaged[Stdcall, SuppressGCTransition]<void> AddressOfLocalFunction_StdcallAndSuppressGCTransition()
160+
{
161+
return &LocalFunction;
55162

163+
[UnmanagedCallersOnly(CallConvs = new Type[] {
164+
typeof(CallConvStdcall),
165+
typeof(CallConvSuppressGCTransition)
166+
})]
167+
static void LocalFunction()
168+
{
56169
}
57170
}
171+
172+
public unsafe delegate* unmanaged[Thiscall, SuppressGCTransition]<void> AddressOfLocalFunction_ThiscallAndSuppressGCTransition()
173+
{
174+
return &LocalFunction;
175+
176+
[UnmanagedCallersOnly(CallConvs = new Type[] {
177+
typeof(CallConvThiscall),
178+
typeof(CallConvSuppressGCTransition)
179+
})]
180+
static void LocalFunction()
181+
{
182+
}
183+
}
184+
#endif
58185
}
59186

60187
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" ? 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: 20 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,24 @@ 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+
switch (modReturn.Modifier.Name)
50+
{
51+
case "CallConvCdecl":
52+
callingConvention = SignatureCallingConvention.CDecl;
53+
break;
54+
case "CallConvFastcall":
55+
callingConvention = SignatureCallingConvention.FastCall;
56+
break;
57+
case "CallConvStdcall":
58+
callingConvention = SignatureCallingConvention.StdCall;
59+
break;
60+
case "CallConvThiscall":
61+
callingConvention = SignatureCallingConvention.ThisCall;
62+
break;
63+
default:
64+
customCallConvs.Add(modReturn.Modifier);
65+
break;
66+
}
4967
}
5068
else
5169
{
@@ -89,7 +107,7 @@ public static FunctionPointerType FromSignature(MethodSignature<IType> signature
89107
parameterReferenceKinds.Add(kind);
90108
}
91109
return new FunctionPointerType(
92-
module, signature.Header.CallingConvention, customCallConvs.ToImmutable(),
110+
module, callingConvention, customCallConvs.ToImmutable(),
93111
returnType, returnIsRefReadOnly,
94112
parameterTypes.MoveToImmutable(), parameterReferenceKinds.MoveToImmutable());
95113
}

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)