From b688819e9312d9b672200616325d31d0c45b8eb6 Mon Sep 17 00:00:00 2001 From: Lee Culver Date: Wed, 4 Dec 2019 16:05:23 -0800 Subject: [PATCH 1/4] Implement type equality --- .../src/TypeTests.cs | 22 +++++++++ .../src/Common/ClrType.cs | 45 +++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/src/Microsoft.Diagnostics.Runtime.Tests/src/TypeTests.cs b/src/Microsoft.Diagnostics.Runtime.Tests/src/TypeTests.cs index ee0240f17..13e4a503d 100644 --- a/src/Microsoft.Diagnostics.Runtime.Tests/src/TypeTests.cs +++ b/src/Microsoft.Diagnostics.Runtime.Tests/src/TypeTests.cs @@ -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] diff --git a/src/Microsoft.Diagnostics.Runtime/src/Common/ClrType.cs b/src/Microsoft.Diagnostics.Runtime/src/Common/ClrType.cs index 48d9b0b22..29e95d655 100644 --- a/src/Microsoft.Diagnostics.Runtime/src/Common/ClrType.cs +++ b/src/Microsoft.Diagnostics.Runtime/src/Common/ClrType.cs @@ -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 (TypeHandle != 0 && type.TypeHandle != 0) + return TypeHandle == type.TypeHandle; + + 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() => TypeHandle.GetHashCode(); + + public static bool operator ==(ClrType t1, ClrType t2) + { + if (t1 == null) + return t2 == null; + + return t1.Equals(t2); + } + + public static bool operator !=(ClrType item1, ClrType item2) + { + return !(item1 == item2); + } } } From a8256967e6ccf75b87402de214150efab74f6ae6 Mon Sep 17 00:00:00 2001 From: Lee Culver Date: Wed, 4 Dec 2019 16:06:38 -0800 Subject: [PATCH 2/4] Rename TypeHandle back to MethodTable It'll just be too confusing. --- .../src/RuntimeTests.cs | 2 +- .../src/TypeTests.cs | 16 ++++++++-------- .../src/Builders/RuntimeBuilder.cs | 4 ++-- .../src/Common/ClrType.cs | 10 +++++----- .../src/Implementation/ClrmdConstructedType.cs | 2 +- .../src/Implementation/ClrmdPrimitiveType.cs | 2 +- .../src/Implementation/ClrmdType.cs | 14 +++++++------- 7 files changed, 25 insertions(+), 25 deletions(-) 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 13e4a503d..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); @@ -199,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; @@ -208,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); } } @@ -238,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); } } @@ -274,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..7b6e7fc25 100644 --- a/src/Microsoft.Diagnostics.Runtime/src/Builders/RuntimeBuilder.cs +++ b/src/Microsoft.Diagnostics.Runtime/src/Builders/RuntimeBuilder.cs @@ -747,7 +747,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 +819,7 @@ void CreateFieldsForMethodTableWorker(ClrType type, out IReadOnlyList - /// 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. @@ -330,8 +330,8 @@ public override bool Equals(object obj) { if (obj is ClrType type) { - if (TypeHandle != 0 && type.TypeHandle != 0) - return TypeHandle == type.TypeHandle; + if (MethodTable != 0 && type.MethodTable != 0) + return MethodTable == type.MethodTable; if (type.IsPointer) { @@ -356,7 +356,7 @@ public override bool Equals(object obj) return false; } - public override int GetHashCode() => TypeHandle.GetHashCode(); + public override int GetHashCode() => MethodTable.GetHashCode(); public static bool operator ==(ClrType t1, ClrType t2) { 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; } From 871fa74be72d71edd2e6291e679d85eed8bbab9b Mon Sep 17 00:00:00 2001 From: Lee Culver Date: Wed, 4 Dec 2019 16:24:07 -0800 Subject: [PATCH 3/4] Add module equality --- .../src/ModuleTests.cs | 21 +++++++++++++++ .../src/Builders/RuntimeBuilder.cs | 4 ++- .../src/Common/ClrModule.cs | 27 +++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) 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/src/Builders/RuntimeBuilder.cs b/src/Microsoft.Diagnostics.Runtime/src/Builders/RuntimeBuilder.cs index 7b6e7fc25..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); diff --git a/src/Microsoft.Diagnostics.Runtime/src/Common/ClrModule.cs b/src/Microsoft.Diagnostics.Runtime/src/Common/ClrModule.cs index 2b2b3c4f0..d7b31e1ab 100644 --- a/src/Microsoft.Diagnostics.Runtime/src/Common/ClrModule.cs +++ b/src/Microsoft.Diagnostics.Runtime/src/Common/ClrModule.cs @@ -136,5 +136,32 @@ public override string ToString() /// Returns the pdb information for this module. /// 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 == null) + return t2 == null; + + return t1.Equals(t2); + } + + public static bool operator !=(ClrModule item1, ClrModule item2) + { + return !(item1 == item2); + } } } \ No newline at end of file From 98dedb45656fb69ca3b0ba85cb4be6c3c5852e76 Mon Sep 17 00:00:00 2001 From: Lee Culver Date: Wed, 4 Dec 2019 16:47:55 -0800 Subject: [PATCH 4/4] Use is null instead of == null Fix stack overflow caused by recursive operator ==. --- src/Microsoft.Diagnostics.Runtime/src/Common/ClrModule.cs | 4 ++-- src/Microsoft.Diagnostics.Runtime/src/Common/ClrType.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.Diagnostics.Runtime/src/Common/ClrModule.cs b/src/Microsoft.Diagnostics.Runtime/src/Common/ClrModule.cs index d7b31e1ab..af39890e7 100644 --- a/src/Microsoft.Diagnostics.Runtime/src/Common/ClrModule.cs +++ b/src/Microsoft.Diagnostics.Runtime/src/Common/ClrModule.cs @@ -153,8 +153,8 @@ public override bool Equals(object obj) public static bool operator ==(ClrModule t1, ClrModule t2) { - if (t1 == null) - return t2 == null; + if (t1 is null) + return t2 is null; return t1.Equals(t2); } diff --git a/src/Microsoft.Diagnostics.Runtime/src/Common/ClrType.cs b/src/Microsoft.Diagnostics.Runtime/src/Common/ClrType.cs index cb4491e2c..f8c7ad1b3 100644 --- a/src/Microsoft.Diagnostics.Runtime/src/Common/ClrType.cs +++ b/src/Microsoft.Diagnostics.Runtime/src/Common/ClrType.cs @@ -360,8 +360,8 @@ public override bool Equals(object obj) public static bool operator ==(ClrType t1, ClrType t2) { - if (t1 == null) - return t2 == null; + if (t1 is null) + return t2 is null; return t1.Equals(t2); }