Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk.IL">
<PropertyGroup>
<AssemblyName>$(MSBuildProjectName)</AssemblyName>
</PropertyGroup>

<ItemGroup>
<Compile Include="$(MSBuildProjectName).il" />
</ItemGroup>
</Project>
32 changes: 32 additions & 0 deletions src/coreclr/tools/ILVerification/AccessVerificationHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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].

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very rare case - check of last resort, and it is somewhat expensive due to custom attribute lookup and parsing. It should be done last when all other ways to prove accessibility failed.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does ILVerify performance matter to this extent? I'd have assumed the overhead to be fine enough for it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is also about matching the runtime behavior. Runtime checks both IgnoresAccessChecksTo and InternalsVisibleTo attributes last. It is not obvious that checking this attribute first is going to match the runtime behavior in all cases.

if (currentTypeDef.Module.IgnoresAccessChecksTo(targetTypeDef.Module))
return true;

var targetContainingType = targetTypeDef.ContainingType;
if (targetContainingType == null)
{
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -274,6 +282,30 @@ static ReadOnlySpan<byte> 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)
Expand Down
Loading