diff --git a/src/Microsoft.VisualStudio.Composition/Configuration/MetadataViewGenerator.cs b/src/Microsoft.VisualStudio.Composition/Configuration/MetadataViewGenerator.cs index f41f0215b..e92f2f854 100644 --- a/src/Microsoft.VisualStudio.Composition/Configuration/MetadataViewGenerator.cs +++ b/src/Microsoft.VisualStudio.Composition/Configuration/MetadataViewGenerator.cs @@ -9,16 +9,14 @@ namespace Microsoft.VisualStudio.Composition { using System; - using System.Collections; using System.Collections.Generic; - using System.ComponentModel; using System.Globalization; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Security; using System.Threading; - using Microsoft.Internal; + using Microsoft.VisualStudio.Composition.Reflection; /// /// Constructs concrete types for metadata view interfaces. @@ -90,6 +88,7 @@ internal static class MetadataViewGenerator private static readonly ConstructorInfo ObjectCtor = typeof(object).GetTypeInfo().GetConstructor(Type.EmptyTypes); private static ModuleBuilder transparentProxyModuleBuilder; + private static SkipClrVisibilityChecks skipClrVisibilityChecks; public delegate object MetadataViewFactory(IReadOnlyDictionary metadata, IReadOnlyDictionary defaultMetadata); @@ -106,6 +105,7 @@ private static ModuleBuilder GetProxyModuleBuilder() // make a new assemblybuilder and modulebuilder var assemblyBuilder = CreateProxyAssemblyBuilder(typeof(SecurityTransparentAttribute).GetTypeInfo().GetConstructor(Type.EmptyTypes)); transparentProxyModuleBuilder = assemblyBuilder.DefineDynamicModule("MetadataViewProxiesModule"); + skipClrVisibilityChecks = new SkipClrVisibilityChecks(assemblyBuilder, transparentProxyModuleBuilder); } return transparentProxyModuleBuilder; @@ -142,6 +142,8 @@ private static TypeInfo GenerateInterfaceViewProxyType(Type viewType) Type[] interfaces = { viewType }; var proxyModuleBuilder = GetProxyModuleBuilder(); + skipClrVisibilityChecks.SkipVisibilityChecksFor(viewType.GetTypeInfo()); + proxyTypeBuilder = proxyModuleBuilder.DefineType( string.Format(CultureInfo.InvariantCulture, "_proxy_{0}_{1}", viewType.FullName, Guid.NewGuid()), TypeAttributes.Public, diff --git a/src/Microsoft.VisualStudio.Composition/Reflection/SkipClrVisibilityChecks.cs b/src/Microsoft.VisualStudio.Composition/Reflection/SkipClrVisibilityChecks.cs new file mode 100644 index 000000000..5dffa968c --- /dev/null +++ b/src/Microsoft.VisualStudio.Composition/Reflection/SkipClrVisibilityChecks.cs @@ -0,0 +1,142 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.Composition.Reflection +{ + using System; + using System.Collections.Generic; + using System.Reflection; + using System.Reflection.Emit; + + /// + /// Gives a dynamic assembly the ability to skip CLR visibility checks, + /// allowing the assembly to access private members of another assembly. + /// + internal class SkipClrVisibilityChecks + { + /// + /// The assembly builder that is constructing the dynamic assembly. + /// + private readonly AssemblyBuilder assemblyBuilder; + + /// + /// The module builder for the default module of the . + /// This is where the special attribute will be defined. + /// + private readonly ModuleBuilder moduleBuilder; + + /// + /// The set of assemblies that already have visibility checks skipped for. + /// + private readonly HashSet attributedAssemblyNames = new HashSet(StringComparer.OrdinalIgnoreCase); + + /// + /// The constructor on the special attribute to reference for each skipped assembly. + /// + private ConstructorInfo magicAttributeCtor; + + /// + /// Initializes a new instance of the class. + /// + /// The builder for the dynamic assembly. + /// The builder for the default module defined by . + internal SkipClrVisibilityChecks(AssemblyBuilder assemblyBuilder, ModuleBuilder moduleBuilder) + { + Requires.NotNull(assemblyBuilder, nameof(assemblyBuilder)); + Requires.NotNull(moduleBuilder, nameof(moduleBuilder)); + this.assemblyBuilder = assemblyBuilder; + this.moduleBuilder = moduleBuilder; + } + + /// + /// Ensures the CLR will skip visibility checks when accessing + /// the assembly that contains the specified member. + /// + /// The member that may not be publicly accessible. + internal void SkipVisibilityChecksFor(MemberInfo memberInfo) + { + this.SkipVisibilityChecksFor(memberInfo.Module.Assembly); + } + + /// + /// Add an attribute to the dynamic assembly so that the CLR will skip visibility checks + /// for the specified assembly. + /// + /// The assembly to skip visibility checks for. + private void SkipVisibilityChecksFor(Assembly assembly) + { + Requires.NotNull(assembly, nameof(assembly)); + var assemblyName = assembly.GetName(); + this.SkipVisibilityChecksFor(assemblyName); + } + + /// + /// Add an attribute to the dynamic assembly so that the CLR will skip visibility checks + /// for the assembly with the specified name. + /// + /// The name of the assembly to skip visibility checks for. + private void SkipVisibilityChecksFor(AssemblyName assemblyName) + { + Requires.NotNull(assemblyName, nameof(assemblyName)); + + string assemblyNameArg = assemblyName.Name; + if (this.attributedAssemblyNames.Add(assemblyNameArg)) + { + var cab = new CustomAttributeBuilder(this.GetMagicAttributeCtor(), new object[] { assemblyNameArg }); + this.assemblyBuilder.SetCustomAttribute(cab); + } + } + + /// + /// Gets the constructor to the IgnoresAccessChecksToAttribute, generating the attribute if necessary. + /// + /// The constructor to the IgnoresAccessChecksToAttribute. + private ConstructorInfo GetMagicAttributeCtor() + { + if (this.magicAttributeCtor == null) + { + var magicAttribute = this.EmitMagicAttribute(); + this.magicAttributeCtor = magicAttribute.GetConstructor(new Type[] { typeof(string) }); + } + + return this.magicAttributeCtor; + } + + /// + /// Defines the special IgnoresAccessChecksToAttribute type in the . + /// + /// The generated attribute type. + private TypeInfo EmitMagicAttribute() + { + var tb = this.moduleBuilder.DefineType( + "System.Runtime.CompilerServices.IgnoresAccessChecksToAttribute", + TypeAttributes.NotPublic, + typeof(Attribute)); + + var attributeUsageCtor = typeof(AttributeUsageAttribute).GetConstructor( + new Type[] { typeof(AttributeTargets) }); + var attributeUsage = new CustomAttributeBuilder( + attributeUsageCtor, + new object[] { AttributeTargets.Assembly }, + new PropertyInfo[] { typeof(AttributeUsageAttribute).GetProperty(nameof(AttributeUsageAttribute.AllowMultiple)) }, + new object[] { false }); + tb.SetCustomAttribute(attributeUsage); + + var cb = tb.DefineConstructor( + MethodAttributes.Public | + MethodAttributes.HideBySig | + MethodAttributes.SpecialName | + MethodAttributes.RTSpecialName, + CallingConventions.Standard, + new Type[] { typeof(string) }); + cb.DefineParameter(1, ParameterAttributes.None, "assemblyName"); + + var il = cb.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); + ConstructorInfo attrBaseCtor = typeof(Attribute).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[0], null); + il.Emit(OpCodes.Call, attrBaseCtor); + il.Emit(OpCodes.Ret); + + return tb.CreateTypeInfo(); + } + } +} diff --git a/src/tests/Microsoft.VisualStudio.Composition.AssemblyDiscoveryTests/AssemblyInfo.cs b/src/tests/Microsoft.VisualStudio.Composition.AssemblyDiscoveryTests/AssemblyInfo.cs new file mode 100644 index 000000000..87b42e638 --- /dev/null +++ b/src/tests/Microsoft.VisualStudio.Composition.AssemblyDiscoveryTests/AssemblyInfo.cs @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Runtime.CompilerServices; + +// This is an important part of testing. We need to know that our ability to handle +// internal types can span assemblies when, for example, an internal type in one assembly +// references an internal type in another assembly. +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.Composition.Tests")] diff --git a/src/tests/Microsoft.VisualStudio.Composition.AssemblyDiscoveryTests/IInternalMetadataView.cs b/src/tests/Microsoft.VisualStudio.Composition.AssemblyDiscoveryTests/IInternalMetadataView.cs new file mode 100644 index 000000000..94ec610f3 --- /dev/null +++ b/src/tests/Microsoft.VisualStudio.Composition.AssemblyDiscoveryTests/IInternalMetadataView.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.VisualStudio.Composition.AssemblyDiscoveryTests +{ + /// + /// An internal interface that is used as the base interface + /// of a metadata view defined in another assembly. + /// + internal interface IInternalMetadataView + { + string MetadataOnInternalInterface { get; } + } +} diff --git a/src/tests/Microsoft.VisualStudio.Composition.Tests/ExportMetadataTests.cs b/src/tests/Microsoft.VisualStudio.Composition.Tests/ExportMetadataTests.cs index 86d0ad636..4ac0afdd0 100644 --- a/src/tests/Microsoft.VisualStudio.Composition.Tests/ExportMetadataTests.cs +++ b/src/tests/Microsoft.VisualStudio.Composition.Tests/ExportMetadataTests.cs @@ -107,6 +107,19 @@ public void ImportManyWithMetadataInterface(IContainer container) Assert.IsType(importingPart.ImportingProperty.Single().Value); } + [Trait("Access", "NonPublic")] + [MefFact(CompositionEngines.V3EmulatingV1, typeof(ImportManyPartWithInternalMetadataInterface), typeof(PartWithExportMetadata))] + public void ImportManyWithInternalMetadataInterface(IContainer container) + { + var importingPart = container.GetExportedValue(); + Assert.NotNull(importingPart.ImportingProperty); + Assert.Equal(1, importingPart.ImportingProperty.Count()); + Assert.Equal("b", importingPart.ImportingProperty.Single().Metadata.a); + Assert.Equal("internal!", importingPart.ImportingProperty.Single().Metadata.MetadataOnInternalInterface); + Assert.False(importingPart.ImportingProperty.Single().IsValueCreated); + Assert.IsType(importingPart.ImportingProperty.Single().Value); + } + [MefFact(CompositionEngines.Unspecified, typeof(ImportingPartWithMetadataInterface), typeof(PartWithExportMetadata))] [Trait("Efficiency", "InstanceReuse")] public void MetadataViewInterfaceInstanceSharedAcrossImports(IContainer container) @@ -552,8 +565,10 @@ public class PartImportingPartWithNamesSingleMetadata [MefV1.Export, MefV1.PartCreationPolicy(MefV1.CreationPolicy.NonShared)] [MefV1.ExportMetadata("a", "b")] + [MefV1.ExportMetadata(nameof(AssemblyDiscoveryTests.IInternalMetadataView.MetadataOnInternalInterface), "internal!")] [Export] [ExportMetadata("a", "b")] + [ExportMetadata(nameof(AssemblyDiscoveryTests.IInternalMetadataView.MetadataOnInternalInterface), "internal!")] public class PartWithExportMetadata { } [MefV1.Export, MefV1.PartCreationPolicy(MefV1.CreationPolicy.NonShared)] @@ -604,6 +619,14 @@ public class ImportManyPartWithMetadataInterface public IEnumerable> ImportingProperty { get; set; } } + [MefV1.Export, MefV1.PartCreationPolicy(MefV1.CreationPolicy.NonShared)] + [Export] + public class ImportManyPartWithInternalMetadataInterface + { + [ImportMany, MefV1.ImportMany] + internal IEnumerable> ImportingProperty { get; set; } + } + [MefV1.Export, MefV1.PartCreationPolicy(MefV1.CreationPolicy.NonShared)] [Export] public class ImportManyPartWithMetadataClass @@ -637,6 +660,10 @@ public interface IMetadata : IMetadataBase int SomeInt { get; } } + internal interface IMetadataInternal : IMetadata, AssemblyDiscoveryTests.IInternalMetadataView + { + } + public class MetadataClass { // Only MEFv1 requires this constructor -- MEFv2 doesn't need it. diff --git a/src/tests/Microsoft.VisualStudio.Composition.Tests/ExportProviderTests.cs b/src/tests/Microsoft.VisualStudio.Composition.Tests/ExportProviderTests.cs index f0af6512f..31ac61fa1 100644 --- a/src/tests/Microsoft.VisualStudio.Composition.Tests/ExportProviderTests.cs +++ b/src/tests/Microsoft.VisualStudio.Composition.Tests/ExportProviderTests.cs @@ -54,6 +54,15 @@ public void GetExportWithMetadataView(IContainer container) Assert.NotNull(export.Value); } + [Trait("Access", "NonPublic")] + [MefFact(CompositionEngines.V3EmulatingV1 | CompositionEngines.V3EmulatingV2, typeof(SomeOtherPart))] + public void GetExportWithInternalMetadataView(IContainer container) + { + var export = container.GetExport(); + Assert.Equal(1, export.Metadata.A); + Assert.NotNull(export.Value); + } + [MefFact(CompositionEngines.V1Compat | CompositionEngines.V3EmulatingV2, typeof(SomeOtherPart))] public void GetExportWithFilteringMetadataView(IContainer container) { @@ -118,6 +127,11 @@ public interface ISomeOtherPartMetadataView int A { get; } } + internal interface ISomeOtherPartInternalMetadataView + { + int A { get; } + } + public interface IMetadataViewWithBMember { int B { get; }