Skip to content

Commit 016d356

Browse files
authored
[cdac] Handle no method def token when trying to get the IL version state (#110449)
Some methods have a nil token - for example, special runtime methods like array functions. When we tried to look up their IL version state, we were throwing an exception. Methods like this will have no versioning state, so check for a nil token and skip the lookup.
1 parent 2d6ea8d commit 016d356

File tree

5 files changed

+58
-28
lines changed

5 files changed

+58
-28
lines changed

src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ internal interface IRuntimeTypeSystem : IContract
8080
#region TypeHandle inspection APIs
8181
public virtual TypeHandle GetTypeHandle(TargetPointer address) => throw new NotImplementedException();
8282
public virtual TargetPointer GetModule(TypeHandle typeHandle) => throw new NotImplementedException();
83+
8384
// A canonical method table is either the MethodTable itself, or in the case of a generic instantiation, it is the
8485
// MethodTable of the prototypical instance.
8586
public virtual TargetPointer GetCanonicalMethodTable(TypeHandle typeHandle) => throw new NotImplementedException();
@@ -130,7 +131,7 @@ internal interface IRuntimeTypeSystem : IContract
130131
public virtual bool IsGenericMethodDefinition(MethodDescHandle methodDesc) => throw new NotImplementedException();
131132
public virtual ReadOnlySpan<TypeHandle> GetGenericMethodInstantiation(MethodDescHandle methodDesc) => throw new NotImplementedException();
132133

133-
// Return mdTokenNil (0x06000000) if the method doesn't have a token, otherwise return the token of the method
134+
// Return mdtMethodDef (0x06000000) if the method doesn't have a token, otherwise return the token of the method
134135
public virtual uint GetMethodToken(MethodDescHandle methodDesc) => throw new NotImplementedException();
135136

136137
// Return true if a MethodDesc represents an array method

src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersions_1.cs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts;
1212
{
1313
private readonly Target _target;
1414

15-
1615
public CodeVersions_1(Target target)
1716
{
1817
_target = target;
@@ -23,9 +22,7 @@ ILCodeVersionHandle ICodeVersions.GetActiveILCodeVersion(TargetPointer methodDes
2322
// CodeVersionManager::GetActiveILCodeVersion
2423
GetModuleAndMethodDesc(methodDesc, out TargetPointer module, out uint methodDefToken);
2524

26-
ModuleHandle moduleHandle = _target.Contracts.Loader.GetModuleHandle(module);
27-
TargetPointer ilCodeVersionTable = _target.Contracts.Loader.GetLookupTables(moduleHandle).MethodDefToILCodeVersioningState;
28-
TargetPointer ilVersionStateAddress = _target.Contracts.Loader.GetModuleLookupMapElement(ilCodeVersionTable, methodDefToken, out var _);
25+
TargetPointer ilVersionStateAddress = GetILVersionStateAddress(module, methodDefToken);
2926
if (ilVersionStateAddress == TargetPointer.Null)
3027
{
3128
return ILCodeVersionHandle.CreateSynthetic(module, methodDefToken);
@@ -73,14 +70,11 @@ IEnumerable<ILCodeVersionHandle> ICodeVersions.GetILCodeVersions(TargetPointer m
7370
// CodeVersionManager::GetILCodeVersions
7471
GetModuleAndMethodDesc(methodDesc, out TargetPointer module, out uint methodDefToken);
7572

76-
ModuleHandle moduleHandle = _target.Contracts.Loader.GetModuleHandle(module);
77-
TargetPointer ilCodeVersionTable = _target.Contracts.Loader.GetLookupTables(moduleHandle).MethodDefToILCodeVersioningState;
78-
TargetPointer ilVersionStateAddress = _target.Contracts.Loader.GetModuleLookupMapElement(ilCodeVersionTable, methodDefToken, out var _);
79-
8073
// always add the synthetic version
8174
yield return ILCodeVersionHandle.CreateSynthetic(module, methodDefToken);
8275

8376
// if explicit versions exist, iterate linked list and return them
77+
TargetPointer ilVersionStateAddress = GetILVersionStateAddress(module, methodDefToken);
8478
if (ilVersionStateAddress != TargetPointer.Null)
8579
{
8680
Data.ILCodeVersioningState ilState = _target.ProcessedData.GetOrAdd<Data.ILCodeVersioningState>(ilVersionStateAddress);
@@ -187,7 +181,6 @@ NativeCodeVersionHandle ICodeVersions.GetActiveNativeCodeVersionForILCodeVersion
187181
return (ilVersionId == codeVersion.ILVersionId)
188182
&& ((NativeCodeVersionNodeFlags)codeVersion.Flags).HasFlag(NativeCodeVersionNodeFlags.IsActiveChild);
189183
});
190-
191184
}
192185

193186
[Flags]
@@ -301,6 +294,18 @@ private void GetModuleAndMethodDesc(TargetPointer methodDesc, out TargetPointer
301294
methodDefToken = rts.GetMethodToken(md);
302295
}
303296

297+
private TargetPointer GetILVersionStateAddress(TargetPointer module, uint methodDefToken)
298+
{
299+
// No token - for example, special runtime methods like array methods
300+
if (methodDefToken == (uint)EcmaMetadataUtils.TokenType.mdtMethodDef)
301+
return TargetPointer.Null;
302+
303+
ModuleHandle moduleHandle = _target.Contracts.Loader.GetModuleHandle(module);
304+
TargetPointer ilCodeVersionTable = _target.Contracts.Loader.GetLookupTables(moduleHandle).MethodDefToILCodeVersioningState;
305+
TargetPointer ilVersionStateAddress = _target.Contracts.Loader.GetModuleLookupMapElement(ilCodeVersionTable, methodDefToken, out var _);
306+
return ilVersionStateAddress;
307+
}
308+
304309
private ILCodeVersionNode AsNode(ILCodeVersionHandle handle)
305310
{
306311
if (handle.ILCodeVersionNode == TargetPointer.Null)

src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ internal partial struct RuntimeTypeSystem_1 : IRuntimeTypeSystem
2323
private readonly Dictionary<TargetPointer, MethodTable> _methodTables = new();
2424
private readonly Dictionary<TargetPointer, MethodDesc> _methodDescs = new();
2525

26-
2726
internal struct MethodTable
2827
{
2928
internal MethodTableFlags_1 Flags { get; }
@@ -126,8 +125,7 @@ private static uint ComputeToken(Target target, Data.MethodDesc desc, Data.Metho
126125

127126
uint tokenRemainder = (uint)(desc.Flags3AndTokenRemainder & tokenRemainderMask);
128127
uint tokenRange = ((uint)(chunk.FlagsAndTokenRange & tokenRangeMask)) << tokenRemainderBitCount;
129-
130-
return 0x06000000 | tokenRange | tokenRemainder;
128+
return EcmaMetadataUtils.CreateMethodDef(tokenRange | tokenRemainder);
131129
}
132130

133131
public MethodClassification Classification => (MethodClassification)((int)_desc.Flags & (int)MethodDescFlags_1.MethodDescFlags.ClassificationMask);

src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Diagnostics;
5+
46
namespace Microsoft.Diagnostics.DataContractReader;
57

68
internal static class EcmaMetadataUtils
@@ -12,4 +14,16 @@ internal static class EcmaMetadataUtils
1214

1315
internal static uint MakeToken(uint rid, uint table) => rid | (table << RowIdBitCount);
1416

17+
// ECMA-335 II.22
18+
// Metadata table index is the most significant byte of the 4-byte token
19+
public enum TokenType : uint
20+
{
21+
mdtMethodDef = 0x06 << 24
22+
}
23+
24+
public static uint CreateMethodDef(uint tokenParts)
25+
{
26+
Debug.Assert((tokenParts & 0xff000000) == 0, $"Token type should not be set in {nameof(tokenParts)}");
27+
return (uint)TokenType.mdtMethodDef | tokenParts;
28+
}
1529
}

src/native/managed/cdacreader/tests/CodeVersionsTests.cs

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ internal class MockMethodDesc
3333
public bool IsVersionable { get; private set; }
3434

3535
public uint RowId { get; set; }
36-
public uint MethodToken => 0x06000000 | RowId;
36+
public uint MethodToken => EcmaMetadataUtils.CreateMethodDef(RowId);
3737

3838
// n.b. in the real RuntimeTypeSystem_1 this is more complex
3939
public TargetCodePointer NativeCode { get; private set; }
@@ -342,9 +342,10 @@ public void GetActiveNativeCodeVersion_DefaultCase(MockTarget.Architecture arch)
342342
{
343343
uint methodRowId = 0x25; // arbitrary
344344
TargetCodePointer expectedNativeCodePointer = new TargetCodePointer(0x0700_abc0);
345-
uint methodDefToken = 0x06000000 | methodRowId;
345+
uint methodDefToken = EcmaMetadataUtils.CreateMethodDef(methodRowId);
346346
var builder = new MockCodeVersions(arch);
347347
var methodDescAddress = new TargetPointer(0x00aa_aa00);
348+
var methodDescNilTokenAddress = new TargetPointer(0x00aa_bb00);
348349
var moduleAddress = new TargetPointer(0x00ca_ca00);
349350

350351
TargetPointer versioningState = builder.AddILCodeVersioningState(
@@ -353,34 +354,45 @@ public void GetActiveNativeCodeVersion_DefaultCase(MockTarget.Architecture arch)
353354
activeVersionModule: moduleAddress,
354355
activeVersionMethodDef: methodDefToken,
355356
firstVersionNode: TargetPointer.Null);
356-
var oneModule = new MockModule() {
357+
var module = new MockModule() {
357358
Address = moduleAddress,
358359
MethodDefToILCodeVersioningStateAddress = new TargetPointer(0x00da_da00),
359360
MethodDefToILCodeVersioningStateTable = new Dictionary<uint, TargetPointer>() {
360361
{ methodRowId, versioningState}
361362
},
362363
};
363-
var oneMethodTable = new MockMethodTable() {
364+
var methodTable = new MockMethodTable() {
364365
Address = new TargetPointer(0x00ba_ba00),
365-
Module = oneModule,
366+
Module = module,
366367
};
367-
var oneMethod = MockMethodDesc.CreateVersionable(selfAddress: methodDescAddress, methodDescVersioningState: TargetPointer.Null, nativeCode: expectedNativeCodePointer);
368-
oneMethod.MethodTable = oneMethodTable;
369-
oneMethod.RowId = methodRowId;
368+
var method = MockMethodDesc.CreateVersionable(selfAddress: methodDescAddress, methodDescVersioningState: TargetPointer.Null, nativeCode: expectedNativeCodePointer);
369+
method.MethodTable = methodTable;
370+
method.RowId = methodRowId;
371+
372+
var methodNilToken = MockMethodDesc.CreateVersionable(selfAddress: methodDescNilTokenAddress, methodDescVersioningState: TargetPointer.Null, nativeCode: expectedNativeCodePointer);
373+
methodNilToken.MethodTable = methodTable;
370374

371-
var target = CreateTarget(arch, [oneMethod], [oneMethodTable], [], [oneModule], builder);
375+
var target = CreateTarget(arch, [method, methodNilToken], [methodTable], [], [module], builder);
372376

373377
// TEST
374378

375379
var codeVersions = target.Contracts.CodeVersions;
376-
377380
Assert.NotNull(codeVersions);
378381

379-
NativeCodeVersionHandle handle = codeVersions.GetActiveNativeCodeVersion(methodDescAddress);
380-
Assert.True(handle.Valid);
381-
Assert.Equal(methodDescAddress, handle.MethodDescAddress);
382-
var actualCodeAddress = codeVersions.GetNativeCode(handle);
383-
Assert.Equal(expectedNativeCodePointer, actualCodeAddress);
382+
{
383+
NativeCodeVersionHandle handle = codeVersions.GetActiveNativeCodeVersion(methodDescAddress);
384+
Assert.True(handle.Valid);
385+
Assert.Equal(methodDescAddress, handle.MethodDescAddress);
386+
var actualCodeAddress = codeVersions.GetNativeCode(handle);
387+
Assert.Equal(expectedNativeCodePointer, actualCodeAddress);
388+
}
389+
{
390+
NativeCodeVersionHandle handle = codeVersions.GetActiveNativeCodeVersion(methodDescNilTokenAddress);
391+
Assert.True(handle.Valid);
392+
Assert.Equal(methodDescNilTokenAddress, handle.MethodDescAddress);
393+
var actualCodeAddress = codeVersions.GetNativeCode(handle);
394+
Assert.Equal(expectedNativeCodePointer, actualCodeAddress);
395+
}
384396
}
385397

386398
[Theory]

0 commit comments

Comments
 (0)