Skip to content

Commit 63f975c

Browse files
Treat signature calling convention bits like we treat
custom modifiers during method lookup.
1 parent c2be6fc commit 63f975c

File tree

4 files changed

+100
-86
lines changed

4 files changed

+100
-86
lines changed

src/coreclr/tools/Common/TypeSystem/Common/MethodDesc.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,12 +184,17 @@ public bool EmbeddedSignatureMismatchPermitted
184184
}
185185
}
186186

187-
public EmbeddedSignatureData[] GetEmbeddedSignatureData(params EmbeddedSignatureDataKind[] kinds)
187+
public EmbeddedSignatureData[] GetEmbeddedSignatureData()
188+
{
189+
return GetEmbeddedSignatureData(default);
190+
}
191+
192+
public EmbeddedSignatureData[] GetEmbeddedSignatureData(ReadOnlySpan<EmbeddedSignatureDataKind> kinds)
188193
{
189194
if ((_embeddedSignatureData == null) || (_embeddedSignatureData.Length == 0))
190195
return null;
191196

192-
if (kinds.Length == 0)
197+
if (kinds.IsEmpty)
193198
return (EmbeddedSignatureData[])_embeddedSignatureData.Clone();
194199

195200
List<EmbeddedSignatureData> ret = new();

src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs

Lines changed: 59 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -227,30 +227,76 @@ private static bool DoesMethodMatchUnsafeAccessorDeclaration(ref GenerationConte
227227
// If we are, do it first.
228228
if (!ignoreCustomModifiers)
229229
{
230-
// Compare custom modifiers on the signatures.
231-
var declCustomMod = declSig.GetEmbeddedSignatureData(EmbeddedSignatureDataKind.RequiredCustomModifier, EmbeddedSignatureDataKind.OptionalCustomModifier) ?? Array.Empty<EmbeddedSignatureData>();
232-
var maybeCustomMod = maybeSig.GetEmbeddedSignatureData(EmbeddedSignatureDataKind.RequiredCustomModifier, EmbeddedSignatureDataKind.OptionalCustomModifier) ?? Array.Empty<EmbeddedSignatureData>();
233-
if (!CompareEmbeddedData(context.Kind, declCustomMod, maybeCustomMod))
230+
// Compare any unmanaged callconv and custom modifiers on the signatures.
231+
// We treat unmanaged calling conventions at the same level of precedance
232+
// as custom modifiers, eventhough they are normally bits in a signature.
233+
ReadOnlySpan<EmbeddedSignatureDataKind> kinds = new EmbeddedSignatureDataKind[]
234+
{
235+
EmbeddedSignatureDataKind.UnmanagedCallConv,
236+
EmbeddedSignatureDataKind.RequiredCustomModifier,
237+
EmbeddedSignatureDataKind.OptionalCustomModifier
238+
};
239+
240+
var declData = declSig.GetEmbeddedSignatureData(kinds) ?? Array.Empty<EmbeddedSignatureData>();
241+
var maybeData = maybeSig.GetEmbeddedSignatureData(kinds) ?? Array.Empty<EmbeddedSignatureData>();
242+
if (declData.Length != maybeData.Length)
234243
{
235244
return false;
236245
}
246+
247+
// Validate the custom modifiers match precisely.
248+
for (int i = 0; i < declData.Length; ++i)
249+
{
250+
EmbeddedSignatureData dd = declData[i];
251+
EmbeddedSignatureData md = maybeData[i];
252+
if (dd.kind != md.kind || dd.type != md.type)
253+
{
254+
return false;
255+
}
256+
257+
// The indices on non-constructor declarations require
258+
// some slight modification since there is always an extra
259+
// argument in the declaration compared to the target.
260+
string declIndex = dd.index;
261+
if (context.Kind != UnsafeAccessorKind.Constructor)
262+
{
263+
string unmanagedCallConvMaybe = string.Empty;
264+
265+
// Check for and drop the unmanaged calling convention
266+
// value suffix to add it back after updating below.
267+
if (declIndex.Contains('|'))
268+
{
269+
Debug.Assert(dd.kind == EmbeddedSignatureDataKind.UnmanagedCallConv);
270+
var tmp = declIndex.Split('|');
271+
Debug.Assert(tmp.Length == 2);
272+
declIndex = tmp[0];
273+
unmanagedCallConvMaybe = "|" + tmp[1];
274+
}
275+
276+
// Decrement the second to last index by one to
277+
// account for the difference in declarations.
278+
string[] lvls = declIndex.Split('.');
279+
int toUpdate = lvls.Length < 2 ? 0 : lvls.Length - 2;
280+
int idx = int.Parse(lvls[toUpdate], CultureInfo.InvariantCulture);
281+
idx--;
282+
lvls[toUpdate] = idx.ToString();
283+
declIndex = string.Join(".", lvls) + unmanagedCallConvMaybe;
284+
}
285+
286+
if (declIndex != md.index)
287+
{
288+
return false;
289+
}
290+
}
237291
}
238292

239-
// Validate calling convention.
293+
// Validate calling convention of declaration.
240294
if ((declSig.Flags & MethodSignatureFlags.UnmanagedCallingConventionMask)
241295
!= (maybeSig.Flags & MethodSignatureFlags.UnmanagedCallingConventionMask))
242296
{
243297
return false;
244298
}
245299

246-
// Compare unmanaged callconv on the signatures.
247-
var declUnmanaged = declSig.GetEmbeddedSignatureData(EmbeddedSignatureDataKind.UnmanagedCallConv) ?? Array.Empty<EmbeddedSignatureData>();
248-
var maybeUnmanaged = maybeSig.GetEmbeddedSignatureData(EmbeddedSignatureDataKind.UnmanagedCallConv) ?? Array.Empty<EmbeddedSignatureData>();
249-
if (!CompareEmbeddedData(context.Kind, declUnmanaged, maybeUnmanaged))
250-
{
251-
return false;
252-
}
253-
254300
// Validate argument count and return type
255301
if (context.Kind == UnsafeAccessorKind.Constructor)
256302
{
@@ -300,64 +346,6 @@ private static bool DoesMethodMatchUnsafeAccessorDeclaration(ref GenerationConte
300346
}
301347

302348
return true;
303-
304-
static bool CompareEmbeddedData(
305-
UnsafeAccessorKind kind,
306-
EmbeddedSignatureData[] declData,
307-
EmbeddedSignatureData[] maybeData)
308-
{
309-
if (declData.Length != maybeData.Length)
310-
{
311-
return false;
312-
}
313-
314-
// Validate the custom modifiers match precisely.
315-
for (int i = 0; i < declData.Length; ++i)
316-
{
317-
EmbeddedSignatureData dd = declData[i];
318-
EmbeddedSignatureData md = maybeData[i];
319-
if (dd.kind != md.kind || dd.type != md.type)
320-
{
321-
return false;
322-
}
323-
324-
// The indices on non-constructor declarations require
325-
// some slight modification since there is always an extra
326-
// argument in the declaration compared to the target.
327-
string declIndex = dd.index;
328-
if (kind != UnsafeAccessorKind.Constructor)
329-
{
330-
string unmanagedCallConvMaybe = string.Empty;
331-
332-
// Check for and drop the unmanaged calling convention
333-
// value suffix to add it back after updating below.
334-
if (declIndex.Contains('|'))
335-
{
336-
Debug.Assert(dd.kind == EmbeddedSignatureDataKind.UnmanagedCallConv);
337-
var tmp = declIndex.Split('|');
338-
Debug.Assert(tmp.Length == 2);
339-
declIndex = tmp[0];
340-
unmanagedCallConvMaybe = "|" + tmp[1];
341-
}
342-
343-
// Decrement the second to last index by one to
344-
// account for the difference in declarations.
345-
string[] lvls = declIndex.Split('.');
346-
int toUpdate = lvls.Length < 2 ? 0 : lvls.Length - 2;
347-
int idx = int.Parse(lvls[toUpdate], CultureInfo.InvariantCulture);
348-
idx--;
349-
lvls[toUpdate] = idx.ToString();
350-
declIndex = string.Join(".", lvls) + unmanagedCallConvMaybe;
351-
}
352-
353-
if (declIndex != md.index)
354-
{
355-
return false;
356-
}
357-
}
358-
359-
return true;
360-
}
361349
}
362350

363351
private static bool TrySetTargetMethod(ref GenerationContext context, string name, out bool isAmbiguous, bool ignoreCustomModifiers = true)

src/coreclr/vm/siginfo.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3927,7 +3927,14 @@ MetaSig::CompareElementType(
39273927
IfFailThrow(CorSigUncompressElementType_EndPtr(pSig1, pEndSig1, &callingConvention1));
39283928
CorElementType callingConvention2 = ELEMENT_TYPE_MAX; // initialize to illegal
39293929
IfFailThrow(CorSigUncompressElementType_EndPtr(pSig2, pEndSig2, &callingConvention2));
3930-
if (callingConvention1 != callingConvention2)
3930+
3931+
// Calling conventions are generally treated as custom modifiers.
3932+
// When callers request calling conventions to be ignored, we also ignore
3933+
// calling conventions and this is okay. It is okay because calling conventions,
3934+
// when more than one is defined (for example, SuppressGCTransition), all
3935+
// become encoded as custom modifiers.
3936+
if (!state->IgnoreCustomModifiers
3937+
&& callingConvention1 != callingConvention2)
39313938
{
39323939
return FALSE;
39333940
}

src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ class UserDataClass
2424
public const string MethodVoidName = nameof(_mvv);
2525
public const string MethodNameAmbiguous = nameof(_Ambiguous);
2626
public const string MethodPointerName = nameof(_Pointer);
27-
public const string MethodCallConvBitsName = nameof(_CallConvBits);
27+
public const string MethodCdeclCallConvBitName = nameof(_CdeclCallConvBit);
28+
public const string MethodStdcallCallConvBitName = nameof(_StdcallCallConvBit);
2829

2930
private static string _F = PrivateStatic;
3031
private string _f;
@@ -52,7 +53,8 @@ private void _mvv() {}
5253

5354
// Used to validate the embedded callconv bits (non-unmanaged bit) in
5455
// ECMA-335 signatures for methods.
55-
private static string _CallConvBits(delegate* unmanaged[Cdecl]<void> fptr) => nameof(CallConvCdecl);
56+
private string _CdeclCallConvBit(delegate* unmanaged[Cdecl]<void> fptr) => nameof(CallConvCdecl);
57+
private string _StdcallCallConvBit(delegate* unmanaged[Stdcall]<void> fptr) => nameof(CallConvStdcall);
5658
}
5759

5860
[StructLayout(LayoutKind.Sequential)]
@@ -326,6 +328,28 @@ public static void Verify_PreciseMatchCustomModifier()
326328
extern static string CallPrivateMethod(UserDataClass d, delegate* unmanaged[Stdcall, MemberFunction]<void> fptr);
327329
}
328330

331+
[Fact]
332+
[ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)]
333+
public static void Verify_CallConvBitsAreTreatedAsCustomModifiersAndIgnored()
334+
{
335+
Console.WriteLine($"Running {nameof(Verify_CallConvBitsAreTreatedAsCustomModifiersAndIgnored)}");
336+
337+
var ud = CallPrivateConstructorClass();
338+
Assert.Equal(nameof(CallConvCdecl), CallCdeclMethod(ud, null));
339+
Assert.Equal(nameof(CallConvStdcall), CallStdcallMethod(ud, null));
340+
341+
// The names of the declarations don't match the calling conventions in the function
342+
// pointer signature, this is by design for this test. The intent here is to validate that
343+
// calling conventions, when encoded in the ECMA-335 bits, are ignored on the first pass
344+
// in the same way custom modifiers are ignored.
345+
[UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataClass.MethodCdeclCallConvBitName)]
346+
extern static string CallCdeclMethod(UserDataClass d, delegate* unmanaged[Stdcall]<void> fptr);
347+
348+
// See comment above regarding naming.
349+
[UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataClass.MethodStdcallCallConvBitName)]
350+
extern static string CallStdcallMethod(UserDataClass d, delegate* unmanaged[Cdecl]<void> fptr);
351+
}
352+
329353
[Fact]
330354
[ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)]
331355
public static void Verify_InvalidTargetUnsafeAccessor()
@@ -352,10 +376,6 @@ public static void Verify_InvalidTargetUnsafeAccessor()
352376
isNativeAot ? null : UserDataClass.MethodPointerName,
353377
() => CallPointerMethod(null, null));
354378

355-
AssertExtensions.ThrowsMissingMemberException<MissingMethodException>(
356-
isNativeAot ? null : UserDataClass.MethodCallConvBitsName,
357-
() => CallCallConvBitsMethod(null, null));
358-
359379
Assert.Throws<AmbiguousMatchException>(
360380
() => CallAmbiguousMethod(CallPrivateConstructorClass(), null));
361381

@@ -375,12 +395,6 @@ public static void Verify_InvalidTargetUnsafeAccessor()
375395
[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name=UserDataClass.MethodPointerName)]
376396
extern static string CallPointerMethod(UserDataClass d, delegate* unmanaged[Stdcall]<void> fptr);
377397

378-
// This is used to validate the ECMA-335 calling convention bits are checked
379-
// before checking the custom modifiers that encode the calling conventions
380-
// when there is more than one or one option doesn't have an existing bit.
381-
[UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataClass.MethodCallConvBitsName)]
382-
extern static string CallCallConvBitsMethod(UserDataClass d, delegate* unmanaged[Cdecl, SuppressGCTransition]<void> fptr);
383-
384398
// This is an ambiguous match since there are two methods each with two custom modifiers.
385399
// Therefore the default "ignore custom modifiers" logic fails. The fallback is for a
386400
// precise match and that also fails because the custom modifiers don't match precisely.

0 commit comments

Comments
 (0)