Skip to content

Commit

Permalink
[cdac] Handle no method def token when trying to get the IL version s…
Browse files Browse the repository at this point in the history
…tate (#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.
  • Loading branch information
elinor-fung authored Dec 9, 2024
1 parent 2d6ea8d commit 016d356
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ internal interface IRuntimeTypeSystem : IContract
#region TypeHandle inspection APIs
public virtual TypeHandle GetTypeHandle(TargetPointer address) => throw new NotImplementedException();
public virtual TargetPointer GetModule(TypeHandle typeHandle) => throw new NotImplementedException();

// A canonical method table is either the MethodTable itself, or in the case of a generic instantiation, it is the
// MethodTable of the prototypical instance.
public virtual TargetPointer GetCanonicalMethodTable(TypeHandle typeHandle) => throw new NotImplementedException();
Expand Down Expand Up @@ -130,7 +131,7 @@ internal interface IRuntimeTypeSystem : IContract
public virtual bool IsGenericMethodDefinition(MethodDescHandle methodDesc) => throw new NotImplementedException();
public virtual ReadOnlySpan<TypeHandle> GetGenericMethodInstantiation(MethodDescHandle methodDesc) => throw new NotImplementedException();

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

// Return true if a MethodDesc represents an array method
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts;
{
private readonly Target _target;


public CodeVersions_1(Target target)
{
_target = target;
Expand All @@ -23,9 +22,7 @@ ILCodeVersionHandle ICodeVersions.GetActiveILCodeVersion(TargetPointer methodDes
// CodeVersionManager::GetActiveILCodeVersion
GetModuleAndMethodDesc(methodDesc, out TargetPointer module, out uint methodDefToken);

ModuleHandle moduleHandle = _target.Contracts.Loader.GetModuleHandle(module);
TargetPointer ilCodeVersionTable = _target.Contracts.Loader.GetLookupTables(moduleHandle).MethodDefToILCodeVersioningState;
TargetPointer ilVersionStateAddress = _target.Contracts.Loader.GetModuleLookupMapElement(ilCodeVersionTable, methodDefToken, out var _);
TargetPointer ilVersionStateAddress = GetILVersionStateAddress(module, methodDefToken);
if (ilVersionStateAddress == TargetPointer.Null)
{
return ILCodeVersionHandle.CreateSynthetic(module, methodDefToken);
Expand Down Expand Up @@ -73,14 +70,11 @@ IEnumerable<ILCodeVersionHandle> ICodeVersions.GetILCodeVersions(TargetPointer m
// CodeVersionManager::GetILCodeVersions
GetModuleAndMethodDesc(methodDesc, out TargetPointer module, out uint methodDefToken);

ModuleHandle moduleHandle = _target.Contracts.Loader.GetModuleHandle(module);
TargetPointer ilCodeVersionTable = _target.Contracts.Loader.GetLookupTables(moduleHandle).MethodDefToILCodeVersioningState;
TargetPointer ilVersionStateAddress = _target.Contracts.Loader.GetModuleLookupMapElement(ilCodeVersionTable, methodDefToken, out var _);

// always add the synthetic version
yield return ILCodeVersionHandle.CreateSynthetic(module, methodDefToken);

// if explicit versions exist, iterate linked list and return them
TargetPointer ilVersionStateAddress = GetILVersionStateAddress(module, methodDefToken);
if (ilVersionStateAddress != TargetPointer.Null)
{
Data.ILCodeVersioningState ilState = _target.ProcessedData.GetOrAdd<Data.ILCodeVersioningState>(ilVersionStateAddress);
Expand Down Expand Up @@ -187,7 +181,6 @@ NativeCodeVersionHandle ICodeVersions.GetActiveNativeCodeVersionForILCodeVersion
return (ilVersionId == codeVersion.ILVersionId)
&& ((NativeCodeVersionNodeFlags)codeVersion.Flags).HasFlag(NativeCodeVersionNodeFlags.IsActiveChild);
});

}

[Flags]
Expand Down Expand Up @@ -301,6 +294,18 @@ private void GetModuleAndMethodDesc(TargetPointer methodDesc, out TargetPointer
methodDefToken = rts.GetMethodToken(md);
}

private TargetPointer GetILVersionStateAddress(TargetPointer module, uint methodDefToken)
{
// No token - for example, special runtime methods like array methods
if (methodDefToken == (uint)EcmaMetadataUtils.TokenType.mdtMethodDef)
return TargetPointer.Null;

ModuleHandle moduleHandle = _target.Contracts.Loader.GetModuleHandle(module);
TargetPointer ilCodeVersionTable = _target.Contracts.Loader.GetLookupTables(moduleHandle).MethodDefToILCodeVersioningState;
TargetPointer ilVersionStateAddress = _target.Contracts.Loader.GetModuleLookupMapElement(ilCodeVersionTable, methodDefToken, out var _);
return ilVersionStateAddress;
}

private ILCodeVersionNode AsNode(ILCodeVersionHandle handle)
{
if (handle.ILCodeVersionNode == TargetPointer.Null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ internal partial struct RuntimeTypeSystem_1 : IRuntimeTypeSystem
private readonly Dictionary<TargetPointer, MethodTable> _methodTables = new();
private readonly Dictionary<TargetPointer, MethodDesc> _methodDescs = new();


internal struct MethodTable
{
internal MethodTableFlags_1 Flags { get; }
Expand Down Expand Up @@ -126,8 +125,7 @@ private static uint ComputeToken(Target target, Data.MethodDesc desc, Data.Metho

uint tokenRemainder = (uint)(desc.Flags3AndTokenRemainder & tokenRemainderMask);
uint tokenRange = ((uint)(chunk.FlagsAndTokenRange & tokenRangeMask)) << tokenRemainderBitCount;

return 0x06000000 | tokenRange | tokenRemainder;
return EcmaMetadataUtils.CreateMethodDef(tokenRange | tokenRemainder);
}

public MethodClassification Classification => (MethodClassification)((int)_desc.Flags & (int)MethodDescFlags_1.MethodDescFlags.ClassificationMask);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;

namespace Microsoft.Diagnostics.DataContractReader;

internal static class EcmaMetadataUtils
Expand All @@ -12,4 +14,16 @@ internal static class EcmaMetadataUtils

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

// ECMA-335 II.22
// Metadata table index is the most significant byte of the 4-byte token
public enum TokenType : uint
{
mdtMethodDef = 0x06 << 24
}

public static uint CreateMethodDef(uint tokenParts)
{
Debug.Assert((tokenParts & 0xff000000) == 0, $"Token type should not be set in {nameof(tokenParts)}");
return (uint)TokenType.mdtMethodDef | tokenParts;
}
}
42 changes: 27 additions & 15 deletions src/native/managed/cdacreader/tests/CodeVersionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ internal class MockMethodDesc
public bool IsVersionable { get; private set; }

public uint RowId { get; set; }
public uint MethodToken => 0x06000000 | RowId;
public uint MethodToken => EcmaMetadataUtils.CreateMethodDef(RowId);

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

TargetPointer versioningState = builder.AddILCodeVersioningState(
Expand All @@ -353,34 +354,45 @@ public void GetActiveNativeCodeVersion_DefaultCase(MockTarget.Architecture arch)
activeVersionModule: moduleAddress,
activeVersionMethodDef: methodDefToken,
firstVersionNode: TargetPointer.Null);
var oneModule = new MockModule() {
var module = new MockModule() {
Address = moduleAddress,
MethodDefToILCodeVersioningStateAddress = new TargetPointer(0x00da_da00),
MethodDefToILCodeVersioningStateTable = new Dictionary<uint, TargetPointer>() {
{ methodRowId, versioningState}
},
};
var oneMethodTable = new MockMethodTable() {
var methodTable = new MockMethodTable() {
Address = new TargetPointer(0x00ba_ba00),
Module = oneModule,
Module = module,
};
var oneMethod = MockMethodDesc.CreateVersionable(selfAddress: methodDescAddress, methodDescVersioningState: TargetPointer.Null, nativeCode: expectedNativeCodePointer);
oneMethod.MethodTable = oneMethodTable;
oneMethod.RowId = methodRowId;
var method = MockMethodDesc.CreateVersionable(selfAddress: methodDescAddress, methodDescVersioningState: TargetPointer.Null, nativeCode: expectedNativeCodePointer);
method.MethodTable = methodTable;
method.RowId = methodRowId;

var methodNilToken = MockMethodDesc.CreateVersionable(selfAddress: methodDescNilTokenAddress, methodDescVersioningState: TargetPointer.Null, nativeCode: expectedNativeCodePointer);
methodNilToken.MethodTable = methodTable;

var target = CreateTarget(arch, [oneMethod], [oneMethodTable], [], [oneModule], builder);
var target = CreateTarget(arch, [method, methodNilToken], [methodTable], [], [module], builder);

// TEST

var codeVersions = target.Contracts.CodeVersions;

Assert.NotNull(codeVersions);

NativeCodeVersionHandle handle = codeVersions.GetActiveNativeCodeVersion(methodDescAddress);
Assert.True(handle.Valid);
Assert.Equal(methodDescAddress, handle.MethodDescAddress);
var actualCodeAddress = codeVersions.GetNativeCode(handle);
Assert.Equal(expectedNativeCodePointer, actualCodeAddress);
{
NativeCodeVersionHandle handle = codeVersions.GetActiveNativeCodeVersion(methodDescAddress);
Assert.True(handle.Valid);
Assert.Equal(methodDescAddress, handle.MethodDescAddress);
var actualCodeAddress = codeVersions.GetNativeCode(handle);
Assert.Equal(expectedNativeCodePointer, actualCodeAddress);
}
{
NativeCodeVersionHandle handle = codeVersions.GetActiveNativeCodeVersion(methodDescNilTokenAddress);
Assert.True(handle.Valid);
Assert.Equal(methodDescNilTokenAddress, handle.MethodDescAddress);
var actualCodeAddress = codeVersions.GetNativeCode(handle);
Assert.Equal(expectedNativeCodePointer, actualCodeAddress);
}
}

[Theory]
Expand Down

0 comments on commit 016d356

Please sign in to comment.