From d954d3fde5afa5e73afb2d1218b8fd068a56eeac Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 22:12:44 +0000 Subject: [PATCH 1/2] Initial plan From dff0ee0cfe63b97b50f53404655ff1f8fdc53f11 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 22:42:35 +0000 Subject: [PATCH 2/2] Replace reflection with UnsafeAccessor in TarReader.SparseFile.Tests.cs Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/26cfdebb-c9b4-43c7-908e-b1d9ce2f1cd6 Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com> --- .../TarReader/TarReader.SparseFile.Tests.cs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.SparseFile.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.SparseFile.Tests.cs index 29c87ea91ae70f..9aa19894fea1db 100644 --- a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.SparseFile.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.SparseFile.Tests.cs @@ -2,8 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; -using System.Reflection; +using System.Runtime.CompilerServices; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -34,13 +35,10 @@ private static void WriteSparseEntry(TarWriter writer, string realName, long rea ["GNU.sparse.name"] = realName, }; var entry = new PaxTarEntry(TarEntryType.RegularFile, "GNUSparseFile.0/" + realName, gnuSparseAttributes); - // Inject GNU.sparse.realsize into the internal _header.ExtendedAttributes dictionary via - // reflection, bypassing the public ReadOnlyDictionary façade and constructor validation. - // This allows intentionally-invalid archives (negative realsize) to be constructed for tests. - var headerField = typeof(TarEntry).GetField("_header", BindingFlags.NonPublic | BindingFlags.Instance)!; - var header = headerField.GetValue(entry)!; - var eaProp = header.GetType().GetProperty("ExtendedAttributes", BindingFlags.NonPublic | BindingFlags.Instance)!; - var ea = (Dictionary)eaProp.GetValue(header)!; + // Inject GNU.sparse.realsize into the internal EA dictionary via UnsafeAccessor, bypassing + // the public ReadOnlyDictionary façade and constructor validation. This allows + // intentionally-invalid archives (negative realsize) to be constructed for tests. + var ea = (Dictionary)ReadOnlyDictionaryAccessors.GetInnerDictionary((ReadOnlyDictionary)entry.ExtendedAttributes); ea["GNU.sparse.realsize"] = realSize.ToString(); entry.DataStream = new MemoryStream(rawSparseData); writer.WriteEntry(entry); @@ -546,6 +544,12 @@ private static void VerifyExpandedContent(byte[] buf, long realSize, (long Offse Assert.Equal(0, buf[i]); } } + + private static class ReadOnlyDictionaryAccessors where TKey : notnull + { + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "m_dictionary")] + public static extern ref IDictionary GetInnerDictionary(ReadOnlyDictionary d); + } } // Minimal non-seekable stream wrapper for testing.