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..9825e0b3f5d4b6 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,30 @@ 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")) + { + // 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; + } + + return false; + } + private static MethodAttributes NestedToMethodAccessAttribute(TypeAttributes nestedVisibility) { switch (nestedVisibility & TypeAttributes.VisibilityMask)