From 5dc87d16f569f42648e54939f7366888b18d22d6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 May 2025 20:31:45 +0000 Subject: [PATCH 1/5] Initial plan for issue From 4b14248bda48efb05e4036f6a92bbff986343c64 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 May 2025 20:37:31 +0000 Subject: [PATCH 2/5] Initial plan for adding thorough tests for partially supported NRBF types Co-authored-by: JeremyKuhne <8184940+JeremyKuhne@users.noreply.github.com> --- global.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/global.json b/global.json index 42f6f940fc7..2055f514d23 100644 --- a/global.json +++ b/global.json @@ -11,7 +11,8 @@ } }, "sdk": { - "version": "10.0.100-preview.5.25265.106" + "version": "8.0.115", + "rollForward": "latestMajor" }, "msbuild-sdks": { "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.25267.102", From 05f3876f337e5ec4e3ae474ef6359d0d952087f3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 May 2025 20:42:36 +0000 Subject: [PATCH 3/5] Add thorough tests for partially supported NRBF types Co-authored-by: JeremyKuhne <8184940+JeremyKuhne@users.noreply.github.com> --- .../Windows/Nrbf/CoreNrbfSerializerTests.cs | 181 +++++++++++++++++- 1 file changed, 178 insertions(+), 3 deletions(-) diff --git a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Nrbf/CoreNrbfSerializerTests.cs b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Nrbf/CoreNrbfSerializerTests.cs index ff49f9ab22c..0796744d49f 100644 --- a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Nrbf/CoreNrbfSerializerTests.cs +++ b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Nrbf/CoreNrbfSerializerTests.cs @@ -1,9 +1,12 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Formats.Nrbf; using System.Reflection.Metadata; using System.Runtime.Serialization.Formatters.Binary; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace System.Private.Windows.Nrbf; @@ -13,7 +16,9 @@ public class CoreNrbfSerializerTests { { 123, true }, { "test", true }, - { new object(), false } + { new object(), false }, + { new Hashtable() { { "key", "value" } }, true }, + { new Hashtable(StringComparer.OrdinalIgnoreCase) { { "key", "value" } }, true } }; [Theory] @@ -94,4 +99,174 @@ public void IsSupportedType_ShouldReturnExpectedResult(Type type, bool expectedR { CoreNrbfSerializer.IsFullySupportedType(type).Should().Be(expectedResult); } -} + + [Fact] + public void HashtableType_IsNotFullySupportedType() + { + // Hashtable is not fully supported but can be round-tripped through TryWriteObject/TryGetObject + CoreNrbfSerializer.IsFullySupportedType(typeof(Hashtable)).Should().BeFalse(); + } + + [Theory] + [MemberData(nameof(HashtableTestData))] + public void Hashtable_TryWriteObject_TryGetObject_RoundTrip(Hashtable hashtable, bool expectSuccessfulDeserialization) + { + using MemoryStream stream = new(); + bool result = CoreNrbfSerializer.TryWriteObject(stream, hashtable); + result.Should().BeTrue(); // All Hashtables should be serializable + + stream.Position = 0; + SerializationRecord record = NrbfDecoder.Decode(stream); + bool deserializationResult = CoreNrbfSerializer.TryGetObject(record, out object? value); + deserializationResult.Should().Be(expectSuccessfulDeserialization); + + // When deserialization is expected to succeed, verify the key-value pairs + if (expectSuccessfulDeserialization) + { + value.Should().BeOfType(); + Hashtable? deserializedTable = value as Hashtable; + deserializedTable!.Count.Should().Be(hashtable.Count); + + foreach (DictionaryEntry entry in hashtable) + { + deserializedTable.Contains(entry.Key).Should().BeTrue(); + deserializedTable[entry.Key].Should().Be(entry.Value); + } + } + } + + [Theory] + [MemberData(nameof(HashtableTestData))] + public void BinaryFormatter_Hashtable_TryGetObject_RoundTrip(Hashtable hashtable, bool expectSuccessfulDeserialization) + { + using MemoryStream stream = new(); + using (BinaryFormatterScope scope = new(enable: true)) + { +#pragma warning disable SYSLIB0011 // Type or member is obsolete + new BinaryFormatter().Serialize(stream, hashtable); +#pragma warning restore SYSLIB0011 + } + + stream.Position = 0; + SerializationRecord record = NrbfDecoder.Decode(stream, leaveOpen: true); + bool deserializationResult = CoreNrbfSerializer.TryGetObject(record, out object? value); + deserializationResult.Should().Be(expectSuccessfulDeserialization); + + // When deserialization is expected to succeed, verify the key-value pairs + if (expectSuccessfulDeserialization) + { + value.Should().BeOfType(); + Hashtable? deserializedTable = value as Hashtable; + deserializedTable!.Count.Should().Be(hashtable.Count); + + foreach (DictionaryEntry entry in hashtable) + { + deserializedTable.Contains(entry.Key).Should().BeTrue(); + deserializedTable[entry.Key].Should().Be(entry.Value); + } + } + } + + public static TheoryData HashtableTestData => new() + { + // Standard hashtable should deserialize correctly + { new Hashtable() { { "key", "value" } }, true }, + { new Hashtable() { { 1, 2 }, { "text", 42 } }, true }, + + // Hashtable with custom comparer won't be deserialized by TryGetPrimitiveHashtable + { new Hashtable(StringComparer.OrdinalIgnoreCase) { { "key", "value" } }, false }, + { new Hashtable(StringComparer.CurrentCulture) { { "key", "value" } }, false }, + + // Hashtable with hash code provider won't be deserialized by TryGetPrimitiveHashtable + { new Hashtable(new CustomHashCodeProvider()) { { "key", "value" } }, false }, + { new Hashtable(new CustomHashCodeProvider(), StringComparer.OrdinalIgnoreCase) { { "key", "value" } }, false } + }; + + [Fact] + public void HashtableWithCustomComparer_PreservesData_EvenWhenNotDeserialized() + { + // Create a hashtable with a custom comparer + Hashtable originalHashtable = new(StringComparer.OrdinalIgnoreCase) + { + { "Key", "Value" } + }; + + // Serialize with BinaryFormatter + using MemoryStream stream = new(); + using (BinaryFormatterScope scope = new(enable: true)) + { +#pragma warning disable SYSLIB0011 // Type or member is obsolete + new BinaryFormatter().Serialize(stream, originalHashtable); +#pragma warning restore SYSLIB0011 + } + + // First verify that CoreNrbfSerializer can't deserialize it + stream.Position = 0; + SerializationRecord record = NrbfDecoder.Decode(stream, leaveOpen: true); + CoreNrbfSerializer.TryGetObject(record, out _).Should().BeFalse(); + + // Now verify that BinaryFormatter can deserialize it with all data intact + stream.Position = 0; + using (BinaryFormatterScope scope = new(enable: true)) + { +#pragma warning disable SYSLIB0011 // Type or member is obsolete + Hashtable deserializedHashtable = (Hashtable)new BinaryFormatter().Deserialize(stream); +#pragma warning restore SYSLIB0011 + + // Verify key-value data + deserializedHashtable.Count.Should().Be(originalHashtable.Count); + deserializedHashtable["Key"].Should().Be("Value"); + + // Verify case-insensitivity was preserved (comparer was not lost) + deserializedHashtable["key"].Should().Be("Value"); + } + } + + [Fact] + public void HashtableWithHashCodeProvider_PreservesData_EvenWhenNotDeserialized() + { + // Create a hashtable with a custom hash code provider + Hashtable originalHashtable = new(new CustomHashCodeProvider()) + { + { "Key", "Value" } + }; + + // Serialize with BinaryFormatter + using MemoryStream stream = new(); + using (BinaryFormatterScope scope = new(enable: true)) + { +#pragma warning disable SYSLIB0011 // Type or member is obsolete + new BinaryFormatter().Serialize(stream, originalHashtable); +#pragma warning restore SYSLIB0011 + } + + // First verify that CoreNrbfSerializer can't deserialize it + stream.Position = 0; + SerializationRecord record = NrbfDecoder.Decode(stream, leaveOpen: true); + CoreNrbfSerializer.TryGetObject(record, out _).Should().BeFalse(); + + // Now verify that BinaryFormatter can deserialize it with all data intact + stream.Position = 0; + using (BinaryFormatterScope scope = new(enable: true)) + { +#pragma warning disable SYSLIB0011 // Type or member is obsolete + Hashtable deserializedHashtable = (Hashtable)new BinaryFormatter().Deserialize(stream); +#pragma warning restore SYSLIB0011 + + // Verify key-value data + deserializedHashtable.Count.Should().Be(originalHashtable.Count); + deserializedHashtable["Key"].Should().Be("Value"); + + // Verify the hash code provider was preserved + deserializedHashtable.GetType().GetField("_keycomparer", + System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic) + .Should().NotBeNull(); + } + } + + [Serializable] + private class CustomHashCodeProvider : IHashCodeProvider + { + public int GetHashCode(object obj) => obj?.GetHashCode() ?? 0; + } +} \ No newline at end of file From d182b49db8d1d276399ea2340dec0239d6cb0597 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 May 2025 20:57:22 +0000 Subject: [PATCH 4/5] Revert changes to global.json Co-authored-by: JeremyKuhne <8184940+JeremyKuhne@users.noreply.github.com> --- global.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/global.json b/global.json index 2055f514d23..42f6f940fc7 100644 --- a/global.json +++ b/global.json @@ -11,8 +11,7 @@ } }, "sdk": { - "version": "8.0.115", - "rollForward": "latestMajor" + "version": "10.0.100-preview.5.25265.106" }, "msbuild-sdks": { "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.25267.102", From 7973629c19bf1dbdd41815aac530f97b1fda9393 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 May 2025 21:28:23 +0000 Subject: [PATCH 5/5] Fix HashCodeProvider and constructor overload issues Co-authored-by: JeremyKuhne <8184940+JeremyKuhne@users.noreply.github.com> --- .../Private/Windows/Nrbf/CoreNrbfSerializerTests.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Nrbf/CoreNrbfSerializerTests.cs b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Nrbf/CoreNrbfSerializerTests.cs index 0796744d49f..fd6a7d1c05e 100644 --- a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Nrbf/CoreNrbfSerializerTests.cs +++ b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Nrbf/CoreNrbfSerializerTests.cs @@ -10,6 +10,9 @@ namespace System.Private.Windows.Nrbf; +// Suppress CS0618 warnings for the entire class as we're deliberately testing backward compatibility +// with obsolete APIs like IHashCodeProvider to ensure proper serialization handling +#pragma warning disable CS0618 public class CoreNrbfSerializerTests { public static TheoryData TryWriteObjectData => new() @@ -178,7 +181,7 @@ public void BinaryFormatter_Hashtable_TryGetObject_RoundTrip(Hashtable hashtable { new Hashtable(StringComparer.CurrentCulture) { { "key", "value" } }, false }, // Hashtable with hash code provider won't be deserialized by TryGetPrimitiveHashtable - { new Hashtable(new CustomHashCodeProvider()) { { "key", "value" } }, false }, + { new Hashtable(new CustomHashCodeProvider(), null) { { "key", "value" } }, false }, { new Hashtable(new CustomHashCodeProvider(), StringComparer.OrdinalIgnoreCase) { { "key", "value" } }, false } }; @@ -226,7 +229,7 @@ public void HashtableWithCustomComparer_PreservesData_EvenWhenNotDeserialized() public void HashtableWithHashCodeProvider_PreservesData_EvenWhenNotDeserialized() { // Create a hashtable with a custom hash code provider - Hashtable originalHashtable = new(new CustomHashCodeProvider()) + Hashtable originalHashtable = new(new CustomHashCodeProvider(), null) { { "Key", "Value" } }; @@ -269,4 +272,5 @@ private class CustomHashCodeProvider : IHashCodeProvider { public int GetHashCode(object obj) => obj?.GetHashCode() ?? 0; } -} \ No newline at end of file +} +#pragma warning restore CS0618 \ No newline at end of file