From 4802b70ab28c12e17400ba07c5d555da5dd7f7d3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Jul 2026 16:16:56 +0000 Subject: [PATCH 1/3] Initial plan From 0bc940ef4f21927e00e56a87567f0c808c339e86 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Jul 2026 16:27:09 +0000 Subject: [PATCH 2/3] Make ilverify respect IgnoresAccessChecksToAttribute Co-authored-by: JulieLeeMSFT <63486087+JulieLeeMSFT@users.noreply.github.com> --- .../ILTests/AccessTestsIgnoreChecks.il | 109 ++++++++++++++++++ .../ILTests/AccessTestsIgnoreChecks.ilproj | 9 ++ .../AccessVerificationHelpers.cs | 29 +++++ 3 files changed, 147 insertions(+) create mode 100644 src/coreclr/tools/ILVerification.Tests/ILTests/AccessTestsIgnoreChecks.il create mode 100644 src/coreclr/tools/ILVerification.Tests/ILTests/AccessTestsIgnoreChecks.ilproj diff --git a/src/coreclr/tools/ILVerification.Tests/ILTests/AccessTestsIgnoreChecks.il b/src/coreclr/tools/ILVerification.Tests/ILTests/AccessTestsIgnoreChecks.il new file mode 100644 index 00000000000000..424476f0e315a4 --- /dev/null +++ b/src/coreclr/tools/ILVerification.Tests/ILTests/AccessTestsIgnoreChecks.il @@ -0,0 +1,109 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +.assembly extern System.Runtime +{ +} + +.assembly extern AccessTests +{ +} + +.assembly AccessTestsIgnoreChecks +{ + // IgnoresAccessChecksTo("AccessTests") + .custom instance void System.Runtime.CompilerServices.IgnoresAccessChecksToAttribute::.ctor(string) = ( + 01 00 0b 41 63 63 65 73 73 54 65 73 74 73 00 00 + ) +} + +// The IgnoresAccessChecksToAttribute is not part of the BCL surface area; consumers of the +// feature (e.g. the IgnoresAccessChecksToGenerator) declare it in their own assembly. The +// runtime and ilverify recognize it purely by its namespace and name. +.class private auto ansi sealed beforefieldinit System.Runtime.CompilerServices.IgnoresAccessChecksToAttribute + extends [System.Runtime]System.Attribute +{ + .method public hidebysig specialname rtspecialname instance void .ctor(string assemblyName) cil managed + { + ldarg.0 + call instance void [System.Runtime]System.Attribute::.ctor() + ret + } +} + +.class public auto ansi beforefieldinit AccessTestsIgnoreChecksType + extends [System.Runtime]System.Object +{ + .method public hidebysig instance void Instantiate.PrivateClass_Valid() cil managed + { + newobj instance void [AccessTests]PrivateClass::.ctor() + pop + ret + } + + .method public hidebysig instance void Instantiate.PrivateNested_Valid() cil managed + { + newobj instance void [AccessTests]SimpleClass/PrivateNestedClass::.ctor() + pop + ret + } + + .method public hidebysig instance void Instantiate.AssemblyNested_Valid() cil managed + { + newobj instance void [AccessTests]SimpleClass/AssemblyNestedClass::.ctor() + pop + ret + } + + .method public hidebysig instance void Instantiate.FamilyNested_Valid() cil managed + { + newobj instance void [AccessTests]SimpleClass/FamilyNestedClass::.ctor() + pop + ret + } + + .method public hidebysig instance void Call.PrivateMethod_Valid() cil managed + { + call void [AccessTests]SimpleClass::PrivateMethod() + ret + } + + .method public hidebysig instance void Call.FamilyMethod_Valid() cil managed + { + call void [AccessTests]SimpleClass::FamilyMethod() + ret + } + + .method public hidebysig instance void Call.AssemblyMethod_Valid() cil managed + { + call void [AccessTests]SimpleClass::AssemblyMethod() + ret + } + + .method public hidebysig instance void Call.FamAndAssemMethod_Valid() cil managed + { + call void [AccessTests]SimpleClass::FamilyAndAssemblyMethod() + ret + } + + .method public hidebysig instance void Load.PrivateField_Valid() cil managed + { + ldsfld int32 [AccessTests]SimpleClass::privateField + pop + ret + } + + .method public hidebysig instance void Load.AssemblyField_Valid() cil managed + { + ldsfld int32 [AccessTests]SimpleClass::assemblyField + pop + ret + } + + .method public hidebysig instance void Load.PublicFieldOfPrivateClass_Valid() cil managed + { + ldsfld int32 [AccessTests]SimpleClass/PrivateNestedClass::publicField + pop + ret + } +} diff --git a/src/coreclr/tools/ILVerification.Tests/ILTests/AccessTestsIgnoreChecks.ilproj b/src/coreclr/tools/ILVerification.Tests/ILTests/AccessTestsIgnoreChecks.ilproj new file mode 100644 index 00000000000000..356b4dcc778989 --- /dev/null +++ b/src/coreclr/tools/ILVerification.Tests/ILTests/AccessTestsIgnoreChecks.ilproj @@ -0,0 +1,9 @@ + + + $(MSBuildProjectName) + + + + + + diff --git a/src/coreclr/tools/ILVerification/AccessVerificationHelpers.cs b/src/coreclr/tools/ILVerification/AccessVerificationHelpers.cs index 81cc18f0c7780a..3a4e7d83db7da9 100644 --- a/src/coreclr/tools/ILVerification/AccessVerificationHelpers.cs +++ b/src/coreclr/tools/ILVerification/AccessVerificationHelpers.cs @@ -37,6 +37,10 @@ internal static bool CanAccess(this TypeDesc currentClass, TypeDesc targetClass) var currentTypeDef = (MetadataType)currentClass.GetTypeDefinition(); var targetTypeDef = (EcmaType)targetClass.GetTypeDefinition(); + // The accessing assembly can waive all access checks to the target assembly via [IgnoresAccessChecksTo]. + if (currentTypeDef.Module.IgnoresAccessChecksTo(targetTypeDef.Module)) + return true; + var targetContainingType = targetTypeDef.ContainingType; if (targetContainingType == null) { @@ -110,6 +114,10 @@ private static bool CanAccessMember(this MetadataType currentType, TypeDesc targ if (memberVisibility == MethodAttributes.Public) return true; + // The accessing assembly can waive all access checks to the target assembly via [IgnoresAccessChecksTo]. + if (currentType.Module.IgnoresAccessChecksTo(targetTypeDef.Module)) + return true; + // This is module-scope checking, to support C++ file & function statics. if (memberVisibility == MethodAttributes.PrivateScope) return currentType.Module == targetTypeDef.Module; @@ -274,6 +282,27 @@ static ReadOnlySpan GetPublicKeyToken(AssemblyNameInfo assemblyName) return false; } + private static bool IgnoresAccessChecksTo(this ModuleDesc accessingModule, ModuleDesc accessedModule) + { + var accessingAssembly = accessingModule.ToEcmaAssembly(); + var accessedAssembly = accessedModule.ToEcmaAssembly(); + if (accessingAssembly == null || accessedAssembly == null || accessingAssembly == accessedAssembly) + { + return false; + } + + var accessedName = accessedAssembly.GetName(); + + foreach (var attribute in accessingAssembly.GetDecodedCustomAttributes("System.Runtime.CompilerServices", "IgnoresAccessChecksToAttribute")) + { + AssemblyNameInfo ignoredName = AssemblyNameInfo.Parse(((string)attribute.FixedArguments[0].Value).AsSpan()); + if (accessedName.Name.Equals(ignoredName.Name, StringComparison.OrdinalIgnoreCase)) + return true; + } + + return false; + } + private static MethodAttributes NestedToMethodAccessAttribute(TypeAttributes nestedVisibility) { switch (nestedVisibility & TypeAttributes.VisibilityMask) From 00431ad46bd1f7d880397854edc6645b2860b46b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Jul 2026 16:28:29 +0000 Subject: [PATCH 3/3] Clarify simple-name matching for IgnoresAccessChecksTo Co-authored-by: JulieLeeMSFT <63486087+JulieLeeMSFT@users.noreply.github.com> --- src/coreclr/tools/ILVerification/AccessVerificationHelpers.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/coreclr/tools/ILVerification/AccessVerificationHelpers.cs b/src/coreclr/tools/ILVerification/AccessVerificationHelpers.cs index 3a4e7d83db7da9..9825e0b3f5d4b6 100644 --- a/src/coreclr/tools/ILVerification/AccessVerificationHelpers.cs +++ b/src/coreclr/tools/ILVerification/AccessVerificationHelpers.cs @@ -295,6 +295,9 @@ private static bool IgnoresAccessChecksTo(this ModuleDesc accessingModule, Modul foreach (var attribute in accessingAssembly.GetDecodedCustomAttributes("System.Runtime.CompilerServices", "IgnoresAccessChecksToAttribute")) { + // The attribute argument is an assembly name; it is typically just the simple name. + // Matching on the simple name mirrors the runtime, which matches by name when the + // declared name omits a public key token (see FriendAssemblyDescriptor::IsAssemblyOnList). AssemblyNameInfo ignoredName = AssemblyNameInfo.Parse(((string)attribute.FixedArguments[0].Value).AsSpan()); if (accessedName.Name.Equals(ignoredName.Name, StringComparison.OrdinalIgnoreCase)) return true;