File: System\Addin\MiniReflection\MiniAssembly.cs
Project: ndp\fx\src\AddIn\AddIn\System.AddIn.csproj (System.AddIn)
// ==++==
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==
/*============================================================
**
** Class:  MiniAssembly
**
** Purpose: Wraps an assembly, using a managed PE reader to
**     interpret the metadata.
**
===========================================================*/
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Text;
using System.AddIn.MiniReflection.MetadataReader;
using System.Diagnostics;
using System.AddIn.Hosting;
using System.Diagnostics.Contracts;
 
namespace System.AddIn.MiniReflection
{
    [Serializable]
    internal sealed class MiniAssembly : MiniModule
    {
        private enum Representation {
            PEFileReader = 1,
            ReflectionAssembly = 2,
        }
 
        // When loading other assemblies, which directory should we look in?
        private List<String> _dependencyDirs;
        private Representation _representation;
        private System.Reflection.Assembly _reflectionAssembly;
        private String _fullName;
 
        public MiniAssembly(String peFileName) : base(peFileName)
        {
            _dependencyDirs = new List<String>();
            _dependencyDirs.Add(Path.GetDirectoryName(peFileName));
            Assembly = this;
            _representation = Representation.PEFileReader;
        }
 
        public MiniAssembly(System.Reflection.Assembly assembly)
        {
            System.Diagnostics.Contracts.Contract.Requires(assembly != null);
 
            _reflectionAssembly = assembly;
            _representation = Representation.ReflectionAssembly;
        }
 
        public List<String> DependencyDirs
        {
            get { return _dependencyDirs; }
        }
 
        internal bool IsReflectionAssembly {
            get { return (_representation & Representation.ReflectionAssembly) != 0; }
        }
 
        public MiniModule[] GetModules()
        {
            // There are two forms of "multiple module" assemblies.  The first is a 
            // multi-module assembly where the assembly's ModuleRef lists multiple 
            // different modules.  The second is a multi-file assembly, where an
            // entry in the File metadata table refers to another file on disk and
            // explicitly declares the file contains metadata (and potentially types).
            // ALink can produce .netmodules, which seem to show up as multi-file 
            // assemblies.
            MDTables metaData = _peFile.MetaData;
            
            for(uint i=0; i<metaData.RowsInTable(MDTables.Tables.File); i++)
            {
                metaData.SeekToRowOfTable(MDTables.Tables.File, i);
                MDFileAttributes attrs = (MDFileAttributes) metaData.B.ReadUInt32();
                if ((attrs & MDFileAttributes.ContainsNoMetaData) != 0)
                    continue;
                System.Diagnostics.Contracts.Contract.Assert(attrs == MDFileAttributes.ContainsMetaData);
                throw new NotImplementedException(String.Format(CultureInfo.CurrentCulture, Res.MultiFileAssembliesNotSupported, FullName));
            }
 
            return new MiniModule[] { this };
        }
 
        // Only returns public types.
        public IList<TypeInfo> GetTypesWithAttribute(Type customAttribute)
        {
            return GetTypesWithAttribute(customAttribute, false);
        }
 
        public TypeInfo FindTypeInfo(String typeName, String nameSpace)
        {
            System.Diagnostics.Contracts.Contract.Assert(!IsReflectionAssembly); // Can be implemented using Assembly.GetType
            MetadataToken token = FindTypeDef(_peFile, _peFile.MetaData, typeName, nameSpace);
            return new TypeInfo(token, this, typeName, nameSpace);
        }
 
        private static MetadataToken FindTypeDef(PEFileReader peFile, MDTables mdScope, String typeName, String nameSpace)
        {
            System.Diagnostics.Contracts.Contract.Requires(typeName != null);
 
            uint numTypeDefs = mdScope.RowsInTable(MDTables.Tables.TypeDef);
            for (uint i = 0; i < numTypeDefs; i++) {
                mdScope.SeekToRowOfTable(MDTables.Tables.TypeDef, i);
                peFile.B.ReadUInt32();  // TypeAttributes
                String rowTypeName = mdScope.ReadString();
                if (!String.Equals(typeName, rowTypeName))
                    continue;
                String rowNameSpace= mdScope.ReadString();
                if (!String.Equals(nameSpace, rowNameSpace))
                    continue;
                return new MetadataToken(MDTables.Tables.TypeDef, i + 1);
            }
            throw new TypeLoadException(String.Format(CultureInfo.CurrentCulture, Res.CantFindTypeName, nameSpace, typeName));
        }
 
        // For each module in the assembly, give back all types with the 
        // given attribute, possibly respecting the type's visibility.
        public IList<TypeInfo> GetTypesWithAttribute(Type customAttribute, bool includePrivate)
        {
            if (IsDisposed)
                throw new ObjectDisposedException(null);
            if (customAttribute == null)
                throw new ArgumentNullException("customAttribute");
            System.Diagnostics.Contracts.Contract.EndContractBlock();
 
            List<TypeInfo> types = new List<TypeInfo>();
            foreach (MiniModule module in GetModules())
            {
                IList<TypeInfo> newTypes = module.GetTypesWithAttributeInModule(customAttribute, includePrivate);
                types.AddRange(newTypes);
            }
            return types;
        }
 
        public MiniAssembly ResolveAssemblyRef(MetadataToken token, bool throwOnError)
        {
            System.Diagnostics.Contracts.Contract.Requires(token.Table == MDTables.Tables.AssemblyRef);
            PEFileReader peFile = this.PEFileReader;
            MDTables metaData = peFile.MetaData;
 
            metaData.SeekToMDToken(token);
            peFile.B.ReadUInt64();  // Skip 4 parts of the version number.
            peFile.B.ReadUInt32();  // AssemblyFlags
            byte[] publicKeyOrToken = metaData.ReadBlob(); // Public key or token
            String assemblySimpleName = metaData.ReadString(); // simple name
            String cultureName = metaData.ReadString();  // assembly culture
            if (!String.IsNullOrEmpty(cultureName))
                throw new BadImageFormatException(Res.UnexpectedlyLoadingASatellite, FullName);
 
            if (assemblySimpleName == "mscorlib" && (cultureName.Length == 0 || cultureName == "neutral"))
                return new MiniAssembly(typeof(Object).Assembly);
 
            MiniAssembly loadedAssembly = Open(assemblySimpleName, _dependencyDirs, throwOnError);
 
            if (loadedAssembly != null)
            {
                // Check whether the reference to the assembly matches what we actually loaded.
                // We don't respect the "throwOnError" parameter here because if someone does
                // violate this, they've either severely messed up their deployment, or they're
                // attempting a security exploit.
                System.Reflection.AssemblyName loadedAssemblyName = 
                    new System.Reflection.AssemblyName(loadedAssembly.FullName);
 
                if (!Utils.PublicKeyMatches(loadedAssemblyName, publicKeyOrToken))
                {
                    throw new FileLoadException(String.Format(CultureInfo.CurrentCulture, Res.AssemblyLoadRefDefMismatch, 
                        assemblySimpleName, publicKeyOrToken, loadedAssemblyName.GetPublicKeyToken()));
                }
 
                if (!String.IsNullOrEmpty(loadedAssemblyName.CultureInfo.Name))
                {
                    throw new FileLoadException(String.Format(CultureInfo.CurrentCulture, Res.AssemblyLoadRefDefMismatch,
                        assemblySimpleName, String.Empty, loadedAssemblyName.CultureInfo.Name));
                }
            }
            return loadedAssembly;
        }
 
        public static MiniAssembly Open(String simpleName, IList<String> dependencyDirs, bool throwOnError)
        {
            String fileName = FindAssembly(simpleName, dependencyDirs, throwOnError);
            if (!throwOnError && fileName == null)
                return null;
            return new MiniAssembly(fileName);
        }
 
        private static String FindAssembly(String simpleName, IList<String> searchDirs, bool throwOnError)
        {
            System.Diagnostics.Contracts.Contract.Requires(!String.IsNullOrEmpty(simpleName));
            System.Diagnostics.Contracts.Contract.Requires(searchDirs != null);
 
            String libName = simpleName + ".dll";
            String exeName = simpleName + ".exe";
            foreach (String dir in searchDirs)
            {
                String fileName = Path.Combine(dir, libName);
                if (File.Exists(fileName))
                    return fileName;
 
                fileName = Path.Combine(dir, exeName);
                if (File.Exists(fileName))
                    return fileName;
            }
            if (throwOnError)
                throw new FileNotFoundException(String.Format(CultureInfo.CurrentCulture, Res.FileNotFoundForInspection, simpleName), libName);
            else
                return null;
        }
 
        internal String FullName {
            get {
                if ((_representation & Representation.ReflectionAssembly) != 0)
                    return AppDomain.CurrentDomain.ApplyPolicy(_reflectionAssembly.FullName);
 
                if (_fullName == null) {
                    AssemblyInfo assemblyInfo = new AssemblyInfo();
                    _peFile.GetAssemblyInfo(ref assemblyInfo);
                    _fullName = AppDomain.CurrentDomain.ApplyPolicy(assemblyInfo.ToString());
                }
 
                return _fullName;
            }
        }
 
        public override bool Equals(object obj)
        {
            MiniAssembly thatAssembly = obj as MiniAssembly;
            if (thatAssembly == null)
                return false;
            // Note that assembly binding redirects and publisher policy will affect
            // versioning (ie binds to 2.0.0.0 redirected to 2.0.1.5).  But for 
            // the Orcas release, we're only planning on using our existing 
            // directory structure, which does not have a great servicing story.
            // As long as we use ReflectionOnlyLoad during discovery, we should 
            // be fetching the exact assembly on disk used to build the entire 
            // pipeline (modulo in-place updates which must keep the same 
            // assembly version number), and we'll load the right version 
            // during activation time.  That should work for Orcas.
            // Long term, we may need an approximately-equals method that gets
            // the assembly info and compares the version number w.r.t. policy.
            return Utils.AssemblyDefEqualsDef(FullName, thatAssembly.FullName);
        }
 
        // See the comments in Equals - we're doing string comparisons here
        // to compare full type names.
        public static bool Equals(MiniAssembly assemblyA, PEFileReader peFileB, MetadataToken assemblyRefB)
        {
            System.Diagnostics.Contracts.Contract.Requires(assemblyA != null);
            System.Diagnostics.Contracts.Contract.Requires(peFileB != null);
            System.Diagnostics.Contracts.Contract.Requires(assemblyRefB.Table == MDTables.Tables.AssemblyRef);
 
            String nameA, nameRefB;
            if (assemblyA.IsReflectionAssembly)
                nameA = AppDomain.CurrentDomain.ApplyPolicy(assemblyA._reflectionAssembly.FullName);
            else
            {
                AssemblyInfo assemblyInfoA = new AssemblyInfo();
                assemblyA._peFile.GetAssemblyInfo(ref assemblyInfoA);
                nameA = AppDomain.CurrentDomain.ApplyPolicy(assemblyInfoA.ToString());
            }
 
            AssemblyInfo assemblyInfoB = ReadAssemblyRef(peFileB, assemblyRefB);
            nameRefB = AppDomain.CurrentDomain.ApplyPolicy(assemblyInfoB.ToString());
 
            return Utils.AssemblyRefEqualsDef(nameRefB, nameA);
        }
 
        private static AssemblyInfo ReadAssemblyRef(PEFileReader peFile, MetadataToken assemblyRef)
        {
            System.Diagnostics.Contracts.Contract.Requires(peFile != null);
            System.Diagnostics.Contracts.Contract.Requires(assemblyRef.Table == MDTables.Tables.AssemblyRef);
            MDTables metaData = peFile.MetaData;
            BinaryReader B = metaData.B;
 
            metaData.SeekToMDToken(assemblyRef);
            UInt16 major = B.ReadUInt16();
            UInt16 minor = B.ReadUInt16();
            UInt16 build = B.ReadUInt16();
            UInt16 revision = B.ReadUInt16();
            Version v = new Version(major, minor, build, revision);
            UInt32 assemblyFlags = B.ReadUInt32();
            byte[] publicKey = metaData.ReadBlob();
            String simpleName = metaData.ReadString();
            String culture = metaData.ReadString();
            if ((culture != null) && (culture.Length == 0)) culture = null;
            return new AssemblyInfo(v, assemblyFlags, publicKey, simpleName, culture);
        }
 
        public override int GetHashCode()
        {
            return FullName.GetHashCode();
        }
    }
}