Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
Expand Up @@ -243,6 +243,15 @@ private static bool IsNamedTypeAccessible(NamedTypeSymbol type, Symbol within, r
}
}

// Check if the type has the EmbeddedAttribute and is in a different assembly.
// The EmbeddedAttribute marks a type as only visible within its own assembly.
// When checking within the context of an assembly, we assume we are checking the primary module.
var withinModule = within is AssemblySymbol assembly ? assembly.Modules[0] : within.ContainingModule;
if ((object)type.ContainingModule != withinModule && type.IsHiddenByCodeAnalysisEmbeddedAttribute())
{
return false;
}

var containingType = type.ContainingType;
return (object)containingType == null
? IsNonNestedTypeAccessible(type.ContainingAssembly, type.DeclaredAccessibility, within)
Expand Down
176 changes: 176 additions & 0 deletions src/Compilers/CSharp/Test/Semantic/Semantics/AccessCheckTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1525,5 +1525,181 @@ unsafe class A
Assert.Throws<ArgumentException>(() => ((Compilation)comp2).IsSymbolAccessibleWithin(ptr1, b));
Assert.Throws<ArgumentException>(() => ((Compilation)comp2).IsSymbolAccessibleWithin(ptr2, b));
}

[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/76584")]
[InlineData("public")]
[InlineData("internal")]
public void EmbeddedAttribute_IsSymbolAccessibleWithin(string accessibility)
{
// Create assembly 1 with a public type marked with EmbeddedAttribute
var source1 = $$"""
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Assembly2")]

namespace Microsoft.CodeAnalysis
{
internal sealed class EmbeddedAttribute : System.Attribute { }
}

[Microsoft.CodeAnalysis.Embedded]
{{accessibility}} class Sample { }

{{accessibility}} class NonEmbeddedSample { }
""";

var comp1 = CreateCompilation(source1, assemblyName: "Assembly1");
comp1.VerifyDiagnostics();

var sampleTypeInComp1 = comp1.GetTypeByMetadataName("Sample");
Assert.NotNull(sampleTypeInComp1);
var nonEmbeddedTypeInComp1 = comp1.GetTypeByMetadataName("NonEmbeddedSample");
Assert.NotNull(nonEmbeddedTypeInComp1);
Assert.True(sampleTypeInComp1.HasCodeAnalysisEmbeddedAttribute);

// The symbol should be accessible within its own assembly
var assembly1SymbolPublic = comp1.Assembly.GetPublicSymbol();
Assert.True(((Compilation)comp1).IsSymbolAccessibleWithin(sampleTypeInComp1.GetPublicSymbol(), assembly1SymbolPublic));
Assert.True(((Compilation)comp1).IsSymbolAccessibleWithin(sampleTypeInComp1.GetPublicSymbol(), nonEmbeddedTypeInComp1.GetPublicSymbol()));

// Create assembly 2 that references assembly 1
var comp2 = CreateCompilation("class OtherClass { }", references: [comp1.ToMetadataReference()], assemblyName: "Assembly2");

// Get the Sample type from the referenced assembly
var sampleSymbol = comp2.GetReferencedAssemblySymbol(comp1.ToMetadataReference()).GetTypeByMetadataName("Sample");
Assert.NotNull(sampleSymbol);
var nonEmbeddedSampleSymbol = comp2.GetTypeByMetadataName("NonEmbeddedSample");
Assert.NotNull(nonEmbeddedSampleSymbol);

var otherClassSymbol = comp2.GetTypeByMetadataName("OtherClass");
var assembly2Symbol = comp2.Assembly.GetPublicSymbol();

// Even with InternalsVisibleTo and public accessibility, symbols with EmbeddedAttribute should not be accessible from another assembly
Assert.False(((Compilation)comp2).IsSymbolAccessibleWithin(sampleSymbol.GetPublicSymbol(), otherClassSymbol.GetPublicSymbol()));
Assert.False(((Compilation)comp2).IsSymbolAccessibleWithin(sampleSymbol.GetPublicSymbol(), assembly2Symbol));
Assert.True(((Compilation)comp2).IsSymbolAccessibleWithin(nonEmbeddedSampleSymbol.GetPublicSymbol(), otherClassSymbol.GetPublicSymbol()));
Assert.True(((Compilation)comp2).IsSymbolAccessibleWithin(nonEmbeddedSampleSymbol.GetPublicSymbol(), assembly2Symbol));
}

[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/76584")]
[CombinatorialData]
public void EmbeddedAttribute_NestedType_OnOuterType_IsSymbolAccessibleWithin([CombinatorialValues("public", "internal")] string outerAccessibility, [CombinatorialValues("public", "internal")] string innerAccessibility)
{
// Test that nested types within an EmbeddedAttribute-decorated type are also inaccessible
var source1 = $$"""
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Assembly2")]

namespace Microsoft.CodeAnalysis
{
internal sealed class EmbeddedAttribute : System.Attribute { }
}

[Microsoft.CodeAnalysis.Embedded]
{{outerAccessibility}} class OuterSample
{
{{innerAccessibility}} class Nested { }
}
""";

var comp1 = CreateCompilation(source1, assemblyName: "Assembly1");
comp1.VerifyDiagnostics();
Copy link
Contributor

Choose a reason for hiding this comment

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

VerifyDiagnostics

Consider testing API's behavior in comp1.


var outerSymbolInComp1 = comp1.GetTypeByMetadataName("OuterSample").GetPublicSymbol();
var nestedPublicSymbolInComp1 = comp1.GetTypeByMetadataName("OuterSample+Nested").GetPublicSymbol();
Assert.NotNull(outerSymbolInComp1);
Assert.NotNull(nestedPublicSymbolInComp1);

// The types should be accessible within their own assembly
var assembly1SymbolPublic = comp1.Assembly.GetPublicSymbol();
Assert.True(((Compilation)comp1).IsSymbolAccessibleWithin(outerSymbolInComp1, assembly1SymbolPublic));
Assert.True(((Compilation)comp1).IsSymbolAccessibleWithin(nestedPublicSymbolInComp1, assembly1SymbolPublic));
Assert.True(((Compilation)comp1).IsSymbolAccessibleWithin(nestedPublicSymbolInComp1, outerSymbolInComp1));

var comp1Reference = comp1.ToMetadataReference();
var comp2 = CreateCompilation("class OtherClass { }", references: [comp1Reference], assemblyName: "Assembly2");

// Get types from the referenced assembly
var assembly1Symbol = comp2.GetReferencedAssemblySymbol(comp1Reference).GetPublicSymbol();
var outerSymbol = assembly1Symbol.GetTypeByMetadataName("OuterSample");
var nestedPublicSymbol = assembly1Symbol.GetTypeByMetadataName("OuterSample+Nested");
Assert.NotNull(outerSymbol);
Assert.NotNull(nestedPublicSymbol);

var otherClassSymbol = comp2.GetTypeByMetadataName("OtherClass").GetPublicSymbol();
var assembly2Symbol = comp2.Assembly.GetPublicSymbol();

// The outer type with EmbeddedAttribute should not be accessible
Assert.False(((Compilation)comp2).IsSymbolAccessibleWithin(outerSymbol, otherClassSymbol));
Assert.False(((Compilation)comp2).IsSymbolAccessibleWithin(outerSymbol, assembly2Symbol));

// Nested types should not be accessible if the outer type has EmbeddedAttribute
Assert.False(((Compilation)comp2).IsSymbolAccessibleWithin(nestedPublicSymbol, otherClassSymbol));
Assert.False(((Compilation)comp2).IsSymbolAccessibleWithin(nestedPublicSymbol, assembly2Symbol));
}

[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/76584")]
[CombinatorialData]
public void EmbeddedAttribute_NestedType_OnNestedType_IsSymbolAccessibleWithin([CombinatorialValues("public", "internal")] string outerAccessibility, [CombinatorialValues("public", "internal")] string innerAccessibility)
{
// Test that nested types within an EmbeddedAttribute-decorated type are also inaccessible
var source1 = $$"""
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Assembly2")]

namespace Microsoft.CodeAnalysis
{
internal sealed class EmbeddedAttribute : System.Attribute { }
}

{{outerAccessibility}} class OuterSample
{
[Microsoft.CodeAnalysis.Embedded]
{{innerAccessibility}} class NestedEmbedded { }

{{innerAccessibility}} class NestedNonEmbedded { }
}
""";

var comp1 = CreateCompilation(source1, assemblyName: "Assembly1");
comp1.VerifyDiagnostics();

var outerSymbolInComp1 = comp1.GetTypeByMetadataName("OuterSample").GetPublicSymbol();
var nestedEmbeddedSymbolInComp1 = comp1.GetTypeByMetadataName("OuterSample+NestedEmbedded").GetPublicSymbol();
var nestedNonEmbeddedSymbolInComp1 = comp1.GetTypeByMetadataName("OuterSample+NestedNonEmbedded").GetPublicSymbol();

Assert.NotNull(outerSymbolInComp1);
Assert.NotNull(nestedEmbeddedSymbolInComp1);
Assert.NotNull(nestedNonEmbeddedSymbolInComp1);

// The types should be accessible within their own assembly
var assembly1SymbolPublic = comp1.Assembly.GetPublicSymbol();
Assert.True(((Compilation)comp1).IsSymbolAccessibleWithin(outerSymbolInComp1, assembly1SymbolPublic));
Assert.True(((Compilation)comp1).IsSymbolAccessibleWithin(nestedEmbeddedSymbolInComp1, assembly1SymbolPublic));
Assert.True(((Compilation)comp1).IsSymbolAccessibleWithin(nestedEmbeddedSymbolInComp1, outerSymbolInComp1));
Assert.True(((Compilation)comp1).IsSymbolAccessibleWithin(nestedNonEmbeddedSymbolInComp1, assembly1SymbolPublic));
Assert.True(((Compilation)comp1).IsSymbolAccessibleWithin(nestedNonEmbeddedSymbolInComp1, outerSymbolInComp1));

var comp1Reference = comp1.ToMetadataReference();
var comp2 = CreateCompilation("class OtherClass { }", references: [comp1Reference], assemblyName: "Assembly2");

// Get types from the referenced assembly
var assembly1Symbol = comp2.GetReferencedAssemblySymbol(comp1Reference);
var outerSymbol = comp2.GetTypeByMetadataName("OuterSample").GetPublicSymbol();
var nestedEmbeddedSymbol = assembly1Symbol.GetTypeByMetadataName("OuterSample+NestedEmbedded").GetPublicSymbol();
var nestedNonEmbeddedPublicSymbol = assembly1Symbol.GetTypeByMetadataName("OuterSample+NestedNonEmbedded").GetPublicSymbol();
Assert.NotNull(outerSymbol);
Assert.NotNull(nestedEmbeddedSymbol);
Assert.NotNull(nestedNonEmbeddedPublicSymbol);

var otherClassSymbol = comp2.GetTypeByMetadataName("OtherClass").GetPublicSymbol(); ;
var assembly2Symbol = comp2.Assembly.GetPublicSymbol();

Assert.True(((Compilation)comp2).IsSymbolAccessibleWithin(outerSymbol, otherClassSymbol));
Assert.True(((Compilation)comp2).IsSymbolAccessibleWithin(outerSymbol, assembly2Symbol));

// Embedded on a nested type has no effect
Assert.True(((Compilation)comp2).IsSymbolAccessibleWithin(nestedEmbeddedSymbol, otherClassSymbol));
Assert.True(((Compilation)comp2).IsSymbolAccessibleWithin(nestedEmbeddedSymbol, assembly2Symbol));

Assert.True(((Compilation)comp2).IsSymbolAccessibleWithin(nestedNonEmbeddedPublicSymbol, otherClassSymbol));
Assert.True(((Compilation)comp2).IsSymbolAccessibleWithin(nestedNonEmbeddedPublicSymbol, assembly2Symbol));
}
}
}
9 changes: 9 additions & 0 deletions src/Compilers/VisualBasic/Portable/Semantics/AccessCheck.vb
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,15 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Next
End If

' Check if the type has the EmbeddedAttribute and is in a different assembly.
' The EmbeddedAttribute marks a type as only visible within its own assembly.
' When checking within the context of an assembly, we assume we are checking the primary module.
Dim withinModule As ModuleSymbol = If(TryCast(within, AssemblySymbol)?.Modules(0), within.ContainingModule)
If typeSym.ContainingModule IsNot withinModule AndAlso
(typeSym.IsHiddenByCodeAnalysisEmbeddedAttribute() OrElse typeSym.IsHiddenByVisualBasicEmbeddedAttribute()) Then
Return AccessCheckResult.Inaccessible
End If

Dim containingType As NamedTypeSymbol = typeSym.ContainingType

If containingType Is Nothing Then
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2333,6 +2333,9 @@ End Class

CompilationUtils.AssertTheseDiagnostics(c,
<error>
BC36666: 'Public Shared SourceLibrary.U As Microsoft.VisualBasic.CompilerServices.Utils' is not accessible in this context because the return type is not accessible.
Copy link
Contributor

Choose a reason for hiding this comment

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

This change is probably undesirable.

Copy link
Member

Choose a reason for hiding this comment

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

I don't know that I agree. SourceLibrary.U is of a type with MS.VB.EmbeddedAttribute applied to it. Right now, Dim u = SourceLibrary.U is not disallowed, even though U is, in theory, a type that the current compilation should not see, and with my change, it becomes disallowed. Am I missing something in my analysis here?

SourceLibrary.U.CopyArray(Nothing, Nothing)
~~~~~~~~~~~~~~~
BC30456: 'CopyArray' is not a member of 'Utils'.
SourceLibrary.U.CopyArray(Nothing, Nothing)
~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
Loading