Skip to content

Commit

Permalink
Implement module and type equality (#373)
Browse files Browse the repository at this point in the history
* Implement type equality

* Rename TypeHandle back to MethodTable

It'll just be too confusing.

* Add module equality

* Use is null instead of == null

Fix stack overflow caused by recursive operator ==.
  • Loading branch information
leculver committed Dec 5, 2019
1 parent 32c23f1 commit 22ebc3e
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 23 deletions.
21 changes: 21 additions & 0 deletions src/Microsoft.Diagnostics.Runtime.Tests/src/ModuleTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,26 @@ public void TestGetTypeByName()
Assert.NotNull(types.GetTypeByName("Types"));
Assert.Null(types.GetTypeByName("Foo"));
}

[Fact]
public void ModuleEqualityTest()
{
using DataTarget dt = TestTargets.Types.LoadFullDump();
using ClrRuntime runtime = dt.ClrVersions.Single().CreateRuntime();

ClrModule[] oldModules = runtime.EnumerateModules().ToArray();
Assert.NotEmpty(oldModules);

runtime.FlushCachedData();

ClrModule[] newModules = runtime.EnumerateModules().ToArray();
Assert.Equal(oldModules.Length, newModules.Length);

for (int i = 0; i < newModules.Length; i++)
{
Assert.Equal(oldModules[i], newModules[i]);
Assert.NotSame(oldModules[i], newModules[i]);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ public void EnsureFlushClearsData()

private void CheckTypeNotSame(ClrType oldType, ClrType newType)
{
Assert.Equal(oldType.TypeHandle, newType.TypeHandle);
Assert.Equal(oldType.MethodTable, newType.MethodTable);

AssertEqualNotSame(oldType.Name, newType.Name);

Expand Down
38 changes: 30 additions & 8 deletions src/Microsoft.Diagnostics.Runtime.Tests/src/TypeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public void TypeEqualityTest()
ClrType[] types = (from obj in heap.EnumerateObjects()
let t = heap.GetObjectType(obj.Address)
where t.Name == TypeName
orderby t.TypeHandle
orderby t.MethodTable
select t).ToArray();

Assert.Equal(2, types.Length);
Expand All @@ -132,6 +132,28 @@ where name.Equals("sharedlibrary", StringComparison.OrdinalIgnoreCase)

Assert.Same(types[0], typesFromModule[0]);
Assert.Same(types[1], typesFromModule[1]);

// Get new types
runtime.FlushCachedData();


ClrType[] newTypes = (from module in runtime.EnumerateModules()
let name = Path.GetFileNameWithoutExtension(module.FileName)
where name.Equals("sharedlibrary", StringComparison.OrdinalIgnoreCase)
let type = module.GetTypeByName(TypeName)
select type).ToArray();

Assert.Equal(2, newTypes.Length);
for (int i = 0; i < newTypes.Length; i++)
{
Assert.NotSame(typesFromModule[i], newTypes[i]);
Assert.Equal(typesFromModule[i], newTypes[i]);
}

// Even though these are the same underlying type defined in sharedlibrary's metadata,
// they have different MethodTables, Parent modules, and parent domains. These do not
// compare as equal.
Assert.NotEqual(typesFromModule[0], typesFromModule[1]);
}

[WindowsFact]
Expand Down Expand Up @@ -177,7 +199,7 @@ public void MethodTableHeapEnumeration()

foreach (ClrType type in heap.EnumerateObjects().Select(obj => heap.GetObjectType(obj.Address)).Unique())
{
Assert.NotEqual(0ul, type.TypeHandle);
Assert.NotEqual(0ul, type.MethodTable);

ClrType typeFromHeap;

Expand All @@ -186,14 +208,14 @@ public void MethodTableHeapEnumeration()
ClrType componentType = type.ComponentType;
Assert.NotNull(componentType);

typeFromHeap = runtime.GetTypeByMethodTable(type.TypeHandle);
typeFromHeap = runtime.GetTypeByMethodTable(type.MethodTable);
}
else
{
typeFromHeap = runtime.GetTypeByMethodTable(type.TypeHandle);
typeFromHeap = runtime.GetTypeByMethodTable(type.MethodTable);
}

Assert.Equal(type.TypeHandle, typeFromHeap.TypeHandle);
Assert.Equal(type.MethodTable, typeFromHeap.MethodTable);
Assert.Same(type, typeFromHeap);
}
}
Expand All @@ -216,7 +238,7 @@ public void GetObjectMethodTableTest()
Assert.NotEqual(0ul, mt);

Assert.Same(type, runtime.GetTypeByMethodTable(mt));
Assert.Equal(mt, type.TypeHandle);
Assert.Equal(mt, type.MethodTable);
}
}

Expand Down Expand Up @@ -252,8 +274,8 @@ public void EnumerateMethodTableTest()

// The MethodTable returned by ClrType should always be the method table that lives in the "first"
// AppDomain (in order of ClrAppDomain.Id).
Assert.Equal(appDomainsFooMethodTable, fooType.TypeHandle);
Assert.Equal(nestedExceptionFooMethodTable, fooType2.TypeHandle);
Assert.Equal(appDomainsFooMethodTable, fooType.MethodTable);
Assert.Equal(nestedExceptionFooMethodTable, fooType2.MethodTable);
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ unsafe sealed class RuntimeBuilder : IRuntimeHelpers, ITypeFactory, ITypeHelpers
private readonly Dictionary<ulong, ClrAppDomain> _domains = new Dictionary<ulong, ClrAppDomain>();
private readonly Dictionary<ulong, ClrModule> _modules = new Dictionary<ulong, ClrModule>();

private ClrRuntime _runtime;
private ClrmdRuntime _runtime;
private ClrHeap _heap;

private readonly Dictionary<ulong, ClrType> _cache = new Dictionary<ulong, ClrType>();
Expand Down Expand Up @@ -483,6 +483,8 @@ void IRuntimeHelpers.FlushCachedData()

_moduleBuilder = new ModuleBuilder(this, _sos, moduleSizes);
}

_runtime.Initialize();
}

ulong IRuntimeHelpers.GetMethodDesc(ulong ip) => _sos.GetMethodDescPtrFromIP(ip);
Expand Down Expand Up @@ -747,7 +749,7 @@ ClrMethod[] ITypeFactory.CreateMethodsForType(ClrType type)
{
CheckDisposed();

ulong mt = type.TypeHandle;
ulong mt = type.MethodTable;
if (!_sos.GetMethodTableData(mt, out MethodTableData data))
return Array.Empty<ClrMethod>();

Expand Down Expand Up @@ -819,7 +821,7 @@ void CreateFieldsForMethodTableWorker(ClrType type, out IReadOnlyList<ClrInstanc
if (type.IsFree)
return;

if (!_sos.GetFieldInfo(type.TypeHandle, out V4FieldInfo fieldInfo) || fieldInfo.FirstFieldAddress == 0)
if (!_sos.GetFieldInfo(type.MethodTable, out V4FieldInfo fieldInfo) || fieldInfo.FirstFieldAddress == 0)
{
if (type.BaseType != null)
fields = type.BaseType.Fields;
Expand Down
27 changes: 27 additions & 0 deletions src/Microsoft.Diagnostics.Runtime/src/Common/ClrModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,5 +136,32 @@ public override string ToString()
/// Returns the pdb information for this module.
/// </summary>
public abstract PdbInfo Pdb { get; }


public override bool Equals(object obj)
{
if (obj is null)
return false;

if (obj is ClrModule module)
return Address == module.Address;

return false;
}

public override int GetHashCode() => Address.GetHashCode();

public static bool operator ==(ClrModule t1, ClrModule t2)
{
if (t1 is null)
return t2 is null;

return t1.Equals(t2);
}

public static bool operator !=(ClrModule item1, ClrModule item2)
{
return !(item1 == item2);
}
}
}
49 changes: 47 additions & 2 deletions src/Microsoft.Diagnostics.Runtime/src/Common/ClrType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ public abstract class ClrType
public abstract GCDesc GCDesc { get; }

/// <summary>
/// The type handle of this type (e.g. MethodTable).
/// The MethodTable of this type (this is the TypeHandle if this is a type without a MethodTable).
/// </summary>
public abstract ulong TypeHandle { get; }
public abstract ulong MethodTable { get; }

/// <summary>
/// Returns the metadata token of this type.
Expand Down Expand Up @@ -325,5 +325,50 @@ public virtual bool TryGetEnumValue(string name, out object value)
/// Used to provide functionality to ClrObject.
/// </summary>
public abstract IClrObjectHelpers ClrObjectHelpers { get; }

public override bool Equals(object obj)
{
if (obj is ClrType type)
{
if (MethodTable != 0 && type.MethodTable != 0)
return MethodTable == type.MethodTable;

if (type.IsPointer)
{
if (type.ComponentType == null)
return base.Equals(obj);

return ComponentType == type.ComponentType;
}

if (IsPrimitive && type.IsPrimitive && ElementType != ClrElementType.Unknown)
return ElementType == type.ElementType;

// Ok we aren't a primitive type, or a pointer, and our MethodTables are 0. Last resort is to
// check if we resolved from the same token out of the same module.
if (Module != null && MetadataToken != 0)
return Module == type.Module && MetadataToken == type.MetadataToken;

// Fall back to reference equality
return base.Equals(obj);
}

return false;
}

public override int GetHashCode() => MethodTable.GetHashCode();

public static bool operator ==(ClrType t1, ClrType t2)
{
if (t1 is null)
return t2 is null;

return t1.Equals(t2);
}

public static bool operator !=(ClrType item1, ClrType item2)
{
return !(item1 == item2);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public ClrmdConstructedType(ClrType componentType, int ranks, bool pointer)

// We have no good way of finding this value, unfortunately
public override ClrElementType ElementType { get; }
public override ulong TypeHandle => 0;
public override ulong MethodTable => 0;
public override bool IsFinalizeSuppressed(ulong obj) => false;
public override bool IsPointer => true;
public override IReadOnlyList<ClrInstanceField> Fields => Array.Empty<ClrInstanceField>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public ClrmdPrimitiveType(ITypeHelpers helpers, ClrModule module, ClrHeap heap,
public override bool IsPublic => false;
public override bool IsSealed => false;
public override uint MetadataToken => 0;
public override ulong TypeHandle => 0;
public override ulong MethodTable => 0;
public override string Name
{
get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public class ClrmdType : ClrType
private GCDesc _gcDesc;


public override string Name => _name ?? (_name = Helpers.GetTypeName(TypeHandle));
public override string Name => _name ?? (_name = Helpers.GetTypeName(MethodTable));

public override int BaseSize { get; }
public override int ComponentSize => 0;
Expand All @@ -44,7 +44,7 @@ public class ClrmdType : ClrType
public bool Shared { get; }
public override IClrObjectHelpers ClrObjectHelpers => Helpers.ClrObjectHelpers;

public override ulong TypeHandle { get; }
public override ulong MethodTable { get; }
public override ClrHeap Heap { get; }

public override ClrType BaseType { get; }
Expand All @@ -58,7 +58,7 @@ public ClrmdType(ClrHeap heap, ClrType baseType, ClrModule module, ITypeData dat
throw new ArgumentNullException(nameof(data));

Helpers = data.Helpers;
TypeHandle = data.MethodTable;
MethodTable = data.MethodTable;
Heap = heap;
BaseType = baseType;
Module = module;
Expand Down Expand Up @@ -91,8 +91,8 @@ private GCDesc GetOrCreateGCDesc()
if (reader == null)
return default;

Debug.Assert(TypeHandle != 0, "Attempted to fill GC desc with a constructed (not real) type.");
if (!reader.Read(TypeHandle - (ulong)IntPtr.Size, out int entries))
Debug.Assert(MethodTable != 0, "Attempted to fill GC desc with a constructed (not real) type.");
if (!reader.Read(MethodTable - (ulong)IntPtr.Size, out int entries))
{
_gcDesc = default;
return default;
Expand All @@ -104,7 +104,7 @@ private GCDesc GetOrCreateGCDesc()

int slots = 1 + entries * 2;
byte[] buffer = new byte[slots * IntPtr.Size];
if (!reader.ReadMemory(TypeHandle - (ulong)(slots * IntPtr.Size), buffer, out int read) || read != buffer.Length)
if (!reader.ReadMemory(MethodTable - (ulong)(slots * IntPtr.Size), buffer, out int read) || read != buffer.Length)
{
_gcDesc = default;
return default;
Expand Down Expand Up @@ -372,7 +372,7 @@ public override ulong LoaderAllocatorHandle
if (_loaderAllocatorHandle != ulong.MaxValue - 1)
return _loaderAllocatorHandle;

ulong handle = Helpers.GetLoaderAllocatorHandle(TypeHandle);
ulong handle = Helpers.GetLoaderAllocatorHandle(MethodTable);
_loaderAllocatorHandle = handle;
return handle;
}
Expand Down

0 comments on commit 22ebc3e

Please sign in to comment.