Skip to content

Commit

Permalink
Merge pull request #10 from Microsoft/dev/andarno/internalMetadataViews
Browse files Browse the repository at this point in the history
Add support for internal metadata view interfaces
  • Loading branch information
AArnott authored May 22, 2017
2 parents 57775f8 + d48ba14 commit 2047839
Show file tree
Hide file tree
Showing 6 changed files with 209 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/// <summary>
/// Constructs concrete types for metadata view interfaces.
Expand Down Expand Up @@ -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<string, object> metadata, IReadOnlyDictionary<string, object> defaultMetadata);

Expand All @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Gives a dynamic assembly the ability to skip CLR visibility checks,
/// allowing the assembly to access private members of another assembly.
/// </summary>
internal class SkipClrVisibilityChecks
{
/// <summary>
/// The assembly builder that is constructing the dynamic assembly.
/// </summary>
private readonly AssemblyBuilder assemblyBuilder;

/// <summary>
/// The module builder for the default module of the <see cref="assemblyBuilder"/>.
/// This is where the special attribute will be defined.
/// </summary>
private readonly ModuleBuilder moduleBuilder;

/// <summary>
/// The set of assemblies that already have visibility checks skipped for.
/// </summary>
private readonly HashSet<string> attributedAssemblyNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

/// <summary>
/// The constructor on the special attribute to reference for each skipped assembly.
/// </summary>
private ConstructorInfo magicAttributeCtor;

/// <summary>
/// Initializes a new instance of the <see cref="SkipClrVisibilityChecks"/> class.
/// </summary>
/// <param name="assemblyBuilder">The builder for the dynamic assembly.</param>
/// <param name="moduleBuilder">The builder for the default module defined by <see cref="assemblyBuilder"/>.</param>
internal SkipClrVisibilityChecks(AssemblyBuilder assemblyBuilder, ModuleBuilder moduleBuilder)
{
Requires.NotNull(assemblyBuilder, nameof(assemblyBuilder));
Requires.NotNull(moduleBuilder, nameof(moduleBuilder));
this.assemblyBuilder = assemblyBuilder;
this.moduleBuilder = moduleBuilder;
}

/// <summary>
/// Ensures the CLR will skip visibility checks when accessing
/// the assembly that contains the specified member.
/// </summary>
/// <param name="memberInfo">The member that may not be publicly accessible.</param>
internal void SkipVisibilityChecksFor(MemberInfo memberInfo)
{
this.SkipVisibilityChecksFor(memberInfo.Module.Assembly);
}

/// <summary>
/// Add an attribute to the dynamic assembly so that the CLR will skip visibility checks
/// for the specified assembly.
/// </summary>
/// <param name="assembly">The assembly to skip visibility checks for.</param>
private void SkipVisibilityChecksFor(Assembly assembly)
{
Requires.NotNull(assembly, nameof(assembly));
var assemblyName = assembly.GetName();
this.SkipVisibilityChecksFor(assemblyName);
}

/// <summary>
/// Add an attribute to the dynamic assembly so that the CLR will skip visibility checks
/// for the assembly with the specified name.
/// </summary>
/// <param name="assemblyName">The name of the assembly to skip visibility checks for.</param>
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);
}
}

/// <summary>
/// Gets the constructor to the IgnoresAccessChecksToAttribute, generating the attribute if necessary.
/// </summary>
/// <returns>The constructor to the IgnoresAccessChecksToAttribute.</returns>
private ConstructorInfo GetMagicAttributeCtor()
{
if (this.magicAttributeCtor == null)
{
var magicAttribute = this.EmitMagicAttribute();
this.magicAttributeCtor = magicAttribute.GetConstructor(new Type[] { typeof(string) });
}

return this.magicAttributeCtor;
}

/// <summary>
/// Defines the special IgnoresAccessChecksToAttribute type in the <see cref="moduleBuilder"/>.
/// </summary>
/// <returns>The generated attribute type.</returns>
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();
}
}
}
Original file line number Diff line number Diff line change
@@ -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")]
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) Microsoft. All rights reserved.

namespace Microsoft.VisualStudio.Composition.AssemblyDiscoveryTests
{
/// <summary>
/// An internal interface that is used as the base interface
/// of a metadata view defined in another assembly.
/// </summary>
internal interface IInternalMetadataView
{
string MetadataOnInternalInterface { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,19 @@ public void ImportManyWithMetadataInterface(IContainer container)
Assert.IsType<PartWithExportMetadata>(importingPart.ImportingProperty.Single().Value);
}

[Trait("Access", "NonPublic")]
[MefFact(CompositionEngines.V3EmulatingV1, typeof(ImportManyPartWithInternalMetadataInterface), typeof(PartWithExportMetadata))]
public void ImportManyWithInternalMetadataInterface(IContainer container)
{
var importingPart = container.GetExportedValue<ImportManyPartWithInternalMetadataInterface>();
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<PartWithExportMetadata>(importingPart.ImportingProperty.Single().Value);
}

[MefFact(CompositionEngines.Unspecified, typeof(ImportingPartWithMetadataInterface), typeof(PartWithExportMetadata))]
[Trait("Efficiency", "InstanceReuse")]
public void MetadataViewInterfaceInstanceSharedAcrossImports(IContainer container)
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -604,6 +619,14 @@ public class ImportManyPartWithMetadataInterface
public IEnumerable<Lazy<PartWithExportMetadata, IMetadata>> ImportingProperty { get; set; }
}

[MefV1.Export, MefV1.PartCreationPolicy(MefV1.CreationPolicy.NonShared)]
[Export]
public class ImportManyPartWithInternalMetadataInterface
{
[ImportMany, MefV1.ImportMany]
internal IEnumerable<Lazy<PartWithExportMetadata, IMetadataInternal>> ImportingProperty { get; set; }
}

[MefV1.Export, MefV1.PartCreationPolicy(MefV1.CreationPolicy.NonShared)]
[Export]
public class ImportManyPartWithMetadataClass
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<SomeOtherPart, ISomeOtherPartInternalMetadataView>();
Assert.Equal(1, export.Metadata.A);
Assert.NotNull(export.Value);
}

[MefFact(CompositionEngines.V1Compat | CompositionEngines.V3EmulatingV2, typeof(SomeOtherPart))]
public void GetExportWithFilteringMetadataView(IContainer container)
{
Expand Down Expand Up @@ -118,6 +127,11 @@ public interface ISomeOtherPartMetadataView
int A { get; }
}

internal interface ISomeOtherPartInternalMetadataView
{
int A { get; }
}

public interface IMetadataViewWithBMember
{
int B { get; }
Expand Down

0 comments on commit 2047839

Please sign in to comment.