File: Microsoft\VisualBasic\Activities\XamlIntegration\VisualBasicExpressionConverter.cs
Project: ndp\cdf\src\NetFx40\System.Activities\System.Activities.csproj (System.Activities)
//-----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//-----------------------------------------------------------------------------
 
namespace Microsoft.VisualBasic.Activities.XamlIntegration
{
    using System;
    using System.Activities.Expressions;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Reflection;
    using System.Xml.Linq;
    using System.ComponentModel;
    using System.Xaml;
    using System.Windows.Markup;
    using System.Security;
    using System.Security.Permissions;
    using System.Runtime;
    using System.Threading;
 
    static class VisualBasicExpressionConverter
    {
        static readonly Regex assemblyQualifiedNamespaceRegex = new Regex(
            "clr-namespace:(?<namespace>[^;]*);assembly=(?<assembly>.*)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
 
        public static VisualBasicSettings CollectXmlNamespacesAndAssemblies(ITypeDescriptorContext context)
        {
            // access XamlSchemaContext.ReferenceAssemblies 
            // for the Compiled Xaml scenario
            IList<Assembly> xsCtxReferenceAssemblies = null;
            IXamlSchemaContextProvider xamlSchemaContextProvider = context.GetService(typeof(IXamlSchemaContextProvider)) as IXamlSchemaContextProvider;
            if (xamlSchemaContextProvider != null && xamlSchemaContextProvider.SchemaContext != null)
            {
                xsCtxReferenceAssemblies = xamlSchemaContextProvider.SchemaContext.ReferenceAssemblies;
                if (xsCtxReferenceAssemblies != null && xsCtxReferenceAssemblies.Count == 0)
                {
                    xsCtxReferenceAssemblies = null;
                }
            } 
 
            VisualBasicSettings settings = null;
            IXamlNamespaceResolver namespaceResolver = (IXamlNamespaceResolver)context.GetService(typeof(IXamlNamespaceResolver));
 
            if (namespaceResolver == null)
            {
                return null;
            }
 
            lock (AssemblyCache.XmlnsMappingsLockObject)
            {
                // Fetch xmlnsMappings for the prefixes returned by the namespaceResolver service
 
                foreach (NamespaceDeclaration prefix in namespaceResolver.GetNamespacePrefixes())
                {
                    ReadOnlyXmlnsMapping mapping;
                    WrapCachedMapping(prefix, out mapping);
                    if (!mapping.IsEmpty)
                    {
                        if (settings == null)
                        {
                            settings = new VisualBasicSettings();
                        }
 
                        if (!mapping.IsEmpty)
                        {
                            foreach (ReadOnlyVisualBasicImportReference importReference in mapping.ImportReferences)
                            {
                                if (xsCtxReferenceAssemblies != null)
                                {
                                    // this is "compiled Xaml" 
                                    VisualBasicImportReference newImportReference;
 
                                    if (importReference.EarlyBoundAssembly != null)
                                    {
                                        if (xsCtxReferenceAssemblies.Contains(importReference.EarlyBoundAssembly))
                                        {
                                            newImportReference = importReference.Clone();
                                            newImportReference.EarlyBoundAssembly = importReference.EarlyBoundAssembly;
                                            settings.ImportReferences.Add(newImportReference);
                                        }
                                        continue;
                                    }
 
                                    for (int i = 0; i < xsCtxReferenceAssemblies.Count; i++)
                                    {
                                        AssemblyName xsCtxAssemblyName = VisualBasicHelper.GetFastAssemblyName(xsCtxReferenceAssemblies[i]);
                                        if (importReference.AssemblySatisfiesReference(xsCtxAssemblyName))
                                        {
                                            // bind this assembly early to the importReference
                                            // so later AssemblyName resolution can be skipped
                                            newImportReference = importReference.Clone();
                                            newImportReference.EarlyBoundAssembly = xsCtxReferenceAssemblies[i];
                                            settings.ImportReferences.Add(newImportReference);
                                            break;
                                        }
                                    }
                                }
                                else
                                {
                                    // this is "loose Xaml"
                                    VisualBasicImportReference newImportReference = importReference.Clone();
                                    if (importReference.EarlyBoundAssembly != null)
                                    {
                                        // VBImportReference.Clone() method deliberately doesn't copy 
                                        // its EarlyBoundAssembly to the cloned instance.
                                        // we need to explicitly copy the original's EarlyBoundAssembly
                                        newImportReference.EarlyBoundAssembly = importReference.EarlyBoundAssembly;
                                    }
                                    settings.ImportReferences.Add(newImportReference);
                                }
                            }
                        }
                    }
                }
            }
            return settings;
        }
 
        [Fx.Tag.SecurityNote(Critical = "Critical because we are accessing critical member AssemblyCache.XmlnsMappings.",
            Safe = "Safe because we prevent partial trusted code from manipulating the cache directly by creating a read-only wrapper around the cached XmlnsMapping.")]
        [SecuritySafeCritical]
        private static void WrapCachedMapping(NamespaceDeclaration prefix, out ReadOnlyXmlnsMapping readOnlyMapping)
        {
            XmlnsMapping mapping = new XmlnsMapping();
            XNamespace xmlns = XNamespace.Get(prefix.Namespace);
 
            if (!AssemblyCache.XmlnsMappings.TryGetValue(xmlns, out mapping))
            {
                // Match a namespace of the form "clr-namespace:<namespace-name>;assembly=<assembly-name>"
 
                Match match = assemblyQualifiedNamespaceRegex.Match(prefix.Namespace);
 
                if (match.Success)
                {
                    mapping.ImportReferences = new HashSet<VisualBasicImportReference>();
                    mapping.ImportReferences.Add(
                        new VisualBasicImportReference
                        {
                            Assembly = match.Groups["assembly"].Value,
                            Import = match.Groups["namespace"].Value,
                            Xmlns = xmlns
                        });
                }
                else
                {
                    mapping.ImportReferences = new HashSet<VisualBasicImportReference>();
                }
                AssemblyCache.XmlnsMappings[xmlns] = mapping;
            }
 
            // ReadOnlyXmlnsMapping constructor tolerates an empty mapping being passed in.
            readOnlyMapping = new ReadOnlyXmlnsMapping(mapping);
        }
 
        /// <summary>
        /// Static class used to cache assembly metadata.
        /// </summary>
        /// <remarks>
        ///     <list type="bullet">
        ///         <item><description>
        ///         XmlnsMappings for static assemblies are not GC'd. In v4.0 we can assume that all static assemblies 
        ///         containing XmlnsDefinition attributes are non-collectible. The CLR will provide no public mechanism 
        ///         for unloading a static assembly or specifying that a static assembly is collectible. While there 
        ///         may be some small number of assemblies identified by the CLR as collectible, none will contain 
        ///         XmlnsDefinition attributes. Should the CLR provide a public mechanism for unloading a static assembly
        ///         or specifying that a static assembly is collectible, we should revisit this decision based on scenarios
        ///         that flow from these mechanisms.
        ///         </description></item>
        ///         <item><description>
        ///         XmlnsMappings for dynamic assemblies are not created. This is because the hosted Visual Basic compiler
        ///         does not support dynamic assembly references. Should support for dynamic assembly references be 
        ///         added to the Visual Basic compiler, we should strip away Assembly.IsDynamic checks from this class and
        ///         update the code ensure that VisualBasicImportReference instances are removed in a timely manner.
        ///         </description></item>
        ///     </list>
        /// </remarks>
        static class AssemblyCache
        {
            static bool initialized = false;
 
            // This is here so that obtaining the lock is not required to be SecurityCritical.
            public static object XmlnsMappingsLockObject = new object();
 
            [Fx.Tag.SecurityNote(Critical = "Critical because we are storing assembly references and if we alloed PT access, they could mess with that.")]
            [SecurityCritical]
            static Dictionary<XNamespace, XmlnsMapping> xmlnsMappings;
 
            public static Dictionary<XNamespace, XmlnsMapping> XmlnsMappings
            {
                [Fx.Tag.SecurityNote(Critical = "Critical because providing access to the critical xmlnsMappings dictionary.")]
                [SecurityCritical]
                get
                {
                    EnsureInitialized();
                    return xmlnsMappings;
                }
            }
 
            [Fx.Tag.SecurityNote(Critical = "Critical because we are accessing critical member xmlnsMappings and CacheLoadedAssembly. Only called from CLR.")]
            [SecurityCritical]
            static void OnAssemblyLoaded(object sender, AssemblyLoadEventArgs args)
            {
                Assembly assembly = args.LoadedAssembly;
 
                if (assembly.IsDefined(typeof(XmlnsDefinitionAttribute), false) && !assembly.IsDynamic)
                {
                    // Don't lock XmlnsMappingsLockObject here. Leave it up to CacheLoadedAssembly to do it. If we
                    // do it here, we could get a deadlock because the GetCustomAttribute in CacheLoadedAssembly might
                    // induce another attempt to load an assembly whose "load lock" is already held by another thread
                    // that may end up waiting for this XmlnsMappingsLockObject. (DevDiv 251926)
                    CacheLoadedAssembly(assembly);
                }
            }
 
            [Fx.Tag.SecurityNote(Critical = "Critical because we are accessing AppDomain.AssemblyLoaded and we are accessing critical member xmlnsMappings.")]
            [SecurityCritical]
            static void EnsureInitialized()            
            {
                if (initialized)
                {
                    return;
                }
 
                if (xmlnsMappings == null)
                {
                    Interlocked.CompareExchange(ref xmlnsMappings,
                        new Dictionary<XNamespace, XmlnsMapping>(new XNamespaceEqualityComparer()),
                        null);
                }
 
                lock (XmlnsMappingsLockObject)
                {
                    if (AssemblyCache.initialized)
                    {
                        return;
                    }
 
                    AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoaded;
 
                    Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
 
                    for (int i = 0; i < assemblies.Length; ++i)
                    {
                        Assembly assembly = assemblies[i];
 
                        if (assembly.IsDefined(typeof(XmlnsDefinitionAttribute), false) && ! assembly.IsDynamic)
                        {
                            CacheLoadedAssembly(assembly);
                        }
                    }
                    
                    initialized = true;
                }
            }
 
            [Fx.Tag.SecurityNote(Critical = "Critical because we are accessing critical member xmlnsMappings.")]
            [SecurityCritical]
            static void CacheLoadedAssembly(Assembly assembly)
            {
                // this VBImportReference is only used as an entry to the xmlnsMappings cache
                // and is never meant to be Xaml serialized.
                // those VBImportReferences that are to be Xaml serialized are created by Clone() method.
                XmlnsDefinitionAttribute[] attributes = (XmlnsDefinitionAttribute[])assembly.GetCustomAttributes(typeof(XmlnsDefinitionAttribute), false);
                string assemblyName = assembly.FullName;
                XmlnsMapping mapping;
 
                // Obtain the lock that is protecting the xmlnsMappings dictionary because we are going
                // to access it and potentially modify it here. Do the lock here, rather than requiring
                // the caller to do it because GetCustomAttributes may end up triggering another
                // assembly load during OnAssemblyLoaded processing, which could cause a deadlock with
                // a lock used when loading an assembly that is out of our control. One caller - EnsureInitialized -
                // will get the lock before calling this. But that's okay because locking again on the same thread
                // works. (DevDiv 251926)
                // Note that CollectXmlNamespacesAndAssemblies also does this lock because it calls
                // WrapCachedMapping, which also access and potentially modifieds the xmlnsMapping dictionary.
                lock (XmlnsMappingsLockObject)
                {
                    for (int i = 0; i < attributes.Length; ++i)
                    {
                        XNamespace xmlns = XNamespace.Get(attributes[i].XmlNamespace);
 
                        if (!xmlnsMappings.TryGetValue(xmlns, out mapping))
                        {
                            mapping.ImportReferences = new HashSet<VisualBasicImportReference>();
                            xmlnsMappings[xmlns] = mapping;
                        }
 
                        VisualBasicImportReference newImportReference = new VisualBasicImportReference
                        {
                            Assembly = assemblyName,
                            Import = attributes[i].ClrNamespace,
                            Xmlns = xmlns,
                        };
                        // early binding the assembly
                        // this leads to the short-cut, skipping the normal assembly resolution routine
                        newImportReference.EarlyBoundAssembly = assembly;
                        mapping.ImportReferences.Add(newImportReference);
                    }
                }
            }
 
            class XNamespaceEqualityComparer : IEqualityComparer<XNamespace>
            {
                public XNamespaceEqualityComparer()
                { }
 
                bool IEqualityComparer<XNamespace>.Equals(XNamespace x, XNamespace y)
                {
                    return x == y;
                }
 
                int IEqualityComparer<XNamespace>.GetHashCode(XNamespace x)
                {
                    return x.GetHashCode();
                }
            }
        }
 
        /// <summary>
        /// Struct used to cache XML Namespace mappings. 
        /// </summary>
        struct XmlnsMapping
        {
            public HashSet<VisualBasicImportReference> ImportReferences;
 
            public bool IsEmpty
            {
                get
                {
                    return this.ImportReferences == null || this.ImportReferences.Count == 0;
                }
            }
        }
 
        [Fx.Tag.SecurityNote(Critical = "Critical because we are accessing a XmlnsMapping that is stored in the XmlnsMappings cache, which is SecurityCritical.",
            Safe = "Safe because we are wrapping the XmlnsMapping and not allowing unsafe code to modify it.")]
        [SecuritySafeCritical]
        struct ReadOnlyXmlnsMapping
        {
            XmlnsMapping wrappedMapping;
 
            internal ReadOnlyXmlnsMapping(XmlnsMapping mapping)
            {
                this.wrappedMapping = mapping;
            }
 
            internal bool IsEmpty
            {
                get
                {
                    return this.wrappedMapping.IsEmpty;
                }
            }
 
            internal IEnumerable<ReadOnlyVisualBasicImportReference> ImportReferences
            {
                get
                {
                    foreach (VisualBasicImportReference wrappedReference in this.wrappedMapping.ImportReferences)
                    {
                        yield return new ReadOnlyVisualBasicImportReference(wrappedReference);
                    }
                }
            }
        }
 
        [Fx.Tag.SecurityNote(Critical = "Critical because we are accessing a VisualBasicImportReference that is stored in the XmlnsMappings cache, which is SecurityCritical.",
            Safe = "Safe because we are wrapping the VisualBasicImportReference and not allowing unsafe code to modify it.")]
        [SecuritySafeCritical]
        struct ReadOnlyVisualBasicImportReference
        {
            readonly VisualBasicImportReference wrappedReference;
 
            internal ReadOnlyVisualBasicImportReference(VisualBasicImportReference referenceToWrap)
            {
                this.wrappedReference = referenceToWrap;
            }
 
            // If this is ever needed, uncomment this. It is commented out now to avoid FxCop violation because it is not called.
            //internal string Assembly
            //{
            //    get
            //    {
            //        return this.wrappedReference.Assembly;
            //    }
            //}
 
            // If this is ever needed, uncomment this. It is commented out now to avoid FxCop violation because it is not called.
            //internal string Import
            //{
            //    get
            //    {
            //        return this.wrappedReference.Import;
            //    }
            //}
 
            internal Assembly EarlyBoundAssembly
            {
                get { return this.wrappedReference.EarlyBoundAssembly; }
            }
 
            internal VisualBasicImportReference Clone()
            {
                return this.wrappedReference.Clone();
            }
 
            // this code is borrowed from XamlSchemaContext
            internal bool AssemblySatisfiesReference(AssemblyName assemblyName)
            {
                if (this.wrappedReference.AssemblyName.Name != assemblyName.Name)
                {
                    return false;
                }
                if (this.wrappedReference.AssemblyName.Version != null && !this.wrappedReference.AssemblyName.Version.Equals(assemblyName.Version))
                {
                    return false;
                }
                if (this.wrappedReference.AssemblyName.CultureInfo != null && !this.wrappedReference.AssemblyName.CultureInfo.Equals(assemblyName.CultureInfo))
                {
                    return false;
                }
                byte[] requiredToken = this.wrappedReference.AssemblyName.GetPublicKeyToken();
                if (requiredToken != null)
                {
                    byte[] actualToken = assemblyName.GetPublicKeyToken();
                    if (!AssemblyNameEqualityComparer.IsSameKeyToken(requiredToken, actualToken))
                    {
                        return false;
                    }
                }
                return true;
            }
 
            public override int GetHashCode()
            {
                return this.wrappedReference.GetHashCode();
            }
 
            // If this is ever needed, uncomment this. It is commented out now to avoid FxCop violation because it is not called.
            //public bool Equals(VisualBasicImportReference other)
            //{
            //    return this.wrappedReference.Equals(other);
            //}
        }
    }
}