File: System\Addin\MiniReflection\MetadataReader\Metadata.cs
Project: ndp\fx\src\AddIn\AddIn\System.AddIn.csproj (System.AddIn)
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.IO;
using System.Diagnostics.Contracts;
 
namespace System.AddIn.MiniReflection.MetadataReader
{
    [Serializable]
    internal struct MetadataToken
    {
        public MDTables.Tables Table;
        public UInt32 Index;   // 1 to N, not 0 to N-1.
 
        public MetadataToken(MDTables.Tables table, UInt32 index)
        {
            System.Diagnostics.Contracts.Contract.Requires((index & 0xFF000000U) == 0);
 
            Table = table;
            Index = index;
        }
 
        public override String ToString()
        {
            return String.Format(CultureInfo.InvariantCulture, "Table {0} ({1}), entry {2}", MDTables.Names[(Int32)Table], (UInt32)Table, Index);
        }
 
        public String ToMDToken()
        {
            return String.Format(CultureInfo.InvariantCulture, "{0:x2}{1:x6}", (UInt32)Table, Index);
        }
    }
 
 
    internal sealed class MDTables
    {
        internal static readonly String[] Names = new String[(Int32)Tables.MaxTable + 1];
        internal static readonly Boolean[] IsDefined = new Boolean[(Int32)Tables.MaxTable + 1];
        private static readonly UTF8Encoding Encoder = new UTF8Encoding(false, true);
 
        // Per-Instance data
        internal BinaryReader B;
        private StreamDesc stringStream, blobStream;
 
        private UInt32[] lengths;   // Indexed by table, gives length of row in bytes
        private UInt32[] tableAt;   // Indexed by table, gives location in file
        private UInt32[] NRows;     // Indexed by table, gives number of rows
        private UInt32 stringIndex, blobIndex, GUIDIndex; // Bytes required to reference these items
 
        
        internal enum Tables
        {
            Invalid = -1,
            XModule = 0,
            TypeRef = 1,
            TypeDef = 2,
            FieldPtr = 3,		// Not public
            FieldDef = 4,
            MethodPtr  = 5,		// Not public
            MethodDef = 6,
            ParamPtr = 7,		// Not public
            ParamDef = 8,
            InterfaceImpl = 9,
            MemberRef = 10,
            Constant = 11,
            CustomAttribute = 12,
            FieldMarshal = 13,
            DeclSecurity = 14,
            ClassLayout = 15,
            FieldLayout = 16,
            StandAloneSig = 17,
            EventMap = 18,
            EventPtr = 19,		// Not public
            XEvent = 20,
            PropertyMap = 21,
            PropertyPtr = 22,	// Not public
            XProperty = 23,
            MethodSemantics = 24,
            MethodImpl = 25,
            ModuleRef = 26,
            TypeSpec = 27,
            ImplMap = 28,
            FieldRVA = 29,
            // Unused 0x1E = 30 Edit&Continue Log
            // Unused 0x1F = 31 Edit&Continue Map
            XAssembly = 32,
            AssemblyProcessor = 33,
            AssemblyOS = 34,
            AssemblyRef = 35,
            AssemblyRefProcessor = 36,
            AssemblyRefOS = 37,
            File = 38,
            ExportedType = 39,
            ManifestResource = 40,
            NestedClass = 41,
            GenericParam = 42,
            GenericMethod = 43,
            GenericConstraint = 44,
            //From 45 through 63, inclusive, are not used
            MaxTable = 63
        }
 
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification="Not possible")]
        static MDTables()
        {
            Names[(Int32)Tables.AssemblyOS] = "AssemblyOS";
            Names[(Int32)Tables.AssemblyProcessor] = "AssemblyProcessor";
            Names[(Int32)Tables.AssemblyRef] = "AssemblyRef";
            Names[(Int32)Tables.AssemblyRefOS] = "AssemblyRefOS";
            Names[(Int32)Tables.AssemblyRefProcessor] = "AssemblyRefProcessor";
            Names[(Int32)Tables.ClassLayout] = "ClassLayout";
            Names[(Int32)Tables.Constant] = "Constant";
            Names[(Int32)Tables.CustomAttribute] = "CustomAttribute";
            Names[(Int32)Tables.DeclSecurity] = "DeclSecurity";
            Names[(Int32)Tables.EventMap] = "EventMap";
            Names[(Int32)Tables.ExportedType] = "ExportedType";
            Names[(Int32)Tables.FieldDef] = "FieldDef";
            Names[(Int32)Tables.FieldLayout] = "FieldLayout";
            Names[(Int32)Tables.FieldMarshal] = "FieldMarshal";
            Names[(Int32)Tables.FieldRVA] = "FieldRVA";
            Names[(Int32)Tables.File] = "File";
            Names[(Int32)Tables.GenericParam] = "GenericParam";
            Names[(Int32)Tables.GenericMethod] = "GenericMethod";
            Names[(Int32)Tables.GenericConstraint] = "GenericConstraint";
            Names[(Int32)Tables.ImplMap] = "ImplMap";
            Names[(Int32)Tables.InterfaceImpl] = "InterfaceImpl";
            Names[(Int32)Tables.ManifestResource] = "ManifestResource";
            Names[(Int32)Tables.MemberRef] = "MemberRef";
            Names[(Int32)Tables.MethodDef] = "MethodDef";
            Names[(Int32)Tables.MethodImpl] = "MethodImpl";
            Names[(Int32)Tables.MethodSemantics] = "MethodSemantics";
            Names[(Int32)Tables.ModuleRef] = "ModuleRef";
            Names[(Int32)Tables.NestedClass] = "NestedClass";
            Names[(Int32)Tables.ParamDef] = "ParamDef";
            Names[(Int32)Tables.PropertyMap] = "PropertyMap";
            Names[(Int32)Tables.StandAloneSig] = "StandAloneSig";
            Names[(Int32)Tables.TypeDef] = "TypeDef";
            Names[(Int32)Tables.TypeRef] = "TypeRef";
            Names[(Int32)Tables.TypeSpec] = "TypeSpec";
            Names[(Int32)Tables.XAssembly] = "Assembly";
            Names[(Int32)Tables.XEvent] = "Event";
            Names[(Int32)Tables.XModule] = "Module";
            Names[(Int32)Tables.XProperty] = "Property";
// Not public
            Names[(Int32)Tables.FieldPtr] = "FieldPointer";
            Names[(Int32)Tables.MethodPtr] = "MethodPointer";
            Names[(Int32)Tables.ParamPtr] = "ParamPointer";
            Names[(Int32)Tables.EventPtr] = "EventPointer";
            Names[(Int32)Tables.PropertyPtr] = "PropertyPointer";
		    
 
            for (Int32 i = 0; i <= (Int32)Tables.MaxTable; i++)
            {
                IsDefined[i] = Names[i] != null;
                if (!IsDefined[i])
                    Names[i] = String.Format(CultureInfo.InvariantCulture, "<<{0}>>", i);
            }
        }
 
        internal enum Encodings
        {
            TypeDefOrRef, HasConstant, HasCustomAttribute, HasFieldMarshall,
            HasDeclSecurity, MemberRefParent, HasSemantics, MethodDefOrRef,
            MemberForwarded, Implementation, CustomAttributeType,
            ResolutionScope, TypeOrMethodDef,
            //Max = TypeOrMethodDef
        }
 
        private struct EncodingDesc
        {
            String Name;
            internal int TagBits;
            internal Tables[] WhichTables;
 
            internal EncodingDesc(String N, int T, Tables[] W)
            {
                Name = N;
                TagBits = T;
                WhichTables = W;
            }
            /*
            public override String ToString()
            {
                return String.Format("Encoding {0} has {1} tag bits for {2} tables", 
                    Name, TagBits, WhichTables.Length);
            }
             */
        }
 
        private static readonly EncodingDesc[] EncodingDescs = new EncodingDesc[]
            // These are ordered to match their definitions in MD\Runtime\Metamodel.h
            {
                new EncodingDesc("TypeDefOrRef", 2, 
                  new Tables[] { Tables.TypeDef, Tables.TypeRef, Tables.TypeSpec }),
                new EncodingDesc("HasConstant", 2, 
                  new Tables[] { Tables.FieldDef, Tables.ParamDef, Tables.XProperty }),
                new EncodingDesc("HasCustomAttribute", 5, 
                  new Tables[] { Tables.MethodDef, Tables.FieldDef, Tables.TypeRef, Tables.TypeDef, 
                      Tables.ParamDef, Tables.InterfaceImpl, Tables.MemberRef, Tables.XModule,
                      Tables.DeclSecurity, Tables.XProperty, Tables.XEvent, Tables.StandAloneSig,
                      Tables.ModuleRef, Tables.TypeSpec, Tables.XAssembly, Tables.AssemblyRef,
                      Tables.File, Tables.ExportedType, Tables.ManifestResource,
                      // These last three aren't documented, but are allowed
                      Tables.GenericParam, Tables.GenericConstraint, Tables.GenericParam}),
                new EncodingDesc("HasFieldMarshall", 1, 
                  new Tables[] { Tables.FieldDef, Tables.ParamDef }),
                new EncodingDesc("HasDeclSecurity", 2, 
                  new Tables[] { Tables.TypeDef, Tables.MethodDef, Tables.XAssembly }),
                new EncodingDesc("MemberRefParent", 3,
                  // TypeDef isn't documented, but it's allowed
                  new Tables[] { Tables.TypeDef, Tables.TypeRef, Tables.ModuleRef, Tables.MethodDef, Tables.TypeSpec }),
                new EncodingDesc("HasSemantics", 1, 
                  new Tables[] { Tables.XEvent, Tables.XProperty }),
                new EncodingDesc("MethodDefOrRef", 1, 
                  new Tables[] { Tables.MethodDef, Tables.MemberRef }),
                new EncodingDesc("MemberForwarded", 1, 
                  new Tables[] { Tables.FieldDef, Tables.MethodDef }),
                new EncodingDesc("Implementation", 2,
                  new Tables[] { Tables.File, Tables.AssemblyRef, Tables.ExportedType }),
                new EncodingDesc("CustomAttributeType", 3, 
                  new Tables[] { Tables.Invalid, Tables.Invalid, Tables.MethodDef, Tables.MemberRef }),
                new EncodingDesc("ResolutionScope", 2,
                  new Tables[] { Tables.XModule, Tables.ModuleRef, Tables.AssemblyRef, Tables.TypeRef }),
                new EncodingDesc("TypeOrMethodDef", 1,
                  new Tables[] { Tables.TypeDef, Tables.MethodDef })
            };
 
        internal MDTables(BinaryReader reader, StreamDesc stringSD, StreamDesc blobSD)
        { // Positioned at start of #~ stream
            stringStream = stringSD;
            blobStream = blobSD;
            B = reader;
            B.BaseStream.Seek(4, SeekOrigin.Current);   // Skip reserved
            byte Major = B.ReadByte();
            byte Minor = B.ReadByte();
            // Console.WriteLine("Table schema version {0}.{1}", Major, Minor);
            byte HeapSizes = B.ReadByte();
            stringIndex = ((HeapSizes & 0x01) == 0) ? 2U : 4U;
            GUIDIndex = ((HeapSizes & 0x02) == 0) ? 2U : 4U;
            blobIndex = ((HeapSizes & 0x04) == 0) ? 2U : 4U;
            B.ReadByte();   // Reserved
            UInt64 Valid = B.ReadUInt64();
            UInt64 Sorted = B.ReadUInt64();
            NRows = new UInt32[(Int32)Tables.MaxTable + 1];
            int NTables = 0;
            UInt64 VBit = 1UL;
            for (int Table = 0; Table <= (int)Tables.MaxTable; Table++, VBit <<= 1)
            {
                if ((Valid & VBit) != 0)
                {
                    NRows[Table] = B.ReadUInt32();
                    if (NRows[Table] == 0)
                    {
                        //Console.WriteLine("Table {0} is valid with 0 rows", Table);
                    }
                    // Console.WriteLine("Table {0} ({1}) has {2} rows", Names[Table], Table, NRows[Table]);
                    NTables += 1;
                }
            }
            // Console.WriteLine("Found {0} tables", NTables);
            ComputeRowLengths();
            /*
            for (int i = 0; i <= (int) Tables.MaxTable; i++)
                Console.WriteLine("Table {0} ({1}) has {2} bytes per row",
                    Names[i], i, lengths[i]); 
             */
            tableAt = new UInt32[(Int32)Tables.MaxTable + 1];
            VBit = 1UL;
            UInt32 Offset = (UInt32)B.BaseStream.Position;
            for (int Table = 0; Table <= (int)Tables.MaxTable; Table++, VBit <<= 1)
            {
                tableAt[Table] = Offset;
                // Console.WriteLine("Table {2} ({0}) at offset 0x{1:x}", Table, Offset, Names[(UInt32)Table]);
                Offset += lengths[Table] * NRows[Table];
                if (((Valid & VBit) != 0) && (NRows[Table] != 0) && (lengths[Table] == 0))
                    throw new BadImageFormatException(String.Format(CultureInfo.CurrentCulture, Res.UnknownMetadataTable, Table));
            }
        }
 
        UInt32 MetadataTokenSize(Encodings Which)
        {
            EncodingDesc E = EncodingDescs[(int) Which];
            int TagBits = E.TagBits;
            UInt32 MaxRows = 0;
            foreach (Tables Table in E.WhichTables)
            {
                if (Table == Tables.Invalid) continue;
                UInt32 N = NRows[(int)Table];
                if (N > MaxRows) MaxRows = N;
            }
            return (MaxRows < (1 << (16 - TagBits))) ? 2U : 4U;
        }
 
        UInt32 RowSize(Tables Table)
        {
            return (NRows[(int) Table] < (1 << 16)) ? 2U : 4U;
        }
 
        private void ComputeRowLengths()
        {
            lengths = new UInt32[(Int32)Tables.MaxTable + 1];
            lengths[(Int32)Tables.XAssembly] = 4 + 4 * 2 + 4 + blobIndex + 2 * stringIndex;
            lengths[(Int32)Tables.AssemblyOS] = 4 * 3;
            lengths[(Int32)Tables.AssemblyProcessor] = 4;
            lengths[(Int32)Tables.AssemblyRef] = 4 * 2 + 4 + 2 * blobIndex + 2 * stringIndex;
            lengths[(Int32)Tables.AssemblyRefOS] = 3 * 4 + RowSize(Tables.AssemblyRef);
            lengths[(Int32)Tables.AssemblyRefProcessor] = 4 + RowSize(Tables.AssemblyRef);
            lengths[(Int32)Tables.ClassLayout] = 2 + 4 + RowSize(Tables.TypeDef);
            lengths[(Int32)Tables.Constant] = 1 + 1 + MetadataTokenSize(Encodings.HasConstant) + blobIndex;
            lengths[(Int32)Tables.CustomAttribute] = MetadataTokenSize(Encodings.HasCustomAttribute) +
                MetadataTokenSize(Encodings.CustomAttributeType) + blobIndex;
            lengths[(Int32)Tables.DeclSecurity] = 2 + MetadataTokenSize(Encodings.HasDeclSecurity) + blobIndex;
            lengths[(Int32)Tables.EventMap] = RowSize(Tables.TypeDef) + RowSize(Tables.XEvent);
            lengths[(Int32)Tables.XEvent] = 2 + stringIndex + MetadataTokenSize(Encodings.TypeDefOrRef);
            lengths[(Int32)Tables.ExportedType] = 4 + 4 + stringIndex*2 + MetadataTokenSize(Encodings.Implementation);
            lengths[(Int32)Tables.FieldDef] = 2 + stringIndex + blobIndex;
            lengths[(Int32)Tables.FieldLayout] = 4 + RowSize(Tables.FieldDef);
            lengths[(Int32)Tables.FieldMarshal] = MetadataTokenSize(Encodings.HasFieldMarshall) + blobIndex;
            lengths[(Int32)Tables.FieldRVA] = 4 + RowSize(Tables.FieldDef);
            lengths[(Int32)Tables.File] = 4 + stringIndex + blobIndex;
            lengths[(Int32)Tables.GenericParam] = 2 + 2 + MetadataTokenSize(Encodings.TypeOrMethodDef) + stringIndex;
            lengths[(Int32)Tables.GenericMethod] = MetadataTokenSize(Encodings.MethodDefOrRef) + blobIndex;
            lengths[(Int32)Tables.GenericConstraint] = RowSize(Tables.GenericParam) + MetadataTokenSize(Encodings.TypeDefOrRef);
            lengths[(Int32)Tables.ImplMap] = 2 + MetadataTokenSize(Encodings.MemberForwarded) + stringIndex +
                RowSize(Tables.ModuleRef);
            lengths[(Int32)Tables.InterfaceImpl] = RowSize(Tables.TypeDef) + MetadataTokenSize(Encodings.TypeDefOrRef);
            lengths[(Int32)Tables.ManifestResource] = 4 + 4 + stringIndex + MetadataTokenSize(Encodings.Implementation);
            lengths[(Int32)Tables.MemberRef] = MetadataTokenSize(Encodings.MemberRefParent) + stringIndex +
                blobIndex;
            lengths[(Int32)Tables.MethodDef] = 4 + 2 + 2 + stringIndex + 
                blobIndex + RowSize(Tables.ParamDef);
            lengths[(Int32)Tables.MethodImpl] = RowSize(Tables.TypeDef) + MetadataTokenSize(Encodings.MethodDefOrRef) * 2;
            lengths[(Int32)Tables.MethodSemantics] = 2 + RowSize(Tables.MethodDef) + MetadataTokenSize(Encodings.HasSemantics);
            lengths[(Int32)Tables.XModule] = 2 + stringIndex + GUIDIndex * 3;
            lengths[(Int32)Tables.ModuleRef] = stringIndex;
            lengths[(Int32)Tables.NestedClass] = RowSize(Tables.TypeDef) * 2;
            lengths[(Int32)Tables.ParamDef] = 2 + 2 + stringIndex;
            lengths[(Int32)Tables.XProperty] = 2 + stringIndex + blobIndex;
            lengths[(Int32)Tables.PropertyMap] = RowSize(Tables.TypeDef) + RowSize(Tables.XProperty);
            lengths[(Int32)Tables.StandAloneSig] = blobIndex;
            lengths[(Int32)Tables.TypeDef] = 4 + stringIndex*2 + MetadataTokenSize(Encodings.TypeDefOrRef) +
                RowSize(Tables.FieldDef) + RowSize(Tables.MethodDef);
            lengths[(Int32)Tables.TypeRef] = MetadataTokenSize(Encodings.ResolutionScope) + stringIndex*2;
            lengths[(Int32)Tables.TypeSpec] = blobIndex;
// Non-public
		    lengths[(Int32)Tables.FieldPtr] = RowSize(Tables.FieldDef);
            lengths[(Int32)Tables.MethodPtr] = RowSize(Tables.MethodDef);
            lengths[(Int32)Tables.ParamPtr] = RowSize(Tables.ParamDef);
            lengths[(Int32)Tables.EventPtr] = RowSize(Tables.XEvent);
            lengths[(Int32)Tables.PropertyPtr] = RowSize(Tables.XProperty);
            return;
        }
 
        // This method is great for iterating over rows, starting at 0.  But metadata tokens start
        // numbering at row 1.  So this can't be used if you've retrieved a token from the metadata!
        internal void SeekToRowOfTable(Tables T, UInt32 Row)
        {
            if (Row >= NRows[(Int32) T])
                throw new BadImageFormatException(
                    String.Format(CultureInfo.CurrentCulture, Res.InvalidMetadataTokenTooManyRows,
                                  Row, T, Names[(Int32) T], NRows[(Int32) T]));
            B.BaseStream.Seek(tableAt[(Int32)T]+Row*lengths[(Int32)T], SeekOrigin.Begin);
        }
 
        // Use this method whenever using a MetadataToken, otherwise you'll be off by 1!
        internal void SeekToMDToken(MetadataToken token)
        {
            if (token.Index == 0)
                throw new BadImageFormatException(String.Format(CultureInfo.CurrentCulture, Res.NilMetadataToken, token));
            SeekToRowOfTable(token.Table, token.Index - 1);
        }
 
        internal UInt32 ReadStringIndex()
        {
            if (stringIndex == 2) return (UInt32)B.ReadUInt16();
            else return B.ReadUInt32();
        }
 
        private String GetString(UInt32 index)
        { // Read a string from the string stream
            stringStream.SeekTo(B, index);
            return ReadNextString();
        }
 
        private String ReadNextString()
        {
            List<byte> bytes = new List<byte>();
            byte next;
            while ((next = B.ReadByte()) != 0)
                bytes.Add(next);
            return Encoder.GetString(bytes.ToArray());
        }
 
        /*
        internal void DumpStringHeap(TextWriter output)
        {
            stringStream.SeekTo(B, 0);
            long Start = B.BaseStream.Position;
            long End = Start + stringStream.Size - 1;
            output.WriteLine("String heap: {0}(0x{0:x}) bytes", End - Start);
            for (long i = Start; i < End; i = B.BaseStream.Position)
            {
                output.WriteLine("0x{0:x}: '{1}'", i - Start, ReadNextString());
            }
        }
         */
 
        internal byte[] ReadBlob()
        {
            UInt32 Index = ReadBlobIndex();
            if (Index == 0) return null;
            UInt32 Position = (UInt32)B.BaseStream.Position;
            blobStream.SeekTo(B, Index);
            UInt32 NBytes = (UInt32)B.ReadByte();
            Byte[] Result = null;
            if ((NBytes & 0x80) != 0)
            {
                if ((NBytes & 0xC0) == 0x80)
                    NBytes = ((NBytes & 0x7F) << 8) + B.ReadByte();
                else NBytes = ((NBytes & 0x7F) << 24) +
                    (UInt16)(B.ReadByte() << 16) +
                    (B.ReadUInt16());
            }
            if (NBytes != 0)
            {
                Result = new byte[NBytes];
                for (UInt32 i = 0; i < NBytes; i++)
                    Result[i] = B.ReadByte();
            }
            B.BaseStream.Seek(Position, SeekOrigin.Begin);
            return Result;
        }
 
        internal String ReadString()
        {
            UInt32 index = ReadStringIndex();
            UInt32 position = (UInt32) B.BaseStream.Position;
            String result = GetString(index);
            B.BaseStream.Seek(position, SeekOrigin.Begin);
            return result;
        }
 
        /*
        internal UInt32 ReadGUIDIndex()
        {
            if (GUIDIndex == 2) 
                return (UInt32)B.ReadUInt16();
            else 
                return B.ReadUInt32();
        }
        */
 
        internal UInt32 ReadBlobIndex()
        {
            if (blobIndex == 2) 
                return (UInt32)B.ReadUInt16();
            else 
                return B.ReadUInt32();
        }
 
        internal UInt32 ReadRowIndex(Tables T)
        {
            if (RowSize(T) == 2) 
                return (UInt32)B.ReadUInt16();
            else 
                return B.ReadUInt32();
        }
 
        internal UInt32 RowsInTable(Tables T)
        {
            return NRows[(UInt32)T];
        }
 
        /*
        internal UInt32 ReadEncodedInt()
        {
            byte first = B.ReadByte();
            if ((first & 0x80) == 0)
                return first;
            byte second = B.ReadByte();
            if ((first & 0xC0) == 0x80)
                return ((uint)(first & 0x7F)) << 8 | second;
            else if ((first & 0xE0) == 0xC0)
            {
                byte third = B.ReadByte();
                byte fourth = B.ReadByte();
                return ((uint)(first & 0x1F) << 24) | ((uint)second) << 16 | ((uint)third) << 8 | (uint)fourth;
            }
            else
                throw new BadImageFormatException(Res.InvalidCompressedInt);
        }
        */
 
        internal MetadataToken ReadMetadataToken(Encodings E)
        {
            UInt32 Code = (MetadataTokenSize(E) == 2) ? B.ReadUInt16() : B.ReadUInt32();
            MetadataToken Result;
            EncodingDesc Desc = EncodingDescs[(Int32)E];
            Tables[] Ts = Desc.WhichTables;
            UInt32 Mask = (1U << Desc.TagBits) - 1;
            Result.Table = Ts[Code & Mask];
            Result.Index = Code >> Desc.TagBits;
            if (Result.Table == Tables.Invalid)
                throw new BadImageFormatException(Res.InvalidMetadataTokenNilTable);
            return Result;
        }
    }
}