From 80302f45bf04ba1646a22fb843ced22accee2b93 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 20:51:46 +0000 Subject: [PATCH 1/8] Initial plan From 6e589e5601de8c8e1d542844e403b78c1cdafe39 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 21:49:35 +0000 Subject: [PATCH 2/8] Preserve invalid friend assembly diagnostics by removing lossy HRESULT roundtrip Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/406c2ee2-d0ea-4ba1-a45b-52ac7d68918e Co-authored-by: elinor-fung <47805090+elinor-fung@users.noreply.github.com> --- src/coreclr/vm/assembly.cpp | 8 ++---- .../InvalidFriendAssemblyName.cs | 25 +++++++++++++++++++ .../InvalidFriendAssemblyName.csproj | 6 +++++ 3 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 src/tests/Loader/classloader/regressions/InvalidFriendAssemblyName/InvalidFriendAssemblyName.cs create mode 100644 src/tests/Loader/classloader/regressions/InvalidFriendAssemblyName/InvalidFriendAssemblyName.csproj diff --git a/src/coreclr/vm/assembly.cpp b/src/coreclr/vm/assembly.cpp index 36c0084ef376fc..8c495ec2bde880 100644 --- a/src/coreclr/vm/assembly.cpp +++ b/src/coreclr/vm/assembly.cpp @@ -2590,12 +2590,8 @@ ReleaseHolder FriendAssemblyDescriptor::CreateFriendAs // Create an AssemblyNameObject from the string. FriendAssemblyNameHolder pFriendAssemblyName; pFriendAssemblyName = new FriendAssemblyName_t; - hr = pFriendAssemblyName->InitNoThrow(displayName); - - if (SUCCEEDED(hr)) - { - hr = pFriendAssemblyName->CheckFriendAssemblyName(); - } + pFriendAssemblyName->Init(displayName); + hr = pFriendAssemblyName->CheckFriendAssemblyName(); if (FAILED(hr)) { diff --git a/src/tests/Loader/classloader/regressions/InvalidFriendAssemblyName/InvalidFriendAssemblyName.cs b/src/tests/Loader/classloader/regressions/InvalidFriendAssemblyName/InvalidFriendAssemblyName.cs new file mode 100644 index 00000000000000..4a682118d1e5a7 --- /dev/null +++ b/src/tests/Loader/classloader/regressions/InvalidFriendAssemblyName/InvalidFriendAssemblyName.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.IO; +using System.Runtime.CompilerServices; +using Xunit; + +[assembly: InternalsVisibleTo("Broken,PublicKey=00240000048000009400000006020000002400005253413100040000010001002d07581667cbf8caf9786ac8d5257eb2c77eb2643a1af1bb89d4286e27a3ff5d805408c9f1a0a392d2f478ca2b9c68e43cdcdcea2d7cf0618dd8ba48858bf5fc74c7fc2af7a29f936398c0f61a2165d08ac4fcc8e8ad04e24633b7ca31a7c13f750ae75e53cea55ced97d3289fd100dbe2661a802bf8bd2acb0b6893ef3fb79c,PublicKeyToken=282361b99ded7e8e")] + +public class InvalidFriendAssemblyName +{ + private const string InvalidAssemblyName = "Broken,PublicKey=00240000048000009400000006020000002400005253413100040000010001002d07581667cbf8caf9786ac8d5257eb2c77eb2643a1af1bb89d4286e27a3ff5d805408c9f1a0a392d2f478ca2b9c68e43cdcdcea2d7cf0618dd8ba48858bf5fc74c7fc2af7a29f936398c0f61a2165d08ac4fcc8e8ad04e24633b7ca31a7c13f750ae75e53cea55ced97d3289fd100dbe2661a802bf8bd2acb0b6893ef3fb79c,PublicKeyToken=282361b99ded7e8e"; + + [Fact] + public static void TestEntryPoint() + { + FileLoadException exception = Assert.Throws(() => new InvalidFriendAssemblyName().GetCopy()); + + Assert.Equal(InvalidAssemblyName, exception.FileName); + Assert.Contains("assembly name was invalid", exception.Message, StringComparison.OrdinalIgnoreCase); + } + + public object GetCopy() => MemberwiseClone(); +} diff --git a/src/tests/Loader/classloader/regressions/InvalidFriendAssemblyName/InvalidFriendAssemblyName.csproj b/src/tests/Loader/classloader/regressions/InvalidFriendAssemblyName/InvalidFriendAssemblyName.csproj new file mode 100644 index 00000000000000..17cf9dbad70f50 --- /dev/null +++ b/src/tests/Loader/classloader/regressions/InvalidFriendAssemblyName/InvalidFriendAssemblyName.csproj @@ -0,0 +1,6 @@ + + + + + + From 6ab5020b2b2efe2d5acd994e9af6e296a3febf47 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Wed, 8 Jul 2026 15:08:21 -0700 Subject: [PATCH 3/8] Preserve invalid friend assembly name as inner exception Wrap the friend-assembly name parse failure so the underlying reason (the invalid assembly identity) is kept as the inner exception, while the outer FileLoadException reports which assembly declared the invalid InternalsVisibleTo/friend reference. Update the regression test to assert the outer exception identifies the declaring assembly and the inner exception carries the invalid name and message. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/assembly.cpp | 15 ++++++++++++++- .../InvalidFriendAssemblyName.cs | 9 +++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/coreclr/vm/assembly.cpp b/src/coreclr/vm/assembly.cpp index 8c495ec2bde880..8c054306b91c57 100644 --- a/src/coreclr/vm/assembly.cpp +++ b/src/coreclr/vm/assembly.cpp @@ -2590,7 +2590,20 @@ ReleaseHolder FriendAssemblyDescriptor::CreateFriendAs // Create an AssemblyNameObject from the string. FriendAssemblyNameHolder pFriendAssemblyName; pFriendAssemblyName = new FriendAssemblyName_t; - pFriendAssemblyName->Init(displayName); + EX_TRY + { + pFriendAssemblyName->Init(displayName); + } + EX_CATCH + { + // Preserve the underlying reason the friend assembly name could not be + // parsed (e.g. a malformed identity string) as the inner exception, while + // reporting the assembly that declared the invalid friend. + Exception *pInnerException = GET_EXCEPTION(); + EEFileLoadException::Throw(pAssembly, pInnerException->GetHR(), pInnerException); + } + EX_END_CATCH + hr = pFriendAssemblyName->CheckFriendAssemblyName(); if (FAILED(hr)) diff --git a/src/tests/Loader/classloader/regressions/InvalidFriendAssemblyName/InvalidFriendAssemblyName.cs b/src/tests/Loader/classloader/regressions/InvalidFriendAssemblyName/InvalidFriendAssemblyName.cs index 4a682118d1e5a7..47c7be59af4fa9 100644 --- a/src/tests/Loader/classloader/regressions/InvalidFriendAssemblyName/InvalidFriendAssemblyName.cs +++ b/src/tests/Loader/classloader/regressions/InvalidFriendAssemblyName/InvalidFriendAssemblyName.cs @@ -17,8 +17,13 @@ public static void TestEntryPoint() { FileLoadException exception = Assert.Throws(() => new InvalidFriendAssemblyName().GetCopy()); - Assert.Equal(InvalidAssemblyName, exception.FileName); - Assert.Contains("assembly name was invalid", exception.Message, StringComparison.OrdinalIgnoreCase); + // The outer exception identifies the assembly that declared the invalid friend reference. + Assert.Contains(nameof(InvalidFriendAssemblyName), exception.FileName, StringComparison.Ordinal); + + // The inner exception preserves the real cause: the invalid friend assembly name. + FileLoadException inner = Assert.IsType(exception.InnerException); + Assert.Equal(InvalidAssemblyName, inner.FileName); + Assert.Contains("assembly name was invalid", inner.Message, StringComparison.OrdinalIgnoreCase); } public object GetCopy() => MemberwiseClone(); From b3bc5b193f2a80d7628651b0f8fdb60eb94c4746 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Wed, 8 Jul 2026 15:55:40 -0700 Subject: [PATCH 4/8] Explain how the test triggers InternalsVisibleTo parsing Address review feedback: clarify that invoking the protected Object.MemberwiseClone drives the accessibility check that parses the invalid InternalsVisibleTo metadata. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../InvalidFriendAssemblyName/InvalidFriendAssemblyName.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tests/Loader/classloader/regressions/InvalidFriendAssemblyName/InvalidFriendAssemblyName.cs b/src/tests/Loader/classloader/regressions/InvalidFriendAssemblyName/InvalidFriendAssemblyName.cs index 47c7be59af4fa9..c2e9bcd3dbcda5 100644 --- a/src/tests/Loader/classloader/regressions/InvalidFriendAssemblyName/InvalidFriendAssemblyName.cs +++ b/src/tests/Loader/classloader/regressions/InvalidFriendAssemblyName/InvalidFriendAssemblyName.cs @@ -15,6 +15,9 @@ public class InvalidFriendAssemblyName [Fact] public static void TestEntryPoint() { + // Calling the protected Object.MemberwiseClone (defined in a different assembly) forces the + // runtime to run an accessibility check against this assembly, which parses its InternalsVisibleTo + // metadata. Because that metadata is invalid, parsing throws and surfaces the FileLoadException. FileLoadException exception = Assert.Throws(() => new InvalidFriendAssemblyName().GetCopy()); // The outer exception identifies the assembly that declared the invalid friend reference. From 72ff4624b4c59a41ca496e2a42690124a31cd6c6 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Wed, 8 Jul 2026 16:56:37 -0700 Subject: [PATCH 5/8] Restrict InvalidFriendAssemblyName test to CoreCLR Mono silently ignores invalid InternalsVisibleTo/friend assembly names rather than throwing, so this diagnostic behavior is CoreCLR-only. Gate the test with CLRTestTargetUnsupported to avoid failing Mono test legs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../InvalidFriendAssemblyName/InvalidFriendAssemblyName.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tests/Loader/classloader/regressions/InvalidFriendAssemblyName/InvalidFriendAssemblyName.csproj b/src/tests/Loader/classloader/regressions/InvalidFriendAssemblyName/InvalidFriendAssemblyName.csproj index 17cf9dbad70f50..ba3587c183819e 100644 --- a/src/tests/Loader/classloader/regressions/InvalidFriendAssemblyName/InvalidFriendAssemblyName.csproj +++ b/src/tests/Loader/classloader/regressions/InvalidFriendAssemblyName/InvalidFriendAssemblyName.csproj @@ -1,4 +1,7 @@ + + true + From 8739b07536d7f50dc1736f6a3d0d6352951a3e04 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Thu, 9 Jul 2026 12:29:13 -0700 Subject: [PATCH 6/8] Skip InvalidFriendAssemblyName test on Mono and NativeAOT The test asserts the CoreCLR-specific FileLoadException thrown when parsing an invalid InternalsVisibleTo friend name. The previous CLRTestTargetUnsupported (RuntimeFlavor != coreclr) gate only excluded Mono; NativeAOT test legs run with RuntimeFlavor=coreclr, so the test still ran there and failed because NativeAOT resolves accessibility at compile time and never throws. Use a ConditionalFact that runs only on CoreCLR JIT (not Mono, not NativeAOT). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: c29dbe26-1e58-4183-a5f0-28a9fa5fd3ff --- .../InvalidFriendAssemblyName/InvalidFriendAssemblyName.cs | 3 ++- .../InvalidFriendAssemblyName/InvalidFriendAssemblyName.csproj | 3 --- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/tests/Loader/classloader/regressions/InvalidFriendAssemblyName/InvalidFriendAssemblyName.cs b/src/tests/Loader/classloader/regressions/InvalidFriendAssemblyName/InvalidFriendAssemblyName.cs index c2e9bcd3dbcda5..01b56da13ed23e 100644 --- a/src/tests/Loader/classloader/regressions/InvalidFriendAssemblyName/InvalidFriendAssemblyName.cs +++ b/src/tests/Loader/classloader/regressions/InvalidFriendAssemblyName/InvalidFriendAssemblyName.cs @@ -12,7 +12,8 @@ public class InvalidFriendAssemblyName { private const string InvalidAssemblyName = "Broken,PublicKey=00240000048000009400000006020000002400005253413100040000010001002d07581667cbf8caf9786ac8d5257eb2c77eb2643a1af1bb89d4286e27a3ff5d805408c9f1a0a392d2f478ca2b9c68e43cdcdcea2d7cf0618dd8ba48858bf5fc74c7fc2af7a29f936398c0f61a2165d08ac4fcc8e8ad04e24633b7ca31a7c13f750ae75e53cea55ced97d3289fd100dbe2661a802bf8bd2acb0b6893ef3fb79c,PublicKeyToken=282361b99ded7e8e"; - [Fact] + // Only CoreCLR surfaces the invalid friend name as a FileLoadException. + [ConditionalFact(typeof(TestLibrary.Utilities), nameof(TestLibrary.Utilities.IsNotMonoRuntime), nameof(TestLibrary.Utilities.IsNotNativeAot))] public static void TestEntryPoint() { // Calling the protected Object.MemberwiseClone (defined in a different assembly) forces the diff --git a/src/tests/Loader/classloader/regressions/InvalidFriendAssemblyName/InvalidFriendAssemblyName.csproj b/src/tests/Loader/classloader/regressions/InvalidFriendAssemblyName/InvalidFriendAssemblyName.csproj index ba3587c183819e..17cf9dbad70f50 100644 --- a/src/tests/Loader/classloader/regressions/InvalidFriendAssemblyName/InvalidFriendAssemblyName.csproj +++ b/src/tests/Loader/classloader/regressions/InvalidFriendAssemblyName/InvalidFriendAssemblyName.csproj @@ -1,7 +1,4 @@ - - true - From c6094e762f089358abaade8a0bc6cf098ccc82fd Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Thu, 9 Jul 2026 14:33:09 -0700 Subject: [PATCH 7/8] Rethrow transient exceptions instead of wrapping invalid friend name EEFileLoadException::Throw asserts its inner exception is not transient. The friend-name parse can fail with a transient exception (e.g. OutOfMemoryException), which would trip that assertion or incorrectly report a transient failure as a load failure. Switch the catch to EX_HOOK and only wrap non-transient exceptions; transient exceptions are rethrown unchanged, matching the pattern in PEAssembly::OpenSystem. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: c29dbe26-1e58-4183-a5f0-28a9fa5fd3ff --- src/coreclr/vm/assembly.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/coreclr/vm/assembly.cpp b/src/coreclr/vm/assembly.cpp index 8c054306b91c57..272339f8aad971 100644 --- a/src/coreclr/vm/assembly.cpp +++ b/src/coreclr/vm/assembly.cpp @@ -2594,15 +2594,16 @@ ReleaseHolder FriendAssemblyDescriptor::CreateFriendAs { pFriendAssemblyName->Init(displayName); } - EX_CATCH + EX_HOOK { // Preserve the underlying reason the friend assembly name could not be // parsed (e.g. a malformed identity string) as the inner exception, while // reporting the assembly that declared the invalid friend. Exception *pInnerException = GET_EXCEPTION(); - EEFileLoadException::Throw(pAssembly, pInnerException->GetHR(), pInnerException); + if (!pInnerException->IsTransient()) + EEFileLoadException::Throw(pAssembly, pInnerException->GetHR(), pInnerException); } - EX_END_CATCH + EX_END_HOOK hr = pFriendAssemblyName->CheckFriendAssemblyName(); From 33bf7282364331867832a83d71a5f11db013a0dc Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Thu, 9 Jul 2026 18:04:19 -0700 Subject: [PATCH 8/8] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../InvalidFriendAssemblyName/InvalidFriendAssemblyName.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tests/Loader/classloader/regressions/InvalidFriendAssemblyName/InvalidFriendAssemblyName.cs b/src/tests/Loader/classloader/regressions/InvalidFriendAssemblyName/InvalidFriendAssemblyName.cs index 01b56da13ed23e..e74e0aaf79a009 100644 --- a/src/tests/Loader/classloader/regressions/InvalidFriendAssemblyName/InvalidFriendAssemblyName.cs +++ b/src/tests/Loader/classloader/regressions/InvalidFriendAssemblyName/InvalidFriendAssemblyName.cs @@ -6,11 +6,11 @@ using System.Runtime.CompilerServices; using Xunit; -[assembly: InternalsVisibleTo("Broken,PublicKey=00240000048000009400000006020000002400005253413100040000010001002d07581667cbf8caf9786ac8d5257eb2c77eb2643a1af1bb89d4286e27a3ff5d805408c9f1a0a392d2f478ca2b9c68e43cdcdcea2d7cf0618dd8ba48858bf5fc74c7fc2af7a29f936398c0f61a2165d08ac4fcc8e8ad04e24633b7ca31a7c13f750ae75e53cea55ced97d3289fd100dbe2661a802bf8bd2acb0b6893ef3fb79c,PublicKeyToken=282361b99ded7e8e")] +[assembly: InternalsVisibleTo(InvalidFriendAssemblyName.InvalidAssemblyName)] public class InvalidFriendAssemblyName { - private const string InvalidAssemblyName = "Broken,PublicKey=00240000048000009400000006020000002400005253413100040000010001002d07581667cbf8caf9786ac8d5257eb2c77eb2643a1af1bb89d4286e27a3ff5d805408c9f1a0a392d2f478ca2b9c68e43cdcdcea2d7cf0618dd8ba48858bf5fc74c7fc2af7a29f936398c0f61a2165d08ac4fcc8e8ad04e24633b7ca31a7c13f750ae75e53cea55ced97d3289fd100dbe2661a802bf8bd2acb0b6893ef3fb79c,PublicKeyToken=282361b99ded7e8e"; + internal const string InvalidAssemblyName = "Broken,PublicKey=00240000048000009400000006020000002400005253413100040000010001002d07581667cbf8caf9786ac8d5257eb2c77eb2643a1af1bb89d4286e27a3ff5d805408c9f1a0a392d2f478ca2b9c68e43cdcdcea2d7cf0618dd8ba48858bf5fc74c7fc2af7a29f936398c0f61a2165d08ac4fcc8e8ad04e24633b7ca31a7c13f750ae75e53cea55ced97d3289fd100dbe2661a802bf8bd2acb0b6893ef3fb79c,PublicKeyToken=282361b99ded7e8e"; // Only CoreCLR surfaces the invalid friend name as a FileLoadException. [ConditionalFact(typeof(TestLibrary.Utilities), nameof(TestLibrary.Utilities.IsNotMonoRuntime), nameof(TestLibrary.Utilities.IsNotNativeAot))]