diff --git a/src/Microsoft.Diagnostics.Runtime.Tests/src/ModuleTests.cs b/src/Microsoft.Diagnostics.Runtime.Tests/src/ModuleTests.cs index d9bff8837..8604d71a0 100644 --- a/src/Microsoft.Diagnostics.Runtime.Tests/src/ModuleTests.cs +++ b/src/Microsoft.Diagnostics.Runtime.Tests/src/ModuleTests.cs @@ -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]); + } + } } } \ No newline at end of file diff --git a/src/Microsoft.Diagnostics.Runtime.Tests/src/RuntimeTests.cs b/src/Microsoft.Diagnostics.Runtime.Tests/src/RuntimeTests.cs index 9e5eb739b..f0d593317 100644 --- a/src/Microsoft.Diagnostics.Runtime.Tests/src/RuntimeTests.cs +++ b/src/Microsoft.Diagnostics.Runtime.Tests/src/RuntimeTests.cs @@ -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); diff --git a/src/Microsoft.Diagnostics.Runtime.Tests/src/TypeTests.cs b/src/Microsoft.Diagnostics.Runtime.Tests/src/TypeTests.cs index ee0240f17..e56c69bbc 100644 --- a/src/Microsoft.Diagnostics.Runtime.Tests/src/TypeTests.cs +++ b/src/Microsoft.Diagnostics.Runtime.Tests/src/TypeTests.cs @@ -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); @@ -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] @@ -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; @@ -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); } } @@ -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); } } @@ -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] diff --git a/src/Microsoft.Diagnostics.Runtime/src/Builders/RuntimeBuilder.cs b/src/Microsoft.Diagnostics.Runtime/src/Builders/RuntimeBuilder.cs index 4e0181f87..c5eee9b4d 100644 --- a/src/Microsoft.Diagnostics.Runtime/src/Builders/RuntimeBuilder.cs +++ b/src/Microsoft.Diagnostics.Runtime/src/Builders/RuntimeBuilder.cs @@ -33,7 +33,7 @@ unsafe sealed class RuntimeBuilder : IRuntimeHelpers, ITypeFactory, ITypeHelpers private readonly Dictionary _domains = new Dictionary(); private readonly Dictionary _modules = new Dictionary(); - private ClrRuntime _runtime; + private ClrmdRuntime _runtime; private ClrHeap _heap; private readonly Dictionary _cache = new Dictionary(); @@ -483,6 +483,8 @@ void IRuntimeHelpers.FlushCachedData() _moduleBuilder = new ModuleBuilder(this, _sos, moduleSizes); } + + _runtime.Initialize(); } ulong IRuntimeHelpers.GetMethodDesc(ulong ip) => _sos.GetMethodDescPtrFromIP(ip); @@ -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(); @@ -819,7 +821,7 @@ void CreateFieldsForMethodTableWorker(ClrType type, out IReadOnlyList 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); + } } } \ No newline at end of file diff --git a/src/Microsoft.Diagnostics.Runtime/src/Common/ClrType.cs b/src/Microsoft.Diagnostics.Runtime/src/Common/ClrType.cs index 48d9b0b22..f8c7ad1b3 100644 --- a/src/Microsoft.Diagnostics.Runtime/src/Common/ClrType.cs +++ b/src/Microsoft.Diagnostics.Runtime/src/Common/ClrType.cs @@ -20,9 +20,9 @@ public abstract class ClrType public abstract GCDesc GCDesc { get; } /// - /// 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). /// - public abstract ulong TypeHandle { get; } + public abstract ulong MethodTable { get; } /// /// Returns the metadata token of this type. @@ -325,5 +325,50 @@ public virtual bool TryGetEnumValue(string name, out object value) /// Used to provide functionality to ClrObject. /// 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); + } } } diff --git a/src/Microsoft.Diagnostics.Runtime/src/Implementation/ClrmdConstructedType.cs b/src/Microsoft.Diagnostics.Runtime/src/Implementation/ClrmdConstructedType.cs index 57ecf41e8..3771142c6 100644 --- a/src/Microsoft.Diagnostics.Runtime/src/Implementation/ClrmdConstructedType.cs +++ b/src/Microsoft.Diagnostics.Runtime/src/Implementation/ClrmdConstructedType.cs @@ -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 Fields => Array.Empty(); diff --git a/src/Microsoft.Diagnostics.Runtime/src/Implementation/ClrmdPrimitiveType.cs b/src/Microsoft.Diagnostics.Runtime/src/Implementation/ClrmdPrimitiveType.cs index ab5d1cad9..c426b7ba2 100644 --- a/src/Microsoft.Diagnostics.Runtime/src/Implementation/ClrmdPrimitiveType.cs +++ b/src/Microsoft.Diagnostics.Runtime/src/Implementation/ClrmdPrimitiveType.cs @@ -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 diff --git a/src/Microsoft.Diagnostics.Runtime/src/Implementation/ClrmdType.cs b/src/Microsoft.Diagnostics.Runtime/src/Implementation/ClrmdType.cs index aeda3ec17..2a24839b7 100644 --- a/src/Microsoft.Diagnostics.Runtime/src/Implementation/ClrmdType.cs +++ b/src/Microsoft.Diagnostics.Runtime/src/Implementation/ClrmdType.cs @@ -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; @@ -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; } @@ -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; @@ -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; @@ -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; @@ -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; }