Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for internal metadata view interfaces #10

Merged
merged 5 commits into from
May 22, 2017
Merged
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
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).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).GetConstructor(Type.EmptyTypes));
transparentProxyModuleBuilder = assemblyBuilder.DefineDynamicModule("MetadataViewProxiesModule");
skipClrVisibilityChecks = new SkipClrVisibilityChecks(assemblyBuilder, transparentProxyModuleBuilder);
}

return transparentProxyModuleBuilder;
Expand Down Expand Up @@ -142,6 +142,8 @@ private static Type 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",
Copy link
Contributor

Choose a reason for hiding this comment

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

Are there docs for this attribute? I haven't heard of it before, but it definitely seems to come in handy here. I'm assuming that this attribute is supported in .NET 4.5, right?

Copy link
Member Author

@AArnott AArnott May 22, 2017

Choose a reason for hiding this comment

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

It's not documented on MSDN. It is in CoreCLR and .NET Framework, although I'm not sure which version started supporting it. If vs-mef runs on a version that doesn't support it, it will just be ignored and we'll "regress" to behavior before this PR. This PR won't make anything worse.

Copy link
Member Author

Choose a reason for hiding this comment

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

I opened a bug to get the attribute documented: Bug 438899: Document IgnoresAccessChecksToAttribute

Copy link
Member Author

Choose a reason for hiding this comment

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

.NET 4.6 added support for this attribute.

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay - that should be fine in VS since we require .NET 4.6. I was initially worried about the situations where we launch devenv /updateConfiguration when only .NET 4.5 is installed - this can happen if we're pending reboot to install .NET 4.6. However, I just remember this was "fixed" in 15.2, though, by avoiding composing MEF in the cases where .NET 4.6 is not installed (DevDiv PR 66331). The next launch of VS will do the composition in that case.

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