/* Copyright (C) 2014-2019 de4dot@gmail.com This file is part of dnSpy dnSpy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. dnSpy is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with dnSpy. If not, see . */ using System; using System.Collections.Generic; using System.Diagnostics; using dnlib.DotNet.MD; namespace dnSpy.AsmEditor.Compiler.MDEditor { sealed class TablesMDHeap : MDHeap { readonly MetadataEditor mdEditor; readonly TablesStream tablesStream; public string Name => tablesStream.Name; public abstract class TableInfo { public abstract MDTable MDTable { get; } public abstract bool HasChanges { get; } public abstract uint FirstModifiedRowId { get; } public bool HasNewRows => Rows > MDTable.Rows; public bool IsEmpty => Rows == 0; public abstract uint Rows { get; } public abstract void WriteRow(uint rowIndex, IList newColumns, byte[] destination, int destinationIndex); } public unsafe sealed class TableInfo : TableInfo where TRow : struct { public override MDTable MDTable { get; } readonly RawRowColumnReader.ReadColumnDelegate readColumn; readonly RawModuleBytes moduleData; readonly byte* peFile; readonly Func readRow; readonly Dictionary rowsDict; readonly uint originalLastRid; uint nextRid; uint firstModifiedRid; public TableInfo(RawModuleBytes moduleData, MDTable mdTable, Func readRow) { readColumn = (RawRowColumnReader.ReadColumnDelegate)RawRowColumnReader.GetDelegate(mdTable.Table); this.moduleData = moduleData; peFile = (byte*)moduleData.Pointer; MDTable = mdTable; this.readRow = readRow; rowsDict = new Dictionary(); originalLastRid = mdTable.Rows + 1; nextRid = originalLastRid; firstModifiedRid = originalLastRid; } public uint Create() { uint rid = nextRid++; rowsDict.Add(rid, default); Debug.Assert(firstModifiedRid <= rid); return rid; } public TRow Get(uint rid) { Debug.Assert(rid != 0); if (rid >= firstModifiedRid && rowsDict.TryGetValue(rid, out var row)) return row; return readRow(rid); } public void Set(uint rid, ref TRow row) { Debug.Assert(rid != 0); rowsDict[rid] = row; firstModifiedRid = Math.Min(firstModifiedRid, rid); } public override bool HasChanges => rowsDict.Count > 0; public override uint FirstModifiedRowId => firstModifiedRid; public override uint Rows => nextRid - 1; public override void WriteRow(uint rowIndex, IList newColumns, byte[] destination, int destinationIndex) { var oldColumns = MDTable.Columns; var rid = rowIndex + 1; if (rid >= firstModifiedRid && rowsDict.TryGetValue(rid, out var row)) { for (int i = 0; i < newColumns.Count; i++) { uint value = readColumn(ref row, i); switch (newColumns[i].Size) { case 1: Debug.Assert(newColumns[i].Size == oldColumns[i].Size); Debug.Assert(value <= byte.MaxValue); destination[destinationIndex++] = (byte)value; break; case 2: // The old and new sizes should match, unless the metadata writer used eg. BigStrings // when it wasn't needed. //Debug.Assert(newColumns[i].Size == oldColumns[i].Size); Debug.Assert(value <= ushort.MaxValue); destination[destinationIndex++] = (byte)value; destination[destinationIndex++] = (byte)(value >> 8); break; case 4: destination[destinationIndex++] = (byte)value; destination[destinationIndex++] = (byte)(value >> 8); destination[destinationIndex++] = (byte)(value >> 16); destination[destinationIndex++] = (byte)(value >> 24); break; default: throw new InvalidOperationException(); } } } else { // We're here if the original metadata row hasn't changed. Convert it from the // old layout to the new layout (most likely they're identical) Debug.Assert(rowIndex < MDTable.Rows); var p = peFile + (int)MDTable.StartOffset + (int)(rowIndex * MDTable.RowSize); Debug.Assert(oldColumns.Count == newColumns.Count); for (int i = 0; i < newColumns.Count; i++) { switch (newColumns[i].Size) { case 1: Debug.Assert(newColumns[i].Size == oldColumns[i].Size); destination[destinationIndex++] = *p++; break; case 2: // The old and new sizes should match, unless the metadata writer used eg. BigStrings // when it wasn't needed. //Debug.Assert(newColumns[i].Size == oldColumns[i].Size); destination[destinationIndex++] = *p++; destination[destinationIndex++] = *p++; break; case 4: Debug.Assert(oldColumns[i].Size == 2 || oldColumns[i].Size == 4); if (oldColumns[i].Size == 2) { destination[destinationIndex++] = *p++; destination[destinationIndex++] = *p++; destination[destinationIndex++] = 0; destination[destinationIndex++] = 0; } else { destination[destinationIndex++] = *p++; destination[destinationIndex++] = *p++; destination[destinationIndex++] = *p++; destination[destinationIndex++] = *p++; } break; default: throw new InvalidOperationException(); } } } } } public TableInfo ModuleTable { get; } public TableInfo TypeRefTable { get; } public TableInfo TypeDefTable { get; } public TableInfo FieldPtrTable { get; } public TableInfo FieldTable { get; } public TableInfo MethodPtrTable { get; } public TableInfo MethodTable { get; } public TableInfo ParamPtrTable { get; } public TableInfo ParamTable { get; } public TableInfo InterfaceImplTable { get; } public TableInfo MemberRefTable { get; } public TableInfo ConstantTable { get; } public TableInfo CustomAttributeTable { get; } public TableInfo FieldMarshalTable { get; } public TableInfo DeclSecurityTable { get; } public TableInfo ClassLayoutTable { get; } public TableInfo FieldLayoutTable { get; } public TableInfo StandAloneSigTable { get; } public TableInfo EventMapTable { get; } public TableInfo EventPtrTable { get; } public TableInfo EventTable { get; } public TableInfo PropertyMapTable { get; } public TableInfo PropertyPtrTable { get; } public TableInfo PropertyTable { get; } public TableInfo MethodSemanticsTable { get; } public TableInfo MethodImplTable { get; } public TableInfo ModuleRefTable { get; } public TableInfo TypeSpecTable { get; } public TableInfo ImplMapTable { get; } public TableInfo FieldRVATable { get; } public TableInfo ENCLogTable { get; } public TableInfo ENCMapTable { get; } public TableInfo AssemblyTable { get; } public TableInfo AssemblyProcessorTable { get; } public TableInfo AssemblyOSTable { get; } public TableInfo AssemblyRefTable { get; } public TableInfo AssemblyRefProcessorTable { get; } public TableInfo AssemblyRefOSTable { get; } public TableInfo FileTable { get; } public TableInfo ExportedTypeTable { get; } public TableInfo ManifestResourceTable { get; } public TableInfo NestedClassTable { get; } public TableInfo GenericParamTable { get; } public TableInfo MethodSpecTable { get; } public TableInfo GenericParamConstraintTable { get; } public TableInfo DocumentTable { get; } public TableInfo MethodDebugInformationTable { get; } public TableInfo LocalScopeTable { get; } public TableInfo LocalVariableTable { get; } public TableInfo LocalConstantTable { get; } public TableInfo ImportScopeTable { get; } public TableInfo StateMachineMethodTable { get; } public TableInfo CustomDebugInformationTable { get; } public TableInfo[] TableInfos => allTableInfos; readonly TableInfo[] allTableInfos; public TablesMDHeap(MetadataEditor mdEditor, TablesStream tablesStream) { this.mdEditor = mdEditor ?? throw new ArgumentNullException(nameof(mdEditor)); this.tablesStream = tablesStream ?? throw new ArgumentNullException(nameof(tablesStream)); allTableInfos = new TableInfo[64]; allTableInfos[(int)Table.Module] = ModuleTable = new TableInfo(mdEditor.ModuleData, tablesStream.ModuleTable, rid => { this.tablesStream.TryReadModuleRow(rid, out var row); return row; }); allTableInfos[(int)Table.TypeRef] = TypeRefTable = new TableInfo(mdEditor.ModuleData, tablesStream.TypeRefTable, rid => { this.tablesStream.TryReadTypeRefRow(rid, out var row); return row; }); allTableInfos[(int)Table.TypeDef] = TypeDefTable = new TableInfo(mdEditor.ModuleData, tablesStream.TypeDefTable, rid => { this.tablesStream.TryReadTypeDefRow(rid, out var row); return row; }); allTableInfos[(int)Table.FieldPtr] = FieldPtrTable = new TableInfo(mdEditor.ModuleData, tablesStream.FieldPtrTable, rid => { this.tablesStream.TryReadFieldPtrRow(rid, out var row); return row; }); allTableInfos[(int)Table.Field] = FieldTable = new TableInfo(mdEditor.ModuleData, tablesStream.FieldTable, rid => { this.tablesStream.TryReadFieldRow(rid, out var row); return row; }); allTableInfos[(int)Table.MethodPtr] = MethodPtrTable = new TableInfo(mdEditor.ModuleData, tablesStream.MethodPtrTable, rid => { this.tablesStream.TryReadMethodPtrRow(rid, out var row); return row; }); allTableInfos[(int)Table.Method] = MethodTable = new TableInfo(mdEditor.ModuleData, tablesStream.MethodTable, rid => { this.tablesStream.TryReadMethodRow(rid, out var row); return row; }); allTableInfos[(int)Table.ParamPtr] = ParamPtrTable = new TableInfo(mdEditor.ModuleData, tablesStream.ParamPtrTable, rid => { this.tablesStream.TryReadParamPtrRow(rid, out var row); return row; }); allTableInfos[(int)Table.Param] = ParamTable = new TableInfo(mdEditor.ModuleData, tablesStream.ParamTable, rid => { this.tablesStream.TryReadParamRow(rid, out var row); return row; }); allTableInfos[(int)Table.InterfaceImpl] = InterfaceImplTable = new TableInfo(mdEditor.ModuleData, tablesStream.InterfaceImplTable, rid => { this.tablesStream.TryReadInterfaceImplRow(rid, out var row); return row; }); allTableInfos[(int)Table.MemberRef] = MemberRefTable = new TableInfo(mdEditor.ModuleData, tablesStream.MemberRefTable, rid => { this.tablesStream.TryReadMemberRefRow(rid, out var row); return row; }); allTableInfos[(int)Table.Constant] = ConstantTable = new TableInfo(mdEditor.ModuleData, tablesStream.ConstantTable, rid => { this.tablesStream.TryReadConstantRow(rid, out var row); return row; }); allTableInfos[(int)Table.CustomAttribute] = CustomAttributeTable = new TableInfo(mdEditor.ModuleData, tablesStream.CustomAttributeTable, rid => { this.tablesStream.TryReadCustomAttributeRow(rid, out var row); return row; }); allTableInfos[(int)Table.FieldMarshal] = FieldMarshalTable = new TableInfo(mdEditor.ModuleData, tablesStream.FieldMarshalTable, rid => { this.tablesStream.TryReadFieldMarshalRow(rid, out var row); return row; }); allTableInfos[(int)Table.DeclSecurity] = DeclSecurityTable = new TableInfo(mdEditor.ModuleData, tablesStream.DeclSecurityTable, rid => { this.tablesStream.TryReadDeclSecurityRow(rid, out var row); return row; }); allTableInfos[(int)Table.ClassLayout] = ClassLayoutTable = new TableInfo(mdEditor.ModuleData, tablesStream.ClassLayoutTable, rid => { this.tablesStream.TryReadClassLayoutRow(rid, out var row); return row; }); allTableInfos[(int)Table.FieldLayout] = FieldLayoutTable = new TableInfo(mdEditor.ModuleData, tablesStream.FieldLayoutTable, rid => { this.tablesStream.TryReadFieldLayoutRow(rid, out var row); return row; }); allTableInfos[(int)Table.StandAloneSig] = StandAloneSigTable = new TableInfo(mdEditor.ModuleData, tablesStream.StandAloneSigTable, rid => { this.tablesStream.TryReadStandAloneSigRow(rid, out var row); return row; }); allTableInfos[(int)Table.EventMap] = EventMapTable = new TableInfo(mdEditor.ModuleData, tablesStream.EventMapTable, rid => { this.tablesStream.TryReadEventMapRow(rid, out var row); return row; }); allTableInfos[(int)Table.EventPtr] = EventPtrTable = new TableInfo(mdEditor.ModuleData, tablesStream.EventPtrTable, rid => { this.tablesStream.TryReadEventPtrRow(rid, out var row); return row; }); allTableInfos[(int)Table.Event] = EventTable = new TableInfo(mdEditor.ModuleData, tablesStream.EventTable, rid => { this.tablesStream.TryReadEventRow(rid, out var row); return row; }); allTableInfos[(int)Table.PropertyMap] = PropertyMapTable = new TableInfo(mdEditor.ModuleData, tablesStream.PropertyMapTable, rid => { this.tablesStream.TryReadPropertyMapRow(rid, out var row); return row; }); allTableInfos[(int)Table.PropertyPtr] = PropertyPtrTable = new TableInfo(mdEditor.ModuleData, tablesStream.PropertyPtrTable, rid => { this.tablesStream.TryReadPropertyPtrRow(rid, out var row); return row; }); allTableInfos[(int)Table.Property] = PropertyTable = new TableInfo(mdEditor.ModuleData, tablesStream.PropertyTable, rid => { this.tablesStream.TryReadPropertyRow(rid, out var row); return row; }); allTableInfos[(int)Table.MethodSemantics] = MethodSemanticsTable = new TableInfo(mdEditor.ModuleData, tablesStream.MethodSemanticsTable, rid => { this.tablesStream.TryReadMethodSemanticsRow(rid, out var row); return row; }); allTableInfos[(int)Table.MethodImpl] = MethodImplTable = new TableInfo(mdEditor.ModuleData, tablesStream.MethodImplTable, rid => { this.tablesStream.TryReadMethodImplRow(rid, out var row); return row; }); allTableInfos[(int)Table.ModuleRef] = ModuleRefTable = new TableInfo(mdEditor.ModuleData, tablesStream.ModuleRefTable, rid => { this.tablesStream.TryReadModuleRefRow(rid, out var row); return row; }); allTableInfos[(int)Table.TypeSpec] = TypeSpecTable = new TableInfo(mdEditor.ModuleData, tablesStream.TypeSpecTable, rid => { this.tablesStream.TryReadTypeSpecRow(rid, out var row); return row; }); allTableInfos[(int)Table.ImplMap] = ImplMapTable = new TableInfo(mdEditor.ModuleData, tablesStream.ImplMapTable, rid => { this.tablesStream.TryReadImplMapRow(rid, out var row); return row; }); allTableInfos[(int)Table.FieldRVA] = FieldRVATable = new TableInfo(mdEditor.ModuleData, tablesStream.FieldRVATable, rid => { this.tablesStream.TryReadFieldRVARow(rid, out var row); return row; }); allTableInfos[(int)Table.ENCLog] = ENCLogTable = new TableInfo(mdEditor.ModuleData, tablesStream.ENCLogTable, rid => { this.tablesStream.TryReadENCLogRow(rid, out var row); return row; }); allTableInfos[(int)Table.ENCMap] = ENCMapTable = new TableInfo(mdEditor.ModuleData, tablesStream.ENCMapTable, rid => { this.tablesStream.TryReadENCMapRow(rid, out var row); return row; }); allTableInfos[(int)Table.Assembly] = AssemblyTable = new TableInfo(mdEditor.ModuleData, tablesStream.AssemblyTable, rid => { this.tablesStream.TryReadAssemblyRow(rid, out var row); return row; }); allTableInfos[(int)Table.AssemblyProcessor] = AssemblyProcessorTable = new TableInfo(mdEditor.ModuleData, tablesStream.AssemblyProcessorTable, rid => { this.tablesStream.TryReadAssemblyProcessorRow(rid, out var row); return row; }); allTableInfos[(int)Table.AssemblyOS] = AssemblyOSTable = new TableInfo(mdEditor.ModuleData, tablesStream.AssemblyOSTable, rid => { this.tablesStream.TryReadAssemblyOSRow(rid, out var row); return row; }); allTableInfos[(int)Table.AssemblyRef] = AssemblyRefTable = new TableInfo(mdEditor.ModuleData, tablesStream.AssemblyRefTable, rid => { this.tablesStream.TryReadAssemblyRefRow(rid, out var row); return row; }); allTableInfos[(int)Table.AssemblyRefProcessor] = AssemblyRefProcessorTable = new TableInfo(mdEditor.ModuleData, tablesStream.AssemblyRefProcessorTable, rid => { this.tablesStream.TryReadAssemblyRefProcessorRow(rid, out var row); return row; }); allTableInfos[(int)Table.AssemblyRefOS] = AssemblyRefOSTable = new TableInfo(mdEditor.ModuleData, tablesStream.AssemblyRefOSTable, rid => { this.tablesStream.TryReadAssemblyRefOSRow(rid, out var row); return row; }); allTableInfos[(int)Table.File] = FileTable = new TableInfo(mdEditor.ModuleData, tablesStream.FileTable, rid => { this.tablesStream.TryReadFileRow(rid, out var row); return row; }); allTableInfos[(int)Table.ExportedType] = ExportedTypeTable = new TableInfo(mdEditor.ModuleData, tablesStream.ExportedTypeTable, rid => { this.tablesStream.TryReadExportedTypeRow(rid, out var row); return row; }); allTableInfos[(int)Table.ManifestResource] = ManifestResourceTable = new TableInfo(mdEditor.ModuleData, tablesStream.ManifestResourceTable, rid => { this.tablesStream.TryReadManifestResourceRow(rid, out var row); return row; }); allTableInfos[(int)Table.NestedClass] = NestedClassTable = new TableInfo(mdEditor.ModuleData, tablesStream.NestedClassTable, rid => { this.tablesStream.TryReadNestedClassRow(rid, out var row); return row; }); allTableInfos[(int)Table.GenericParam] = GenericParamTable = new TableInfo(mdEditor.ModuleData, tablesStream.GenericParamTable, rid => { this.tablesStream.TryReadGenericParamRow(rid, out var row); return row; }); allTableInfos[(int)Table.MethodSpec] = MethodSpecTable = new TableInfo(mdEditor.ModuleData, tablesStream.MethodSpecTable, rid => { this.tablesStream.TryReadMethodSpecRow(rid, out var row); return row; }); allTableInfos[(int)Table.GenericParamConstraint] = GenericParamConstraintTable = new TableInfo(mdEditor.ModuleData, tablesStream.GenericParamConstraintTable, rid => { this.tablesStream.TryReadGenericParamConstraintRow(rid, out var row); return row; }); allTableInfos[(int)Table.Document] = DocumentTable = new TableInfo(mdEditor.ModuleData, tablesStream.DocumentTable, rid => { this.tablesStream.TryReadDocumentRow(rid, out var row); return row; }); allTableInfos[(int)Table.MethodDebugInformation] = MethodDebugInformationTable = new TableInfo(mdEditor.ModuleData, tablesStream.MethodDebugInformationTable, rid => { this.tablesStream.TryReadMethodDebugInformationRow(rid, out var row); return row; }); allTableInfos[(int)Table.LocalScope] = LocalScopeTable = new TableInfo(mdEditor.ModuleData, tablesStream.LocalScopeTable, rid => { this.tablesStream.TryReadLocalScopeRow(rid, out var row); return row; }); allTableInfos[(int)Table.LocalVariable] = LocalVariableTable = new TableInfo(mdEditor.ModuleData, tablesStream.LocalVariableTable, rid => { this.tablesStream.TryReadLocalVariableRow(rid, out var row); return row; }); allTableInfos[(int)Table.LocalConstant] = LocalConstantTable = new TableInfo(mdEditor.ModuleData, tablesStream.LocalConstantTable, rid => { this.tablesStream.TryReadLocalConstantRow(rid, out var row); return row; }); allTableInfos[(int)Table.ImportScope] = ImportScopeTable = new TableInfo(mdEditor.ModuleData, tablesStream.ImportScopeTable, rid => { this.tablesStream.TryReadImportScopeRow(rid, out var row); return row; }); allTableInfos[(int)Table.StateMachineMethod] = StateMachineMethodTable = new TableInfo(mdEditor.ModuleData, tablesStream.StateMachineMethodTable, rid => { this.tablesStream.TryReadStateMachineMethodRow(rid, out var row); return row; }); allTableInfos[(int)Table.CustomDebugInformation] = CustomDebugInformationTable = new TableInfo(mdEditor.ModuleData, tablesStream.CustomDebugInformationTable, rid => { this.tablesStream.TryReadCustomDebugInformationRow(rid, out var row); return row; }); } public override bool MustRewriteHeap() { foreach (var table in allTableInfos) { if (table?.HasChanges == true) return true; } return false; } public override bool ExistsInMetadata => tablesStream.StreamHeader is not null; } }